AWS WAFの基礎が学べるワークショップをやってみた

AWS WAFの基礎が学べる公式ワークショップをやってみました。内容がかなりしっかりしているため、AWS WAFの学習を始める足掛かりとしてぴったりではないでしょうか。
2023.08.20

こんにちは、AWS事業本部@福岡オフィスのべこみん(@beco_minn)です。

皆さんAWS WAFを使ったことはありますか?AWS WAFは名前の通り、AWSが提供するウェブアプリケーションファイアウォールサービスで、Web ACLというリソースをALBやAmazon CloudFrontディストリビューションなどにアタッチして利用出来ます。

今回はそんなAWS WAFのいろはを学ぶことが出来るハンズオン形式のAWS公式ワークショップを触ってみました。内容は英語ですが、動画等のコンテンツは無いためブラウザの翻訳機能で日本語化可能です。

このワークショップで学べること

本ワークショップはAWS WAFの基礎が詰まった内容で、主に下記を学ぶことが出来ます。

  • AWS WAFとは何か
  • Web ACLとマネージドルールの設定方法
  • カスタムルールの設定方法
  • 高度なカスタムルールの設定方法
  • 新しいルールのテスト方法について
  • AWS WAFのロギングについて
    • Kinesis Firehoseを経由したS3バケットへのログ出力方法が記載されています。現在はKinesis Firehoseを経由しなくてもS3バケットへログを吐き出すことが出来るため、ここに関しては記述が古いと感じました。

やってみた

本ワークショップはハンズオン形式ですが、ハンズオンの結果がクイズになっているためユーザーが実際に考えながら触ることが出来ました。すぐに答えやヒントを見るのではなく自分で考えることで、より深くワークショップの内容を理解出来るはずです。

具体的には各チャプターが下記のような構成になっています。

  • サービスやリソースの概要説明
  • チャレンジ
    • 問題
    • テスト実行(動作確認)
    • 模範回答
  • 結論

Introduction

ここでは下記が記載されていました。

  • AWS WAFとは何か
  • ワークショップのための事前準備
    • OSごとの準備。curlAWS CLIが使える環境があればOK。
  • サンプルWebアプリケーションの準備方法
  • ワークショップにかかる時間とコスト(料金)
    • 想定時間は120分
    • AWSリソースの想定総コストは2.00USD

サンプルWebアプリケーションですが、本ワークショップではWebアプリとしてOWASP Juice Shopを使用します。ドキュメント内にCloudFormationのテンプレートが用意されているため、手順に従ってサクッとデプロイしましょう。ドキュメントに記載されているリージョンでのデプロイを推奨しますが、東京リージョンでも一応デプロイは可能でした。

Web ACLs and Managed Rules

ここからがワークショップのスタートです。

AWS WAFにはWeb ACLというリソースが存在するのですが、ここではそのWeb ACLとそのWeb ACLにアタッチするマネージドルール(特にAWSマネージドなルール)について説明されています。

説明によると、どうやらこのWeb ACLというリソースがAWS WAFの核となるリソースであり、ユーザーが作成したWeb ACLをALBやCloudFront ディストリビューションにアタッチすることでWAFとして機能するようです。

本チャプターでは最初にWeb ACLとマネージドルールの概要が記載されていて、その後にハンズオン形式のチャレンジが用意されています。

まずはパートAです。どうやらAWS WAFのコンソールからCloudFrontにアタッチするためのWeb ACLを作れば良いようです。

Web ACLの作成画面でリソースタイプとしてAmazon CloudFront distributionsを選択し、AWSリソースとして作成済みのCloudFront ディストリビューションを選択します。ディストリビューションを選択する際、「Workshop」で検索するとすぐに出てきます。

これでパートAはクリアです。まだWeb ACLは作成途中ですね。次のパートBでマネージドルールを設定し、Web ACLの作成を完了するようです。

