Azure Web Application Firewall (WAF) の導入と運用

[センシンロボティクス Advent Calendar](https://qiita.com/advent-calendar/2019/sensyn-robotics) 11 日目です。

こんにちわ。@kotetsu-sr です。2019年1月にセンシンロボティックスに参画しました。この一年、ドローン周辺の動画配信システムの構築、システム全体のインフラまわりの色々、DevOps みたいなことをやってきました。センシンロボティクスは [Microsoft For Startups に採択されている](https://www.sensyn-robotics.com/news/microsoft-for-startups)こともあり、Microsoft Azure を活用しています。

半年くらい前の話になりますが、Azure Application Gateway 上で提供する Web Application Firewall(以下、WAF)を実際に導入したときの記録です。この記事では WAF の詳細や、Application Gateway そのものの導入には触れません。また Azure Application Gateway は SKU v1 を対象にしています。

## 導入に向けて
### 転ばぬ先の杖としての WAF
現在運用しているウェブアプリケーションに既知の脆弱性がない場合でも、今後新たな脆弱性が発見され、ミドルウェアのバージョンアップが必要であったり、すぐに対応することが困難な状況もあると思います。WAF を導入することにより不審なリクエストをアプリケーションで処理する前に遮断することで**未知の脆弱性に対する事前対策**になることを期待しました。もちろん WAF は完全に攻撃を防いでくれるわけではないので、アプリケーションの脆弱性がないような状態に保っておくことは大切です。

### マネージドな WAF がそこにあった
まだ小さな開発チームなので限られたリソースで、多種多様な課題を解決しなければなりません。WAF の導入だけに多くの時間を割くことは難しい状況でした。そこで、元来利用していた [Azure Application Gateway](https://docs.microsoft.com/en-us/azure/application-gateway/overview) (v1 SKU) が WAF の機能を兼ね供えていたのでそれを利用することにしました。設定の変更だけで、新たにリソースを追加する必要もありません。また、リクエストを拒否するモード(防止モード)と、検出するだけのモード(検出モード)を簡単に切り替えることが可能です。**手軽に試してみることができたこと**で導入までのハードルが一気に下がりました。

### OWASP CRS ベース
Azure の提供する WAF は [OWASP Core Rule Set](https://coreruleset.org/) ベースのシグネイチャ型の WAF です。ルールセットは GitHub で公開されているので、検出された場合の分析はルールがブラックボックスになっている製品より容易です。ルールセットの更新は Azure が自動的に行うので、利用者が更新する必要はありません。v1 SKU なので、ルールやスコアの細かなカスタマイズはできませんでしたが(v2 SKU だとカスタマイズが可能)、カスタマイズすることで運用コストも増えるので、まずは基本のルールセットをそのまま利用することにしました。

## 稼動までの道程

### Application Gateway の診断ログを有効化
WAF のログを [Azure Log Analytics](https://docs.microsoft.com/azure/azure-monitor/log-query/get-started-portal) に転送しておきます。こうすることで Azure Log Analytics から Kusto と呼ばれるクエリ言語でログに対して検索を行うことができます。

(参考) Azure Portal を使用したログの有効化
https://docs.microsoft.com/azure/application-gateway/application-gateway-diagnostics#enable-logging-through-the-azure-portal

### WAF 機能の有効化
**検出モードで** WAF を有効にします。防止モードにしてしまうと偽陽性(false positive)のリクエストがあった場合、ブロックされてしまい正常にアプリケーションが動作しなくなります。Azure Portal を利用する場合、`アプリケーション ゲートウェイ`→`Web アプリケーション ファイアウォール`から設定を変更できます。この状態で一週間ほど放置しておきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58490/22175b13-aefa-1857-4ed6-7fa260b56be5.png)

### ファイアーウォールログの収集
ルールに一致したリクエストがあった場合、診断ログ(`AzureDiagnostics`)に記録されます、これは [Azure Log Analytics](https://docs.microsoft.com/azure/azure-monitor/log-query/get-started-portal) からクエリを実行すれば、表示したり、 csv ファイルにエクスポートすることができます。テーブル名が `ApplicationGatewayFirewallLog` ではないことに注意してください。

```
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| sort by TimeGenerated asc
```
各カラムの意味は以下のページに記載されています。

(参考) Application Gateway のバックエンドの正常性および診断ログ
https://docs.microsoft.com/azure/application-gateway/application-gateway-diagnostics#firewall-log

## 問題のあったリクエストのリストアップと対応の検討
上記で収集したログを元に、検知された偽陽性のリクエストを以下のような形で GitLab の Issue にまとめました。導入においてこれがあまりに多いと挫折しそうですが、今回対象としたシステムでは十数種類だったので、開発メンバーに協力してもらって対応はそれほど時間をかけずにできました。

```
## 8. 920170: GET or HEAD Request with Body Content.
### 検出されたアクション
* `GET api.example.com/drones`
### OWASP ルール
* https://github.com/SpiderLabs/owasp-modsecurity-crs/blob/v3.0.0/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf#L226-L264
### ログ内容の分析
* Content-Length ヘッダが存在しない。
* details_message_s
  * ```Warning. Match of "rx ^0?$" against "REQUEST_HEADERS:Content-Length" required.```
### 回避方法
* API クライアント側で Content-Length を設定する。
### 類似の問題
* No.9
```

正常なリクエストを WAF でブロックしないようにするため、検知された偽陽性リクエストに対して以下のいずれかの対応が必要になります。

* WAF に検知されないようなリクエストを送るように、アプリケーションの改修する。
* WAF のルールから除外する。

この例のようにアプリケーションの不備や、変更が軽微である場合は改修で対応し、変更が困難である場合はルールから除外しました。

(参考) Azure portal を使用した Web アプリケーション ファイアウォール規則のカスタマイズ
https://docs.microsoft.com/azure/web-application-firewall/ag/application-gateway-waf-configuration

## ログの監視
WAF に検出された場合通知する仕組みを用意します。実際に攻撃された場合の状況把握と、正常なリクエストが遮断された場合に対応が必要になるためです。今回は [Grafana](https://grafana.com/) で監視の仕組みを構築しました。
詳細は割愛しますが、Grafana の Azure Monitor Plugin を用いると Azure Monitor のメトリクスからアラートを作成したり、Azure Log Analytics へクエリを実行した結果を表示することができます。
検知は Application Gateway のメトリクス `Web Application Firewall Total Rule Distribution` でグラフを作成し、アラートを設定しました。発生したアラートは Slack に通知するようにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58490/2cfabbea-4997-ae3b-2418-5352c2c432bf.png)

ログの確認は `ApplicationGatewayFirewallLog` の内容をテーブルとしてダッシュボードに表示するようにします。テーブルには OWASP CRS の GitHub へのリンクを埋め込んで、どのルールに抵触したのかがすぐにわかるようにしておきます。本筋ではありませんが、攻撃的なリクエストを観察することができるので、セキュリティの生きた教材になります。

```
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where $__timeFilter(TimeGenerated)
| extend owasp_url = strcat("https://github.com/SpiderLabs/owasp-modsecurity-crs/blob/v3.0/master/", details_file_s, "#L", details_line_s)
| sort by TimeGenerated desc
| project owasp_url,TimeGenerated,hostname_s,clientIp_s,ruleId_s,action_s,Message,details_message_s,requestUri_s,details_data_s
```

実際に運用しているダッシュボードです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58490/322f3361-f953-bd3a-31cc-50842b068dfd.png)

(参考)ファイアウォール メトリック (WAF_v1 のみ)
https://docs.microsoft.com/azure/web-application-firewall/ag/web-application-firewall-troubleshoot#firewall-metrics-waf_v1-only

## 防止モードへの切替
偽陽性のリクエストが検出されなくなり、アラートを設定した段階で、防止モードを有効にします。
半年ほど本番環境で運用していますが、現在のところ大きなトラブルもなくたまに攻撃を検知しながら元気に動いています。

## 参考リンク
* [IPA の Web Application Firewall 読本](https://www.ipa.go.jp/security/vuln/waf.html)
* [Azure Web Application Firewall (WAF) の一般提供を開始](  https://blogs.technet.microsoft.com/jpitpro/2017/04/10/azure-web-application-firewall-waf-generally-available/)
* [v1 SKU と v2 SKU の機能比較](https://docs.microsoft.com/azure/application-gateway/application-gateway-autoscaling-zone-redundant#feature-comparison-between-v1-sku-and-v2-sku)
* [Azure Monitor Data Source For Grafana
](https://grafana.com/grafana/plugins/grafana-azure-monitor-datasource)
* [Getting started with Kusto
](https://docs.microsoft.com/azure/kusto/concepts/)