AWS WAFの概念を分かりやすく説明してみた
背景
私は、半年ほど前に仕事でAWS WAFを運用し始めました。
最初の頃は、AWS WAFの概念や挙動の理解に苦しめられました。少しづつ理解を深めた結果、今なら他人に説明できるレベルには達した気がします。 そこで、備忘録もかねてこの記事を書こうと思った次第です。
これから下に書かれている内容は、ラフに書かれているため、公式ドキュメントよりは分かりやすいかもしれませんが正確性は劣ります。
正確性を求める場合は、AWS WAF | AWSドキュメンテーションを参照ください。
以下、AWS WAFを、WAF
と記述します。
WAFの概略
- WAFでは、webACLというリソースに対してルール(コントロールとも呼ばれる)のセットを設定します。ルールには評価する順番を意味する優先度が設定されます。
- それぞれのルールは、独自のロジックに基づいてリクエストを評価します。
- 評価の結果は、一致が検出されるか、一致が検出されないかのいずれかです。例えば、bot検知のルールでは、リクエストを評価し、
botが検出された
か、botが検出されなかった
かのいずれかの評価となります。 - 一致が検出されたら、そのルールに対して事前に設定されたアクションが行われます。検出されない場合は何もせずに次のルールの評価に移ります。
アクションについて
WAFのアクションは、シンプルな仕様に見えて、認識がずれることが多いです。 そのため、私の認識がずれていたところを中心に、詳しく解説してみたいと思います。
まず、アクションにはALLOW
BLOCK
COUNT
CAPTCHA
Challenge
の5つがあります。
文字通り、ALLOWはルールへの一致が検出されたリクエストを許可し、BLOCKはルールへの一致が検出されたリクエストをブロックします。COUNT、CAPTCHA、Challengeについては以下で詳しく説明します。
COUNTについて
COUNTについては上記のような説明がされているのですが、私はこれを読んで完全に理解した気持ちにはなれませんでした。
非終了アクション
は後に説明するため、一旦脇に置かせて下さい。
そもそもカウントする
とはなんなのでしょうか?
カウントする、とは?
他の記事では、ログを取ることのように説明されていますが、COUNT以外のアクションでもログを取ることはできるのでこの説明は正確ではありません。
ずばり、カウントする
とは、後に評価されるルールで利用できるステータス(ラベル
と呼ばれます)を保持しておくことです。
例えば、API server全体に対してAWSが提供するXSS検知のルールを設定したとします。 このルールによって、明らかに正常だと分かっているリクエスト(定期ジョブによって発行されるリクエストなど)が誤ってBLOCKされてしまうことがあります。AWSが提供するルールの判定ロジックは、セキュリティの観点から公開されておらず、判定ロジックをいじることもできません。
このときにCOUNTをアクションとして設定します。
以下のようにwebACLを設定します。
- 優先度1: (AWSが提供) XSS検知のルール(アクションはCOUNT)
- 優先度2: (自分で作成) XSS検知されたリクエストのうち、明らかに正常なリクエストのみ通すルール(アクションはBLOCK)
最初のルールでXSSが検知されても、すぐにブロックせずに、「XSSが検知された」というラベルを保持しておきます。
次のルールでは、XSSが検知されたラベルが保持されている場合のみ、明らかに正常なリクエストかどうかを判定するロジックを組みます。
このように、後続のルールで利用できるステータスを保持できる性質を利用して、複数ルールの評価結果を組み合わせて判定ロジックを組むことができます。 他の具体的なユースケースについては、ウェブリクエストの AWS WAF ラベルが詳しいです。
webACLの設定を効率的にテストするためのCOUNT
上に載っていないCOUNTアクションのユースケースとして、WAFのテスト運用での活用があります。
正常なリクエストがWAFによってBLOCKされてしまうと、ユーザに迷惑をかけてしまいます。そのため、事前にテスト環境でWAFを運用し、正常なリクエストがブロックされないことと、攻撃と思われるリクエストがブロックされることを確かめておきたいです。
このとき、以下のようにwebACLを設定したとします。
- 優先度1: ルールA(アクションはBLOCK)
- 優先度2: ルールB(アクションはBLOCK)
- 優先度2: ルールC(アクションはBLOCK)
ルールAによってBLOCKされたリクエストは、ルールBとルールCによる評価が行われません。WAFのログをみた時に、正常なリクエストがルールAによってBLOCKされてしまったことは分かりますが、ルールBやルールCに関しては評価がされていないので未知数です。
そこで、以下のように設定してあげます。
- 優先度1: ルールA(アクションはCOUNT)
- 優先度2: ルールB(アクションはCOUNT)
- 優先度2: ルールC(アクションはCOUNT)
この設定では全てのルールが評価され、一致したルールのラベルがリクエストのログに含まれます。そのため、一気に3つのルールをテストできるというわけです。
COUNTモードに関する説明は以上です。
非終了アクションと、終了アクション
ついでに、非終了アクションと、終了アクションについて説明します。今ならスッと理解できると思われるためです。
複数のルールがwebACLに設定されている場合には、優先度が高い順から評価されていきます。
一致したルールのアクションがACTIONだった場合、そのリクエストは問答無用で許可され、後続のルールは評価されません。 同じように、一致したルールのアクションがBLOCKだった場合、その時点でstatus code 403のレスポンスが返され、後続のルールは評価されません。
これが、ACTIONとBLOCKは終了アクションである、という意味です。
一方、一致したルールのアクションがCOUNTだった場合、ラベルを保持して、次のルールの評価に移ります。そのルールの評価時点では、リクエストを許可することもブロックすることもしないため、非終了アクションと呼ばれます。
CAPTCHAとChallengeについて
CAPTCHAとChallengeというアクションは似ているようで、異なります。
まず、どちらもクライアントがbotかどうかを判定するという目的を持っている点では同じです。
どちらも、リクエストを送ってきたクライアントに対してチャレンジを要求します。クライアントがチャレンジに成功した場合のみリクエストを許可します。
WAFはクライアントがチャレンジに成功するとaws-waf-token
というクッキーを設定します。このクッキーはトークン
と呼ばれています。一回チャレンジに成功すると、次回からのリクエストでは、またチャレンジを要求するのではなく、トークンを検証するだけですみます。こうすることで、毎回、人間の手を煩わせたりするのを防げるわけですね。
次にCAPTCHAとChallengeの異なる点について説明します。
ずばり、CAPTHCAがクライアントが人間であるかどうかを確かめるチャレンジを要求するのに対して、Challengeはクライアントがブラウザであるかどうかを確かめるチャレンジを要求します。
人間であるかどうかを確かめるチャレンジは、パズルと呼ばれ、人間に操作を要求します。よくある例としては、「9つのパネルのうち車が写っているものを全て選択してください」みたいなものです。
AWS WAFで要求されるパズルについては、CAPTCHA パズルとは?が詳しいです。
CAPTCHAはクライアントが人間であるかを検証するために、パズルという形式のチャレンジを要求します。
一方、ブラウザであるかどうかを確かめるチャレンジは、 人間の操作なしに実施されます。公式ドキュメントでは、サイレントに実施される
と書かれてあります。
Challengeアクションでは、リクエストを受けたWAFが、JavaScriptのチャレンジを検証するスクリプトを含んだHTMLをクライアントに返します。クライアントがブラウザの場合は、そのスクリプトがブラウザで実行され、クライアントがブラウザかどうかが検証されます。
(ChallengeアクションでWAFから返されるHTML)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Human Verification</title> <style> body { font-family: "Arial"; } </style> <script type="text/javascript"> window.awsWafCookieDomainList = []; </script> <script src="https://xxxx.ap-northeast-1.token.awswaf.com/xxxx/xxxx/xxxx/xxxx.js"></script> </head> <body> <div id="challenge-container"></div> <script type="text/javascript"> AwsWafIntegration.checkForceRefresh().then((forceRefresh) => { if (forceRefresh) { AwsWafIntegration.forceRefreshToken().then(() => { window.location.reload(true); }); } else { AwsWafIntegration.getToken().then(() => { window.location.reload(true); }); } }); </script> <noscript> <h1>JavaScript is disabled</h1> In order to continue, we need to verify that you're not a robot. This requires JavaScript. Enable JavaScript and then reload the page. </noscript> </body> </html>
引用元: AWS WAFのChallengeアクションの動作を見てみる
CAPTCHAとChallengeは、終了アクション?非終了アクション?
CAPTCHAとChallengeは、終了アクションであるケースと、非終了アクションであるケースが存在します。
リクエストがブロックされると、Challengeの場合は、status code 202(Request Accepted)が返されます。CAPTCHAの場合は、status code 405(Method Not Allowed)が返されます。どちらも、チャレンジを実行するJavaScriptのスクリプトがレスポンスのHTMLに含まれます。
参照: CAPTCHA および Challenge アクション動作
ルールグループのアクションについて
冒頭で、webACLというリソースに対してルール(コントロールとも呼ばれる)のセットを設定します
と説明しましたが、これは少し単純化した記述でした。
実際には、webACLには、ルールに加えて、ルールグループを設定できます。ルールグループとは、複数のルールを意味のある単位にまとめられる仕組みです。
たとえば、Linuxに対するよくある攻撃を検知する複数のルールを1つのルールグループにまとめておくと、Linuxサーバを使っている時に1発で設定できるため便利です。
このルールグループに関して、似ているが全く挙動が異なる2パターンの設定オプションがあるため紹介します。
1. ルールグループに含まれるルールのオーバーライド
ルールグループは、webACLとは別の場所で管理され、複数のwebACLで再利用できるようになっています。プログラミング言語の機構に置き換えると、クラスとインスタンスみたいなイメージです。
クラスであるルールグループをwebACLに設定する際には、個々のルールアクションをオーバーライドしたインスタンスを作れます。
例えば、Linuxに対するよくある攻撃を検知するためのルールグループでは、全てのルールのアクションがBLOCKと設定されているとします。webACLに適用する際に、何らかの都合で一部のルールだけCOUNTにしたいということであれば、COUNTにオーバーライドすることができます。
COUNT以外のアクションにもオーバーライドできます。
2. ルールグループの評価結果をCOUNTにオーバーライド
これとは全く別の仕組みで、ルールグループの評価結果をCOUNTにオーバーライドすることができます。
ルールグループを活用して以下のようにwebACLを設定したとします。
- 優先度1: ルールA (アクションはBLOCK)
- 優先度2: ルールグループB
- ルールB1(アクションはBLOCK)
- ルールB2(アクションはCOUNT)
- ルールB3(アクションはBLOCK)
- 優先度3: ルールC(アクションはCOUNT)
これは、挙動としては、以下と同じです。
- 優先度1: ルールA (アクションはBLOCK)
- 優先度2: ルールB1(アクションはBLOCK)
- 優先度3: ルールB2(アクションはCOUNT)
- 優先度4: ルールB3(アクションはBLOCK)
- 優先度5: ルールC(アクションはCOUNT)
ここで、ルールグループBの評価結果をCOUNTにオーバーライドします。
- 優先度1: ルールA (アクションはBLOCK)
- 優先度2: ルールグループB(アクションをCOUNTにオーバーライド)
- ルールB1(アクションはBLOCK)
- ルールB2(アクションはCOUNT)
- ルールB3(アクションはBLOCK)
- 優先度3: ルールC(アクションはCOUNT)
これは以下の設定と同じ挙動です。
- 優先度1: ルールA (アクションはBLOCK)
- 優先度2: ルールB1(アクションはCOUNT)
- 優先度3: ルールB2(アクションはCOUNT)
- 優先度4: ルールB3(アクションはCOUNT)
- 優先度5: ルールC(アクションはCOUNT)
公式ドキュメントでは、次のように説明されています。
ルールグループ内のルールの設定または評価方法を変更せずに、ルールグループ評価の結果によって発生するアクションをオーバーライドできます
引用元: ウェブ ACL でのルールグループの動作の管理 | AWS公式ドキュメント
さいごに
私自身、公式ドキュメントと巷に溢れるWeb記事では、AWS WAFをちゃんと理解するには至れませんでした。 そのため、実際に動かしてみたりAWS Supportに質問することで理解していきました。
この記事では、私がAWS WAFを設定する時に理解しずらかった箇所を中心に、公式ドキュメントから一歩踏み込んだ説明をしました。
この記事が好評なら、WAFを運用する中で困ったことやハマりポイントについても記事を書こうと思うので、感想やフィードバックをいただけると嬉しいです。
具体的な指摘でも、「参考になった」「読みにくい」みたいな大まかなフィードバックでも構いません。