パートBでは次の条件が記載されています。

  1. Add the Core Rule Set which will cover a wide range of vulnerabilities common to web applications.
  2. Add the SQL database which will provide rules to protect against exploits of SQL databases, such as SQL injection.

そのため、AWS managed rule groupsから Core rule setSQL database をアタッチします。

Web ACLにルールをアタッチし、作成完了しました。

どうやらパートBでは動作確認テストがあるようです。

指示通りにcurlコマンドで実際にリクエストを飛ばしてみましょう。

JUICESHOP_URL はCloudFront ディストリビューションのドメイン名を指定します。

ここの動作確認では実際にAWS WAFをアタッチしたCloudFront ディストリビューションに対し、XSS攻撃とSQLインジェクション攻撃を含むリクエストを投げて、どちらもブロックされることを確かめるようです。

まずはXSSのリクエストを投げてみます。

$ curl -X POST  $JUICESHOP_URL -F "user='<script><alert>Hello></alert></script>'"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: nMFjUU8Gb3I84iDmVIq0F9DXDNLP-w4wnnSD5OFR5q9wf52ZMzys2A==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>%

403が返ってきました。しっかりとブロックされていることが分かりますね。

$ curl -X POST $JUICESHOP_URL -F "user='AND 1=1;"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: o2GBOU89VcoKhn7K_hzfTdQrFhAtY_pEGaxicRCeNx7bWiTpP8EbhA==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>%

SQLiのリクエストも同様にブロックされました。AWS WAFは正常に機能しているようです。

最後に答えを確認し、自分の中で納得出来れば次のチャプターへ進みましょう。

Custom Rules

先ほど設定したルールは、あらかじめAWSやサードパーティが用意しているマネージドルールでした。ここではユーザーがカスタマイズ可能なカスタムルールについてのチャプターになっています。

それでは手順に従ってカスタムルールを作成、追加します。

このままルールを作成しようとしたのですが、Oversize handlingを指定していませんでした。下図のように設定します。

ルールを追加すると、下図のようになります。

ルールを追加したら動作確認を行います。Test Caseに記載されているcurlコマンドを実行し、先ほどのチャプターと同様にリクエストがブロックされればOKです。

ここでは実行結果を割愛します。

Advanced Custom Rules

次は高度なカスタムルールのチャプターです。先ほどのカスタムルールはコンソールからGUIでポチポチ作成しましたが、Web ACLのカスタムルールはJSON形式で定義可能です。

JSONでルールを定義することにより、GUI上では設定出来ない複雑なルールを定義することも可能です。

ドキュメントの内容に沿って作成したJSONファイルが下記です。ハンズオンを実施してみたい方は下記を見る前にご自身でチャレンジしてみてください。

{
    "Name": "complex-rule-challenge",
    "Priority": 0,
    "Action": {
        "Block": {}
    },
    "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "complex-rule-challenge"
    },
    "Statement": {
        "OrStatement": {
            "Statements": [
                {
                    "AndStatement": {
                        "Statements": [
                            {
                                "ByteMatchStatement": {
                                    "FieldToMatch": {
                                        "SingleHeader": {
                                            "Name": "x-milkshake"
                                        }
                                    },
                                    "PositionalConstraint": "EXACTLY",
                                    "SearchString": "chocolate",
                                    "TextTransformations": [
                                        {
                                            "Type": "NONE",
                                            "Priority": 0
                                        }
                                    ]
                                }
                            },
                            {
                                "ByteMatchStatement": {
                                    "FieldToMatch": {
                                        "SingleHeader": {
                                            "Name": "x-favourite-topping"
                                        }
                                    },
                                    "PositionalConstraint": "EXACTLY",
                                    "SearchString": "nuts",
                                    "TextTransformations": [
                                        {
                                            "Type": "NONE",
                                            "Priority": 0
                                        }
                                    ]
                                }
                            }
                        ]
                    }
                },
                {
                    "AndStatement": {
                        "Statements": [
                            {
                                "ByteMatchStatement": {
                                    "FieldToMatch": {
                                        "SingleQueryArgument": {
                                            "Name": "milkshake"
                                        }
                                    },
                                    "PositionalConstraint": "EXACTLY",
                                    "SearchString": "banana",
                                    "TextTransformations": [
                                        {
                                            "Type": "NONE",
                                            "Priority": 0
                                        }
                                    ]
                                }
                            },
                            {
                                "ByteMatchStatement": {
                                    "FieldToMatch": {
                                        "SingleQueryArgument": {
                                            "Name": "favourite-topping"
                                        }
                                    },
                                    "PositionalConstraint": "EXACTLY",
                                    "SearchString": "sauce",
                                    "TextTransformations": [
                                        {
                                            "Type": "NONE",
                                            "Priority": 0
                                        }
                                    ]
                                }
                            }
                        ]
                    }
                }
            ]
        }
    }
}

