AWS WAFが攻撃を検知したあとの反応を整理する
AWS WAF
AWS WAFはAWSが提供するファイアウォールサービスのひとつでWebアプリケーションの保護を目的としています。
HTTPの通信の内容まで確認することでSQLインジェクションやXSSなどを検知・遮断することが可能です。
ルールを設定することで様々な攻撃に対応することができます。
また、マネージドルールと呼ばれるセキュリティベンダーが提供する定義済みのルールを利用することで素早く導入することができます。
AWS WAFについて詳しく知りたい人には以下の記事がおすすめです。
設定
今回は簡略化のためにSQLインジェクションを検知し、通信をブロックしてみましょう。 API Gateway + Lambdaで構築したREST APIをWAFで保護した状態で悪意のあるリクエストを送ってみましょう。
以下は参考にした記事です。
REST APIのデプロイ
今回Lambdaで実行するコードは以下のものです。
import json def injection(event, context): print(json.dumps(event)) body = { "response": "Success" } response = {"statusCode": 200, "body": json.dumps(body)} return response
Serverless Frameworkを使用してこれをデプロイしましょう。 今回はPOSTメソッドを攻撃対象にします。
service: injection frameworkVersion: '2' provider: name: aws runtime: python3.8 lambdaHashingVersion: '20201221' functions: injection: handler: handler.injection events: - http: path: / method: post
正しくデプロイできたか試してみましょう。
$ curl -s \ -X POST \ -H "Content-Type: application/json" \ -d '{"User": "Foo", "Pass": "secret"}' \ ${ENDPOINT} {"response": "Success"}
AWS WAFを設定する
以下のように設定します。
今回はAWSが用意したSQL用のルールを使用しました。
まずは普通にリクエストを送ってみましょう。
$ curl -s \ -X POST \ -H "Content-Type: application/json" \ -d '{"User": "Foo", "Pass": "secret"}' \ ${ENDPOINT} {"response": "Success"}
次にSQLインジェクションを狙ったリクエストを送ってみましょう。
4行目のUser内の' OR 'A' = 'A
がSQLインジェクションを狙ったものです。
$ curl -s \ -X POST \ -H "Content-Type: application/json" \ -d "{\"User\": \"' OR 'A' = 'A\", \"Pass\": \"secret\"}" \ ${ENDPOINT} { "message": "Forbidden" }
それらしいレスポンスが帰ってきました。
JSONのスキーマも違います。(response
フィールドがありません)
以下で詳しく見てきましょう。
攻撃を検知すると何が起きるのか
ここでいう攻撃の検知とはWAFのWeb ACLにて定義されたルールにマッチするリクエストが送られてきたということです。
今回はSQLインジェクションのパターンに一致するデータがリクエストのJSONに含まれていました。
以下ではクライアント側とサービス提供者側でそれぞれ何が起きるのか確認していきます。
クライアント側
先ほどと同様のリクエストを送信し、一緒にレスポンスヘッダも表示してみます。
$ curl -s \ -X POST \ -H "Content-Type: application/json" \ -d "{\"User\": \"' OR 'A' = 'A\", \"Pass\": \"secret\"}" \ --dump-header - \ ${ENDPOINT} HTTP/2 403 content-type: application/json content-length: 23 date: XXXXX x-amzn-requestid: XXXXXXX x-amzn-errortype: ForbiddenException x-amz-apigw-id: XXXXXXX x-cache: Error from cloudfront via: 1.1 XXXXXX.cloudfront.net (CloudFront) x-amz-cf-pop: NRTXXXXX x-amz-cf-id: XXXXXX {"message":"Forbidden"}
HTTPのステータスコードは403(Forbidden)です。
またx-amzn-errortype
もForbiddenException
となっています。
これはWAFによってブロックされた結果です。
レスポンスのスキーマも変化しています。
これは今回利用しているルールAWS-AWSManagedRulesSQLiRuleSet
によって設定されているレスポンスです。
カスタムレスポンスを設定することもできますが条件があります。
今回の用途だとその条件を満たさないので、レスポンスを変えたい場合には工夫が必要です。
サービス提供者側
今回はマネージドコンソールから確認していきます。 見やすいように以下のスクリプトで不正なリクエストを予め行っておきました。
まずはOverviewの画面から見ていきます。
CloudWatchに記録されたWAFのメトリクスとSampled Requests
の二つが表示されています。
CloudWatch
AWS WAFではリクエストとそのマッチ結果をCloudWatchのメトリクスとして利用できます。 CloudWatch側で確認すると以下の6つのメトリクスが利用できるようです。
今回はBlockedだけですが、メトリクス名にBlocked,Allowed,Countedの三つがあります。
- Allowed: 許可されたリクエスト
- Blocked: ブロックされたリクエスト
- Counted: カウントモードで検知されたリクエスト
大まかに分けるとRuleには今回利用しているSQL用のルールとALLの二種類があります。
これはルールが増えるごとに増えていきます。
今回は1つしかルールがないのでALLとSQL用のルールのメトリクスは一致していますが、
ALLでは全てのルールの合計値がメトリクスとして利用できます。
ちなみにCloudWatchでのモニタリングが可能なのでアラームの設定も可能です。
SampledRequest
AWS WAFではリクエストの内容とそれに対する処理が記録されています。 過去3時間分についてはこのSampledRequestから確認できます。 リクエストの詳細を確認してみると以下のようになっています。
どのルールに検知されブロックされたのかが確認できました。 意図した通りにルールが設定するか確認するのに便利です。 ただ、リクエストのどの部分が問題となったのかや、bodyの情報は見れない点は注意が必要です。
ロギング
リクエストのログはKinesis Data Firehose経由でS3などに保存することができます。
このときKinesis Date Firehoseの名前はaws-waf-logs-
から始まる必要があります。
Athenaなどを使用すればログの分析も可能です。
ログではリクエストのbodyは保存されませんが、どの部分にマッチしたのかを見ることはできます。
以下がログの例です。
{ "timestamp": XXXXX, "formatVersion": 1, "webaclId": "arn:aws:wafv2:XXXXXX", "terminatingRuleId": "AWS-AWSManagedRulesSQLiRuleSet", "terminatingRuleType": "MANAGED_RULE_GROUP", "action": "BLOCK", "terminatingRuleMatchDetails": [ { "conditionType": "SQL_INJECTION", "location": "BODY", "matchedData": [ "{\"User\": \"", "OR", "A", "=", "A\", \"Pass\": \"secret\"}" ] } ], "httpSourceName": "APIGW", "httpSourceId": "XXXXX:XXXXX:dev", "ruleGroupList": [ { "ruleGroupId": "AWS#AWSManagedRulesSQLiRuleSet", "terminatingRule": { "ruleId": "SQLi_BODY", "action": "BLOCK", "ruleMatchDetails": null }, "nonTerminatingMatchingRules": [], "excludedRules": null } ], "rateBasedRuleList": [], "nonTerminatingMatchingRules": [], "requestHeadersInserted": null, "responseCodeSent": null, "httpRequest": { "clientIp": "XXX.XXX.XXX.XXX", "country": "JP", "headers": [ { "name": "X-Forwarded-For", "value": "XXX.XXX.XXX.XXX" }, { "name": "X-Forwarded-Proto", "value": "https" }, { "name": "X-Forwarded-Port", "value": "443" }, { "name": "Host", "value": "XXXXX.us-east-1.amazonaws.com" }, { "name": "X-Amzn-Trace-Id", "value": "XXXXXX" }, { "name": "Content-Length", "value": "43" }, { "name": "User-Agent", "value": "curl/7.77.0" }, { "name": "X-Amz-Cf-Id", "value": "XXXXXXXXX" }, { "name": "Via", "value": "2.0 XXXXX.cloudfront.net (CloudFront)" }, { "name": "Accept", "value": "*/*" }, { "name": "content-type", "value": "application/json" }, { "name": "CloudFront-Is-Mobile-Viewer", "value": "false" }, { "name": "CloudFront-Is-Tablet-Viewer", "value": "false" }, { "name": "CloudFront-Is-SmartTV-Viewer", "value": "false" }, { "name": "CloudFront-Is-Desktop-Viewer", "value": "true" }, { "name": "CloudFront-Viewer-Country", "value": "JP" }, { "name": "CloudFront-Forwarded-Proto", "value": "https" } ], "uri": "/dev/", "args": "", "httpVersion": "HTTP/1.1", "httpMethod": "POST", "requestId": "XXXXXXXXX" } }
感想
WAFを使用すればL7でのアプリケーションの保護が可能になることは知っていましたが、検知されたあとにどうなるかは理解が曖昧でした。 今回の記事を通して整理できたと思います。