ISID テックブログ

ISIDが運営する技術ブログ

AWS WAF について最初から知りたかったこと8選

こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。

AWS WAF は簡単に Web アプリに WAF を追加でき、かつ値段も他の WAF 製品より安いため、好きな AWS サービスの一つです。そんな AWS WAF ですがしばらく構築・運用し、これを最初から知っておけば・・・と思ったことがあるので 8つご紹介します。
AWS WAF の基本については分かっている前提で、特に説明はいたしません。また2023年10月現在の最新バージョンである、いわゆる「AWS WAF v2」を対象としています。

その1: AWS マネージドルールのボディサイズ制限が厳しい

AWS WAF を利用する際に AWS マネージドルールはとても便利です。その中のコアルールセット (CRS) マネージドルールグループには SizeRestrictions_BODY というリクエストボディのサイズを検査するルールがあり、8 KB を超えるボディを持つリクエストをブロックします。ボディサイズが大きいリクエストや、ファイルアップロードにおいては 8 KB を簡単に超えてしまうことがあるので、本番環境に導入する前に想定するサイズのリクエストがブロックされないかしっかりテストが必要です。

※このようなルールが設定されている理由は、AWS WAF はリクエストボディの最初の 8 KB しか検査できないという制約があるためです。リクエストボディの 8 KB 以降の位置に攻撃文字列が含まれていても検査できないため、コアルールセットでは 8 KB を超えるリクエストを一律でブロックすることで対応しています。

想定する正当なリクエストが 8 KB を超えてしまう場合、ルールグループ内の SizeRestrictions_BODY ルールのアクションを「Count」にオーバーライドすることで、実質的に除外することができます。ただしボディサイズ制限が全くないのは心許ないので、次のように WAF Web ACL を作ることが考えられます。

  • コアルールセットマネージドルールグループでは SizeRestrictions_BODY を「Count」にオーバーライドする
  • 適切な値でボディサイズ制限を行う独自ルールを作成する
    • このようなルールを複数作り、リクエストパスに応じて異なるボディサイズの閾値を設定することも可能です(例えばファイルアップロードを受け付ける URI パスに対しては 100 KB に制限し、それ以外の URI パスに対しては 8 KB に制限するなど)
    • ボディサイズ制限の閾値を 8 KB より大きくする場合、前述の通り、それを超える部分のリクエストボディの中身は他のルールでも検査されないことに留意してください

なお、re:Post の次の投稿でも SizeRestrictions_BODY でブロックされた場合の対応方法が記載されています。マネージドルールに一致した際に追加されるラベルと独自ルールを組み合わせて、特定の URI パス(=ファイルアップロードを行う URI パス)ではない場合のみブロックを発動させる方法です。

AWS WAF によってブロックされているファイルをアップロードするにはどうすればよいですか?
https://repost.aws/ja/knowledge-center/waf-upload-blocked-files

その2: ファイルアップロードが AWS マネージドルールの XSS に引っかかることがある

Web アプリに対してバイナリファイルのアップロードをした時に、運悪くコアルールセット (CRS) マネージドルールグループCrossSiteScripting_BODY ルールに引っかかってしまったことがありました。そのときのログの一部は次の通りです。バイナリデータが XSSシグネチャに一致してしまったようです。

"terminatingRuleMatchDetails": [
    {
        "conditionType": "XSS",
        "location": "BODY",
        "matchedData": [
            "<",
            "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
        ]
    }
],

re:Post の次の投稿によると、ファイルアップロードでは CrossSiteScripting_BODY 以外にも SQLi_BODYWindowsShellCommands_BODYGenericLFI_BODYSizeRestrictions_BODY ルールによってブロックされる可能性があるようです。

AWS WAF によってブロックされているファイルをアップロードするにはどうすればよいですか?
https://repost.aws/ja/knowledge-center/waf-upload-blocked-files

投稿では対応方法として「文字列または正規表現 (regex) 一致条件が設定されたセーフリストを使用して、リクエストを許可します。」が紹介されています。マネージドルールに一致した際に追加されるラベルと独自ルールを組み合わせて、リクエストボディに特定の文字列(ファイル拡張子などを想定していると解釈しました)が含まれていない場合のみブロックを発動する方法です。(注:日本語の投稿ではなぜかリクエストヘッダーから特定の文字列を探すとありますが、英語の投稿ではリクエストボディから探すと書いてあります。リクエストボディの方がしっくりきています。)
あるいは「その1」の SizeRestrictions_BODY の場合と同じように、特定の URI パス(=ファイルアップロードを行う URI パス)ではない場合のみブロックを発動させる方法を取っても良さそうです。

その3: マネージドルールにはバージョンがある

