サイトアイコン はぐれメタルはにげだした

AWS WAFでWordPressを保護するための設定例

[この記事を読むのにかかる時間] 4

AWS WAFはセットアップするだけならクリックぽちぽちでいけるので非常に簡単なんですが、いざ運用を検討しだすとハマりポイントがいくつも出てきて頭を悩ませてしまいます。比較的新しいサービスのせいかネット上の情報量も少なく、苦労している人も多いのではないでしょうか。

僕もそのうちの一人なんですが、使ってるうちに段々と痒いところが分かってきたので、これから数回に分けて少しずつノウハウを共有していこうと思います。

この記事では最もシンプルなレギュラールールの設定方法のみを書いていますので、もしマネージドルールの導入を検討されている方はAWS WAFマネージドルールの致命的な1つのデメリットの記事もあわせて読んでみてください。

AWS WAFの設定方法

AWS WAFはWebアプリケーションを悪意ある攻撃から保護するためのマネージドサービスです。現状、WAFで保護できるリソースはCloudFrontとALBのみですが、もしかしたら今後もっと増えてくるかもしれません。

今回はEC2上に立てたWordPressアプリをAWS WAFで保護するという想定で設定方法を説明していきます。WordPressのフロントにはALBを使用しますが、ALBとWordPressサーバの構築方法はここでは割愛します。

WAFは「条件」「ルール」「WebACL」という3つの要素を組み合わせて構築します。今回は「社内IP以外からのWordPress管理画面へのアクセスをWAFでブロックする」ことを目的にして、各要素を1つずつ順番に作成していきます。ちなみに先日wordpressにDoSの脆弱性が見つかった話(CVE-2018-6389)の記事で書いた脆弱性は、今回のWAF設定を適用すれば防御できるようになります。

条件 1. 社内IP
2. WordPress管理画面
ルール 1. 条件1に一致しない AND 条件2に一致する
WebACL 1. ルール1に該当するリクエストはBlock

 ルールは最大10個まで適用可能

いずれのルールにも該当しないリクエスト(デフォルトアクション)はAllow

条件

条件とは「リクエストのどこをWAFで評価するか」を定義するもので、この6項目から選択することができます。

条件 概要
IP 接続元のIP
地域 接続元の国
リクエストサイズ リクエストの大きさ
文字列 リクエストに特定の文字列が含まれているか
SQLインジェクション リクエストにSQLインジェクションの疑いがある文字列が含まれているか
クロスサイトスクリプティング リクエストにクロスサイトスクリプティングの疑いがある文字列が含まれているか

全ての条件タイプは、1つの条件につき100個までパラメータを指定できます。例えばIPであれば100IPまで、クエリストリングであれば100パターンの文字列までです。101個以上のIPを制限したい場合は、IP条件を2つ作成する必要があります。パラメータは正規表現で指定することもできますが、1アカウントあたり最大10個の正規表現条件しか設定できません。パラメータの上限100も正規表現の上限10も、上限緩和申請はできません。

条件1「社内IP」の作成

まず「社内IP」という条件を作成してみます。
マネージメントコンソールでWAFの管理画面を開き[IP addresses]をクリックします。

[Create Condition]をクリックします。

次にIP条件のパラメータを入力していきます。

Name 任意の条件名を入力。
Region CloudFrontならGlobal、ALBなら対象のリージョンを選択。
IP Version v4またはv6を選択。
Address 対象のIPを入力。CIDRで/8、/16、/24、/32を指定できます。

今回はap-northeast-1にあるALBにWAFで保護することにします。名前はOfficeIPとし、社内IPは13.115.194.191としておきます。[Address]まで入力すると[Add IP address of range]が押せるようになるのでクリックします。

[Add IP address of range]をクリックすると、すぐ下の[Filters in IP match condition]に条件が追加されます。続けて右下の[Create]をクリックします。

これで「条件:OfficeIP」が作成されます。

条件2「WordPress管理画面」の作成