上記JSONで定義したルールを追加したら、動作確認を行いましょう。今回はリクエストが通るパターンとブロックされるパターンが用意されています。

本チャプターも実行結果は割愛します。

Testing New Rules

次は新しいルールのテスト方法についてです。

基本的にWAFで新しいルールを導入する場合、誤検知によるリクエストブロックを警戒してカウントモードに設定します。カウントモードに設定することで、実際にどのようなリクエストが新ルールに引っ掛かるのかを確認することが出来ます。

本チャプターではそのことについての説明と、簡単なチャレンジが用意されています。

チャレンジ通りにルールを作成し、動作確認を行います。

ただし、今回はリクエストのログを確認するためにCloudWatch Logsへのログ出力設定を行います。

下図のようにLogging and metricsから設定します。ログの出力先にはCloudWatch Logs log groupを選択します。

それでは動作確認のcurlコマンドを実行しましょう。ここでは動作確認の実行結果は割愛します。

実行後、CloudWatch Logsでログを確認します。

ログはCloudWatch Logsコンソールからグラフで見ても良いですし、Web ACLコンソールからCloudWatch Log Insightsで確認してみても良いと思います。

一通り確認したら次のチャプターに進みましょう。

Logging

まだCleanupがありますが、ここが実質最後のチャプターです。最後はロギングについてです。

先ほどCloudWatch Logsへのロギング設定を行いましたが、ここではS3バケットへのロギング出力について説明されています。

ただし、本ワークショップは更新されていないためか本チャプターではAmazon Kinesis Firehoseを利用したS3バケットへのログ出力が紹介されていました。

2021年のアップデートでWeb ACLのトラフィックログをS3バケットに直接出力することが出来るようになっているので、本チャプターは割愛しても問題無いかと思います。

私は下記の手順で実施しました。

  • ログ出力用のS3バケットを作成
  • Web ACLコンソールにてLogging and metricsの設定を行い、出力先をS3に指定する
  • 動作確認を行い、ログが出力されていることを確認する

Cleanup

最後にハンズオンで作成したリソースの片付け作業を行います。

下記リソースを削除します。

  • サンプルWebアプリケーション(CloudFrontディストリビューション含む)
    • CloudFormationのスタックごと削除します
  • AWS WAF Web ACL
    • AWS WAFコンソールから削除します
  • Kinesis Data Firehose
    • 作成した場合はKinesisコンソールから削除します
  • S3バケット
    • S3コンソールから削除します

まとめ

一部内容が古い箇所もありましたが、本ワークショップを行うことでAWS WAF Web ACLの基礎的な部分については理解出来るのではないでしょうか。

私もこれからWAFの勉強を行っていくにあたり、本ワークショップは良い復習になりました。特にあまり使う頻度が少ない高度なカスタムルールのチャプターは良い勉強になりましたね。

本記事がどなたかのお役に立てれば幸いです。

以上、べこみんでした。