マネジメントコンソールでマネージドルールを使っていたらすぐに気が付いたと思うのですが、筆者は CDK で Web ACL を作っていたのでずっと知りませんでした。マネージドルールにはバージョンが存在します
AWS マネージドルールのうち「IP レピュテーション、Bot Control、アカウント乗っ取り防止のルールグループ」にはバージョンが存在しません。バージョンに関わらず、日ごろから頻繁にルールが更新されるためでしょう。)

マネージドルールのバージョン指定画面

バージョンを特に指定しない場合、そのマネージドルールがデフォルトと設定したバージョンが利用され、ルールプロバイダーがデフォルトバージョンを変更すると構築した Web ACL でも自動的に新しいバージョンに更新されます。自動的に更新されることを避けたい場合は、「静的バージョン」を明示的に指定します。ただしこの場合も、「緊急時の必須の更新」がされる可能性があるそうです。またバージョンの有効期限が切れると、次のような扱いになるようです。

  • AWS マネージドルールルールグループの場合、AWS WAF は、有効期限切れのバージョンを使用しているウェブ ACL を、ルールグループのデフォルトバージョンに移動します。
  • AWS Marketplace ルールグループの場合、プロバイダーは有効期限の処理方法を決定します。詳細については、マネージドルールグループプロバイダーに問い合わせてください。

マネージドルールには SNS トピックが用意されている場合があり、サブスクライブすることでバージョンを含めたルールの更新情報を受け取ることができます。特に静的バージョンを使用する場合はルールバージョンを手動更新することになるので、SNS トピックをサブスクライブしておくと良いでしょう。

マネージドグループのSNSトピック

その4: CloudWatch Logs のロググループ名に決まりがある

AWS WAF を利用する場合はログをぜひ取っておきたいところですが、CloudWatch Logs に出力する場合、ロググループ名は aws-waf-logs- で始まる必要があります
https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/logging-cw-logs.html#logging-cw-logs-naming

マネジメントコンソールでロググループを指定する場合は画面にこの条件が書いてありますし、 aws-waf-logs- で始まるロググループ以外は選択肢に表示されないのでまだ分かりやすいです。

ロググループの選択画面

しかし CloudFormation (もちろん CDK も)を利用する場合、aws-waf-logs- で始まるロググループ以外を Web ACL に関連付けしようとすると、デプロイ時に次のようなエラーになります。「ARN が有効でない」というエラーメッセージでは何がいけないのか全く分からず、どハマりしたことがあるのは自分だけではないはずです。

Resource handler returned message: "Error reason: The ARN isn't valid. A valid ARN begins with arn: and includes other information separated by colons or slashes., field: LOG_DESTINATION, parameter: arn:aws:logs:ap-northeast-1:111122223333:log-group:waf-logs-xxxxx

ロググループ関連付けのエラー

ちなみにログの出力先が S3 バケットの場合も、バケット名は aws-waf-logs- で始まる必要があります。
https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/logging-s3.html#logging-s3-naming

その5: 35個のログストリームに分割される

CloudWatch Logs にログを出力すると、35個のログストリームに分割され、この数を減らすことはできません。出力のスループットを高めるためのようです。

ログストリームが分割される

近い時刻で発生したログメッセージでも、複数のログストリームに分散して出力されるため、ロググループから特定のログを探すのは難しいです。ログストリームを横断して時系列にログを見たり、特定のログを探したい場合は CloudWatch Logs Insights を利用するのが便利です。以下の公式ブログや re:Post 投稿が参考になります。

Amazon CloudWatch Logs による AWS WAF ログの分析
https://aws.amazon.com/jp/blogs/news/analyzing-aws-waf-logs-in-amazon-cloudwatch-logs/

CloudWatch または Amazon S3 に保存されている AWS WAF ログを分析するオプションは何ですか?
https://repost.aws/ja/knowledge-center/waf-analyze-logs-stored-cloudwatch-s3

WAF Web ACL のログを出力すると、httpRequest.headers フィールドにリクエストヘッダーが記録されます。つまり、クライアントが送信した Cookie も記録されてしまいます

ログに記録されたCookie
(スクリーンキャプチャの値はダミーです)

例えば分析のためにログを出力したり、共有したりすると有効なセッション ID などセンシティブな情報が漏えいするリスクがあります。
この問題への対応として、ログ出力時に特定フィールドをマスキングすることが可能です。例えば cookie ヘッダーをマスキングするには次のように設定します。

Cookieヘッダーをマスキングする設定

このように設定すると、出力されるログの cookie ヘッダーは REDACTED という文字列に置き換えられます。

Cookieヘッダーがマスキングされたログ

その7: ログ出力条件の EXCLUDED_AS_COUNT とは何か

ログを出力する場合、アクションが特定の条件に該当する場合のみ出力するように設定できます。リクエストが許可された場合のログも全て記録すると量が多くなってしまうので、「ブロック」時と「カウント」時のみ出力したいことがあります。