続けて「WordPress管理画面」という条件を作成してみましょう。
WordPressの管理画面は「/wp-admin/*」というパスなので、このURIを評価する文字列条件を作ります。[String and regex matching]をクリックします。

[Create Condition]をクリックします。

次に文字列条件のパラメータを指定します。

Name 任意の条件名を入力。
Region CloudFrontならGlobal、ALBなら対象のリージョンを選択。
Type 素の文字列か正規表現かを選択
Part of the request to filter on 評価の対象を5つから選択する。

・Header
・HTTP Method
・Query String
・URI
・Body

Match type 一致条件を5つから選択する。

・Contains(部分一致)
・Exactoly matches(完全一致)
・Starts with(前方一致)
・Ends with(後方一致)
・Contains word(単語を含む)

Transformation 評価するときの文字列の変換条件を6つから選択する。

・None(変換しない)
・Convert to lowercase(小文字に変換)
・HTML Decode(HTMLデコードする)
・Normalize whitespace(空白を変換)
・Simplify command line(OSのコマンドラインを変換)
・URL Decode(URLデコードする)

Value is base64-encoded 値がBase64エンコーディングされてる場合はチェック
Value to match* 文字列の一致条件

今回はwp-adminという条件名で正規表現を使わずに文字列で制御することにします。

URIを前方一致で /wp-admin で引っ掛けます。パラメータを全て入力したら[Add filter]を押せるようになるのでクリックします。

[Add filter]をクリックすると、すぐ下の[Filters in this string match condition]に条件が追加されます。続けて右下の[Create]をクリックします。

これで「条件:wp-admin」も作成完了です。次に、この条件を組み合わせてルールを作成します。

ルール

作成した条件がリクエストに「一致する」か「一致しない」かを定義するのが「ルール」です。1つのルールにつき10個までAND条件を指定することができます。ORはできません。今回は「条件1:社内IP以外からの条件2:WordPress管理画面へのアクセス」を制御するので2つの条件を使います。

ルールの作成

[Rules]をクリックします。

[Filter]で[Asia Pacific (Tokyo)]を選択します。

[Create rule]をクリックします。

次にルールのパラメータを指定します。

Name 任意の条件名を入力。
CloudWatch metric name CloudWatchのメトリクス名。Nameを入力すると自動入力されます。
Rule type レギュラールールかレートベースルールのどちらかを選択します。レギュラールールの挙動はただルールを評価するだけなので、基本的にはレギュラールールを指定すれば良いです。

レートベースルールとは、DoS攻撃やブルートフォース攻撃など短時間での高頻度アクセスを防ぐために用意されているルールです。レートベースルールに該当するリクエストが同一IPから継続して発生した場合にのみ、指定したアクション(Allow、Block、Count)がトリガされます。閾値は最低2000以上の値を任意に指定でき、直近5分間のアクセス数が閾値を超えたIPのみがブラックリストに登録され、アクションが実行されます。アクション発生後、継続して5分以上閾値を下回った場合にはブラックリストが解除されます。

Region CloudFrontならGlobal、ALBなら対象のリージョンを選択。
Add conditions 一致条件 does(一致する)、does not(一致しない)のどちらかを選択
条件の種類 ルールに追加する条件の種類
条件名 ルールに追加する条件の名前

今回はwp-admin-accessというルール名でレギュラールールを作成します。また、2つの条件をAND条件で指定したルールを作るので、まずは「社内IPに一致しない」というルールを指定ために「does not OfficeIP」を選択します。次に[Add condition]をクリックします。

[Add condition]をクリックするとAND条件を指定できるので「does wp-admin」を選択します。最後に右下の[Create]をクリックします。

これでルールも作成完了です。てかスクショ見て気づきましたがtypoしてますねw
ap-admin-accessになってますがこのまま続けます。

WebACL

ルールに該当するリクエストに対して「Block」か「Allow」か「Count」のアクションを指定するのがWebACLです。1つのWebACLに定義できるルールは10個まで、いずれのルールにも該当しないリクエストはデフォルトアクションで処理されます。デフォルトアクションは「Block」「Allow」の2つから指定します。

各アクションの挙動は以下の通りです。

アクション 挙動
Allow リクエストをバックエンドのCloudFrontまたはALBに流す
Block リクエストにhttpコード403を返す
Count リクエストにマッチしたことだけを記録しておき、デフォルトルールのアクションを実行する。

Countはテスト用に使うアクションです。そのルールに該当したリクエストの回数のみを記録することで、誤って想定外のリクエストが引っかからないかを事前に確認することができます。

こうして作成したWebACLをCloudFrontまたはALBに適用することで、WAFが機能します。

WebACLの作成

まずは[Web ACLs]をクリックします。

[Configure web ACL]をクリックします。

WebACLの設定例が色々書かれた画面に遷移します。スクロールして右下の[Next]をクリックします。

WebACLのパラメータを入力します。

Web ACL name 任意の条件名を入力。
CloudWatch metric name CloudWatchのメトリクス名。Nameを入力すると自動入力されます。
Region CloudFrontならGlobal、ALBなら対象のリージョンを選択。
AWS resource to associate WebACLで保護するリソースを選択。

今回はMyWordPressAppという名前で東京リージョンにWebACLを作成します。保護対象には、事前に用意していたWordPressALBを指定しています。全て入力したら右下の[Next]をクリックします。

条件の新規作成画面に遷移しますが、既に作成済みのためここは飛ばします。スクロールして右下の[Next]をクリックします。

WebACLにルールを追加していきます。

[Rules]で先ほど作成した「ap-admin-access」を選択してから[Add rule to web ACL]をクリックします。

ap-admin-accessが追加されました。このルールに該当するリクエストは全て拒否したいため[Action]は「Block」を選択します。また、それ以外のリクエストは全て許可するため[Default Action]は「Allow all requests that don’t match any rules」を選択します。最後に[Review and create]をクリックします。

設定の確認画面に遷移します。パラメータに誤りがなければ、スクロールして右下の[Confirm and create]をクリックします。

「WebACL:MyWordPressApp」が作成されました。パラメータで指定した通り「ルール:ap-admin-access」で「ALB:WordPressALB」が保護されていることが分かります。

以上でWAFの作成は完了です。

WAFの動作確認

では、パブリックサブネットにテスト用のEC2を立ててちゃんとBlockされるか確認してみましょう。

許可されてないIPからアクセスしてみる

パブリックIPが13.230.168.75のインスタンスからALB経由で/wp-admin/にcurlすると403が返ってきました。

403は許可されていないリソースへアクセスした場合に返ってくるステータスコードです。このリクエストが何かしらの理由で遮断されたことを表していますが、これがWAFによる防御なのかどうかはこれだけでは分かりません。

サンプルリクエストを確認する

本当にWAFでBlockされたのかどうかは、サンプルリクエストで確認できます。サンプルリクエストとは、WAFのルールに該当したリクエストの情報を記録しておくリクエストログのことです。直近3時間までしか保持されないですが、接続元IPやヘッダなどリクエストの細かい情報を確認できます。

マネジメントコンソールでWebACLを開き[Requests]タブから確認できます。

少し下にスクロールすると出てくる[Sampled requests]のプルダウンメニューから「ap-admin-access」を選択して[Get new samples]をクリックします。

このように、Blockしたログが一覧表示されます。今回は1件だけでしたが、確かに13.230.168.75から/wp-admin/へのリクエストがBlockされていることを確認できました。

許可されてるIPからアクセスしてみる

許可されてるIPからアクセスできることも確認しておきます。

この通り、パブリックIPが13.115.194.191のインスタンスからALB経由で/wp-admin/にcurlすると302が返ってきました。

302はリクエストがリダイレクトされた場合に返ってくるステータスコードです。/wp-admin/は管理画面のURIですが、未ログイン状態ならまずログイン画面へリダイレクトするようWordPressは設計されています。先ほどのように403が返ってこないことから、WAFが接続元IPを判断してアクセスを許可していることがわかります。

まとめ

今回はWordPressを例にしてAWS WAFの設定方法を説明しました。

条件としてはIPとクエリストリングしか例に挙げていませんが、他の条件も大体同じように設定できるのでアプリに応じて使い分けると良いと思います。なお、真面目にWordPressを保護するのであれば、以下のようなURIもユーザにアクセスさせる必要はないので「条件:wp-admin」に追加してBlockしておいたほうがいいと思います。

上述した通り、サンプルリクエストは直近3時間までしか保持されないため、別の方法で長期保存する仕組みを作ってあげる必要があります。また、サンプルリクエストはあくまでサンプルであり全てのアクセスログを取れるわけではありません。何かとクセのある仕様ですので、次回はこの辺の詳細をもう少し詰めて説明したいと思います。

こんな記事も読まれてます

20代30代の客先常駐ITエンジニアが最速で年収を上げる方法

この記事の所要時間: 1456

この記事の所要時間: 約 14分56秒 もしあなたが以下の3つ全てに該当するエンジニアであれば、今すぐにでも転職をオススメします。 客先常駐型のエンジニアとして働いている 案件の途中で契約を切られるこ …

モバイルバージョンを終了