ログが出力される条件

カウント時の条件は、実は COUNTEXCLUDED_AS_COUNT の2種類があります。基本的には COUNT 扱いと考えて良いですが、 EXCLUDED_AS_COUNT として扱われるケースが2つ、以下のブログで説明されています。

AWS WAF のログ分析に関する考慮事項
https://aws.amazon.com/jp/blogs/news/aws-waf-log-analysis-considerations/

  • マネージドルールグループで ”Set all rule actions to count” で Count に設定した場合
  • マネージドルールグループで個別のルールを Count に設定した場合

ただし上の AWS のブログは情報が古く、現在個別のルールを Count に設定した場合の動きは少々異なるようです(参照)。簡単にまとめると、以下のようになると理解しています:

  • 2022 年 10 月 27 日より前に、
    • WAF ルールの JSON では ExcludedRules が利用でき、これに含めた個別ルールは EXCLUDED_AS_COUNT 扱いになる
    • マネジメントコンソールで個別ルールを Count に設定すると、 EXCLUDED_AS_COUNT 扱いになる
  • 2022 年 10 月 27 日以降は、
    • WAF ルールの JSONExcludedRules に含めた個別ルールは変わらず EXCLUDED_AS_COUNT 扱いになる
    • WAF ルールの JSONRuleActionOverrides が新しく利用でき、オーバーライド先をカウントにした場合は COUNT 扱いになる
    • マネジメントコンソールで個別ルールを Count に設定(過去に作成したルールの編集も含めて)すると、COUNT 扱いに変わる

この動きを意識してログ出力条件を作成すると良いと思います。

その8: コンソールで作成したルールを JSON 出力すると IaC での書き方がわかる

CloudFormation もしくは CDK で複雑な条件の Web ACL を作成する場合、書き方が分からずに困ることがあります。
例えば「URIパスが /file/upload 以外へのリクエストでボディサイズが 100 KB 以上の場合にブロックする」ルールは、CDK だとこんな感じで書くことになります。(ルール部分のみ)

{
    name: "SizeConstraint",
    priority: 10,
    statement: {
        andStatement: {
            statements: [
                {
                    notStatement: {
                        statement: {
                            byteMatchStatement: {
                                searchString: "/file/upload",
                                fieldToMatch: {
                                    uriPath: {},
                                },
                                textTransformations: [
                                    {
                                        priority: 0,
                                        type: "NONE",
                                    },
                                ],
                                positionalConstraint: "EXACTLY",
                            },
                        },
                    },
                },
                {
                    sizeConstraintStatement: {
                        fieldToMatch: {
                            body: {
                                oversizeHandling: "CONTINUE",
                            },
                        },
                        comparisonOperator: "GE",
                        size: 100 * 1000,
                        textTransformations: [
                            {
                                priority: 0,
                                type: "NONE",
                            },
                        ],
                    },
                },
            ],
        },
    },
    action: { block: {} },
    visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudWatchMetricsEnabled: true,
        metricName: "SizeConstraint",
    },
},

これをヒントなしで正しく書ける気がしません。一方でマネジメントコンソールでルールを作成すれば、画面の説明に従って割と分かりやすく作成できます。

WAFルールビジュアルエディタ

そこで IaC で WAF のルールを作る場合も、まずはマネジメントコンソールで試しに作成してみて、画面で「ルール JSON エディタ」に切り替えましょうJSON でのルールの書き方を表示してくれます。これをコピーして IaC で利用すると良いでしょう。(CDK に使う場合はプロパティ名の頭文字を大文字から小文字に変換する必要がありますが、自前で書くよりはずっと楽です。)

WAFルールJSONエディタ

ちなみにルール JSON エディタ画面の説明にもある通り、JSON を使えばビジュアルエディタではサポートされていないような、深い階層の条件を持つルールを作ることもできます。以下の re:Post 投稿も参考になります。

複雑なカスタム AWS WAF JSON ルールを作成する方法を教えてください。
https://repost.aws/ja/knowledge-center/waf-create-complex-custom-rules

さいごに

ご紹介した内容はいずれも公式ドキュメントやブログに書いてあることなのですが、WAF を使い始める時は早く構成したい気持ちが先走ってなかなか網羅的にチェックはできませんね。今振り返ると最初に知っておきたかったと感じたことをまとめてみました。誰かの参考になれば嬉しいです。

記事の中に掲載したリンクで特におすすめのものを再掲しておきます。

以下は、この記事では触れませんでしたが参考となる re:Post の投稿です。


私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。

セキュリティエンジニア

執筆:@kou.kinyo、レビュー:寺山 輝 (@terayama.akira)
Shodoで執筆されました