はじめに
こんにちは、研究開発の宇田川です。
過去2回と防御ログ(リスト)と防御ログ(詳細)についてAWSを使用した自動取得の方法をご紹介しました。
今回は、CloudFrontのログとScutumの防御ログの結合についてご紹介いたします。
経緯
キャッシュによる配信でWEBサーバーの負荷軽減や世界中にあるエッジロケーションを使用したレイテンシ低減等のメリットからScutumの前段にAWSのCDNサービスであるCloudFrontを配置する構成は多くのお客様で導入されています。
CloudFrontでもログの取得が可能で標準ログ、今年の夏ぐらいにリリースされたリアルタイムログの取得が可能となっています。 docs.aws.amazon.com docs.aws.amazon.com
200レスポンスの場合、CloudFrontのログを見れば、それがキャッシュによりCloudFrontから返されたものかWEBサーバにリクエストが到達し、WEBサーバにより返されたものか確認することが可能です。
例えば、CloudFrontのログの「x-edge-response-result-type」で「Hit」ならCloudFrontのキャッシュによるレスポンス、「Miss」であればWEBサーバによるレスポンスみたいに認識することができます。
ただ、403レスポンスの場合、CloudFrontのログではScutumによってブロックされたのかWEBサーバにより返されたのか判断できません。
明確にするにはCloudFrontのログで403レスポンスのデータを確認した後、Scutumの管理画面かScutumAPIから取得した防御ログで日時やパスで照らし合わせて確認する方法が考えられます。防御ログが無ければ、WEBサーバによるレスポンスとなるため、WEBサーバのログを確認するといった流れになります。ちょっと手間です。
そこで、CloudFrontのログとScutumの防御ログを結合して403レスポンスがどこから返られたのか分かるようにできないか考えてみました。
結びつけるもの
CloudFrontのログを見直して、使えそうな項目を見つけました。
それがx-edge-request-idです。カラムで言うと15番目の値です。
ドキュメントには下記のような説明があります。
x-edge-request-id リクエストを一意に識別する不透明な文字列。 CloudFront では、この文字列を x-amz-cf-id レスポンスヘッダーでも送信します。
標準ログ (アクセスログ) の設定および使用 - Amazon CloudFront
ドキュメントを読みレスポンスヘッダーのみか…とがっかりしてしまいました。
諦めていたのですが、前回の防御ログ(詳細)のログ取得テストでは、Scutumの前段にCloudFrontを入れた状態で行っておりまして、その時のログの内容がこちら
{ "log_id": "0000888574723_240_00000", "ip": "X.X.X.X", "block": true, "category": [ "クロスサイトスクリプティング攻撃" ], "uri": "/", "ts": "2020-12-02T00:00:00+09:00", "httpMethod": "POST", "Host": "www.example.jp", "User-Agent": "curl/7.58.0", ↓↓↓↓↓↓↓↓ これ ↓↓↓↓↓↓↓↓ "X-Amz-Cf-Id": "xxxxg2SCY837b0_teksu-Lvd7sxSherAVV_VWRSYWhxxxx_dqEZW_A==", ↑↑↑↑↑↑↑↑ これ ↑↑↑↑↑↑↑↑↑ "Connection": "Keep-Alive", "Content-Length": "31", "Via": "1.1 xxxxxxxxxxxxxxxxxxxcxxxxxxxxxxxx.cloudfront.net (CloudFront)", "X-Forwarded-For": "X.X.X.X", "Accept": "*/*", "Content-Type": "application/x-www-form-urlencoded", "CloudFront-Forwarded-Proto": "https", "X-Client-Host": "Y.Y.Y.Y", "X-Forwarded-For-Orig": "X.X.X.X", "X-Forwarded-For2": "X.X.X.X", "X-Client-Port": "41890", "body": "<script>alert(\"TEST\");</script>" }
ScutumAPIリリース記念 第二弾 詳細な防御ログをAWSで自動取得してみた。 - SSTエンジニアブログ
"X-Amz-Cf-Id"がある!!!(文字の頭が大文字になっているけど)
CloudFrontを通過するとそれより後段のリクエストには、「X-Amz-Cf-Id」というヘッダー名でリクエストIDが付与されるみたいです。
もちろんCloudFrontの後段がScutumの場合、前回紹介した内容で防御ログ(詳細)を取得することで「X-Amz-Cf-Id」というヘッダーを取得することも可能なります。
まとめるとCloudFrontのログにある「x-edge-request-id」とScutumの防御ログ(詳細)にある「X-Amz-Cf-Id」で結合することによりバラバラだったCloudFrontとScutum詳細ログが一つのデータとして扱うことができます。
結合してみる
それではCloudFrontのログとScutumの防御ログを結合といきたいところですが、AWS上の構成で前回のブログから追加点がありますので触れておきます。
構成
構成は、防御ログ(詳細)の取得の部分は前回のブログの内容と同じです。
CloudFrontのログは標準ログで取得し、S3に保存します。CloudFrontのログをAthenaで分析しやすいようにLambdaでリネームした後、Scutumの防御ログ(詳細)、CloudFrontのログをGlueのクローラーによりデータカタログを作成し、AthenaでSQLクエリを実行できる状態にします。
防御ログ(詳細)の取得の部分以外の構成については下記の通りです。
CloudFrontのログをAthenaで読むためにリネームするLambda
こちらのブログの内容をがっつり使っているので、まず目を通していただればと思います。
CloudFrontのログは年月日等のパスで分けられておらず、特定のフォルダに溜まっていってしまいます。
この仕様だと何が困るかというと、Athenaでクエリーを実行するたびにすべてのファイルを読み込むことになります。ログが少ないうちはいいですが、溜まって来ると、処理時間もお金も相当なコストがかかります。
コストがかからないように、Athenaはパーテーションという機能があり、特定のパス、範囲毎にクエリーを実行することができます。
パーテーションを利用するために1時間に1回、前の1時間のCloudFrontのログを「年/月/日/ログファイル」という形で別のCloudFrontのログを保存するS3バケットに保存し直します。
CloudFrontログ保存用S3バケット
上記の利用のため、CloudFrontログ保存用S3バケットを2つ作成します。
1つ目をCloudFrontに設定するログ出力用S3バケットとして「<任意の文字列>-cloudfrontlogs-orginal-bucket」、2つ目をCloudFrontリネームログ出力用S3バケットとして「<任意の文字列>-cloudfrontlogs-custom-bucket」みたいに作成してください。
作成はドキュメントに沿って、S3バケットを作成してみてください。
docs.aws.amazon.com
CloudFront
前提として、Scutum及びWEBサーバは設定済みとします。
その上で、CloudFrontの構築の内容を載せておきます。
ほぼデフォルト設定となります。FQDNもCloudFrontのデフォルトドメイン(~.clodufornt.net)を使用します。
あくまでテスト目的での設定のため、本番環境で使用する際は、しっかり設計して構築してください。
AWSのマネージドコンソールのCloudFrontのページを開きます。
ページにある「Create Distribution」をクリック。
そしてWeb項目の「Get Started」をクリック。(RTMPは動画配信用のため、間違えないようにしましょう。)
下記が設定内容です。結構な設定数のため表にしました。
Origin Settings
設定項目 | 設定値 | 備考 |
---|---|---|
Origin Domain Name | ScutumのFQDN | 例 www.example.jp.scutum.jp |
Origin Path | 空 | |
Enable Origin Shield | No | |
Origin ID | Origin Domain Nameに入力すると勝手に「Custom- |
|
Minimum Origin SSL Protocol | TLSv1.2 | |
Origin Protocol Policy | Match Viewer | |
Origin Connection Attempts | 3 | |
Origin Connection Timeout | 10 | |
Origin Response Timeout | 30 | |
Origin Keep-alive Timeout | 5 | |
HTTP Port | 80 | |
HTTPS Port | 443 | |
Origin Custom Headers | 空 |
Default Cache Behavior Settings
設定項目 | 設定値 | 備考 |
---|---|---|
Viewer Protocol Policy | HTTP and HTTPS | |
Allowed HTTP Methods | GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE | |
Field-level Encryption Config | 空 | |
Cached HTTP Methods | 「GET, HEAD (Cached by default) 「Option」にチェック無し。 |
|
Cache and origin request settings | Use legacy cache settingsにチェック | |
Cache Based on Selected Request Headers | WhiteList | |
Whitelist Headers | Authorization | CloudFrontはデフォルト設定だと削除してしまうヘッダーがあるので注意。デフォルトでは削除されるヘッダーでAuthorizationはオリジン(Scutum)にクライアントから送られてきた値をそのまま転送します。 Authorizationを入れたのは、テストで使用したWEBサーバにBASIC認証を設定していたので追加しました。詳細はドキュメント を参照 |
Smooth Streaming | No | |
Restrict Viewer Access(Use Signed URLs or Signed Cookies) | No | |
Compress Objects Automatically | No | |
Lambda Function Associations | 空 | |
Enable Real-time Logs | No | リアルタイムログの設定はしません。下記のDistribution Settingsで標準ログを出力するS3バケットの設定をします。 |
Distribution Settings
設定項目 | 設定値 | 備考 |
---|---|---|
Price Class | Use All Edge Locations (Best Performance) | |
AWS WAF Web ACL | None | |
Alternate Domain Names(CNAMEs) | 空 | ~.cloudfront.netで接続テストを行います。 |
SSL Certificate | Default CloudFront Certificate (*.cloudfront.net) | ~.cloudfront.netで接続テストを行います。 |
Supported HTTP Versions | HTTP/2, HTTP/1.1, HTTP/1.0 | |
Default Root Object | 空 | |
Standard Logging | On | 標準ログを出力のためここをOnにします。 |
S3 Bucket for Logs | 上記で作成した1つ目をCloudFrontに設定するログ出力用S3バケット名を入力 | |
Log Prefix | 空 | |
Cookie Logging | Off | 検知、ブロックしたCookieの値は防御ログ(詳細)で取得可能 |
Enable IPv6 | チェックを外す | IPv6でのテストはしないため。 |
Comment | 空 | |
Distribution State | Enabled |
Create Distributionをクリック。
5分ぐらい待ってStateが「Enabled」になれば構築完了です。
Glue クローラー
ここは以前書いたブログの設定がそのままなので、上記で作成した「CloudFrontリネームログ保存用S3バケット」、前回のブログで作成した「防御ログ(詳細)保存用S3」を対象として、Glueクローラーの設定とデータカタログを作成してみてください。
注意点として、無いログからデータカタログは作成できないので、「CloudFrontリネームログ保存用S3バケット」、「防御ログ(詳細)保存用S3」はログが保存されている状態でGlueクローラーの設定とデータカタログを行ってください。
正常なリクエストとブロックされるリクエストのログで使いたかったので、こんなコマンドでリクエストを投げてみました。
for i in `seq 1 10` do # 正常リクエスト curl -X GET -u https://xxxxxx.cloudfront.net/ > /dev/null # XSSでブロックされるリクエスト curl -X POST -d '<script>alert("SSTTEST");</script>' https://xxxxxx.cloudfront.net/ > /dev/null done
結合
では、実際にクエリを投げて、結合されたログを見ていきましょう。
AWS マネージメントコンソールの「Athena」の画面を開いてください。
全表示クエリ
すべての要素を取得するクエリは下記の通りとなります。
cloudfrontのログをGlueのクローラーでデータカタログを作成すると、カラムがcol0~col32という名前になっていたので、それぞれの値の意味にリネームしています。
with cloudfrontlogs as ( SELECT col0 as "date", col1 as "time", col2 as "x-edge-location", col3 as "sc-bytes", col4 as "c-ip", col5 as "cs-method", col6 as "cs-host", col7 as "cs-uri-stem", col8 as "sc-status", col9 as "cs-referer", col10 as "cs-user-agent", col11 as "cs-uri-query", col12 as "cs-cookie", col13 as "x-edge-result-type", col14 as "x-edge-request-id", col15 as "x-host-header", col16 as "cs-protocol", col17 as "cs-bytes", col18 as "time-taken", col19 as "x-forwarded-for", col20 as "ssl-protocol", col21 as "ssl-cipher", col22 as "x-edge-response-result-type", col23 as "cs-protocol-version", col24 as "fle-status", col25 as "fle-encrypted-fields", col26 as "c-port", col27 as "time-to-first-byte", col28 as "x-edge-detailed-result-type", col29 as "sc-content-type", col30 as "sc-content-len", col31 as "sc-range-start", col32 as "sc-range-end" FROM "<DB名>"."<CloudFrontログテーブル名>" ), scutumblocklogs as ( SELECT * FROM "<DB名>"."<Scutum防御ログ(詳細)テーブル名>" ) select scutumblocklogs.*, cloudfrontlogs.* FROM cloudfrontlogs LEFT OUTER JOIN scutumblocklogs ON cloudfrontlogs."x-edge-request-id" = scutumblocklogs."x-amz-cf-id"
"<DB名>"、"<CloudFrontログテーブル名>"、"<Scutum防御ログ(詳細)テーブル名>"は適宜設定を変更してください。
ログ要素
取得するログの要素は下記の通りとなります。
要素名 | 説明 | 取得ログ |
---|---|---|
date | イベントが発生した日付。YYYY-MM-DD 形式 | CloudFrontログ |
time | CloudFront サーバーがリクエストへの対応を完了した時刻 (UTC) | CloudFrontログ |
x-edge-location | リクエストを処理したエッジロケーション。 | CloudFrontログ |
sc-bytes | サーバーがリクエストに応じてビューワーに送信したデータ (ヘッダーを含む) のバイトの合計数 | CloudFrontログ |
c-ip | リクエスト元のビューワーの IP アドレス | CloudFrontログ |
cs-method | ビューワーから受信した HTTP リクエストメソッド | CloudFrontログ |
cs(Host) | CloudFront ディストリビューションのドメイン名 (d111111abcdef8.cloudfront.net など)。 | CloudFrontログ |
cs-uri-stem | パスとオブジェクトを識別するリクエスト URL の部分 (/images/cat.jpg など) | CloudFrontログ |
sc-status | サーバーのレスポンスの HTTP ステータスコード (例: 200) | CloudFrontログ |
cs(Referer) | リクエスト内の Referer ヘッダーの値 | CloudFrontログ |
cs(User-Agent) | リクエスト内の User-Agent ヘッダーの値 | CloudFrontログ |
cs-uri-query | リクエスト URL のクエリ文字列の部分 | CloudFrontログ |
cs(Cookie) | リクエスト内の Cookie ヘッダー | CloudFrontログ |
x-edge-result-type | サーバーが、最後のバイトを渡した後で、レスポンスを分類した方法。 | CloudFrontログ |
x-edge-request-id | CloudFront リクエストID | CloudFrontログ |
x-host-header | ビューワーが、このリクエストの Host ヘッダーに追加した値 | CloudFrontログ |
cs-protocol | ビューワーリクエストのプロトコル (http、https、ws、wss のいずれか)。 | CloudFrontログ |
cs-bytes | ビューワーがリクエストに含めたデータ (ヘッダーを含む) のバイトの合計数 | CloudFrontログ |
time-taken | サーバーが、ビューワーのリクエストを受信してからレスポンスの最後のバイトを出力キューに書き込むまでの秒数 | CloudFrontログ |
x-forwarded-for | リクエスト内の x-forwarded-for ヘッダー。 | CloudFrontログ |
ssl-protocol | SSL/TLS プロトコル | CloudFrontログ |
ssl-cipher | SSL/TLS 暗号 | CloudFrontログ |
x-edge-response-result-type | ビューワーにレスポンスを返す直前にサーバーがレスポンスを分類した方法 | CloudFrontログ |
cs-protocol-version | ビューワーがリクエストで指定した HTTP バージョン | CloudFrontログ |
fle-status | リクエストボディが正常に処理されたかどうかを示すコード | CloudFrontログ |
fle-encrypted-fields | サーバーが暗号化してオリジンに転送したフィールドレベル暗号化フィールドの数 | CloudFrontログ |
c-port | 閲覧者からのリクエストのポート番号。 | CloudFrontログ |
time-to-first-byte | サーバー上で測定される、要求を受信してから応答の最初のバイトを書き込むまでの秒数。 | CloudFrontログ |
x-edge-detailed-result-type | x-edge-result-type フィールドが Error の場合、特定のタイプのエラー | CloudFrontログ |
sc-content-type | レスポンスの HTTP Content-Type ヘッダーの値。 | CloudFrontログ |
sc-content-len | レスポンスの HTTP Content-Length ヘッダーの値。 | CloudFrontログ |
sc-range-start | レスポンスに HTTP Content-Range ヘッダーが含まれている場合、このフィールドには範囲の開始値 | CloudFrontログ |
sc-range-end | レスポンスに HTTP Content-Range ヘッダーが含まれている場合、このフィールドには範囲の終了値 | CloudFrontログ |
log_id | 防御ログID | 防御ログ(詳細) |
ip | 送信元のIPアドレス | 防御ログ(詳細) |
block | ブロックの有無 | 防御ログ(詳細) |
category | 検知した攻撃分類、複数の攻撃分類を検知する場合あり | 防御ログ(詳細) |
uri | リクエストURI | 防御ログ(詳細) |
ts | Scutumが通信を検知・ブロックした日時 | 防御ログ(詳細) |
httpMethod | メソッド | 防御ログ(詳細) |
Host | Hostヘッダー(FQDN名) | 防御ログ(詳細) |
User-Agent | ユーザエージェントヘッダー | 防御ログ(詳細) |
X-Amz-Cf-Id | CloudFront リクエストID | 防御ログ(詳細) |
… | その他のヘッダー | 防御ログ(詳細) |
body | Bodyのデータ | 防御ログ(詳細) |
response | Base64エンコードされたHTTPレスポンスのデータ | 防御ログ(詳細) |
クエリ実行テスト
上記のクエリを実行して、ここに表示させたかったのですが…
要素数が多すぎて、入りきらなくなってしまったので、間引いて表示させてみました。
with cloudfrontlogs as ( SELECT col0 as "date", col1 as "time", col2 as "x-edge-location", col3 as "sc-bytes", col4 as "c-ip", col5 as "cs-method", col6 as "cs-host", col7 as "cs-uri-stem", col8 as "sc-status", col9 as "cs-referer", col10 as "cs-user-agent", col11 as "cs-uri-query", col12 as "cs-cookie", col13 as "x-edge-result-type", col14 as "x-edge-request-id", col15 as "x-host-header", col16 as "cs-protocol", col17 as "cs-bytes", col18 as "time-taken", col19 as "x-forwarded-for", col20 as "ssl-protocol", col21 as "ssl-cipher", col22 as "x-edge-response-result-type", col23 as "cs-protocol-version", col24 as "fle-status", col25 as "fle-encrypted-fields", col26 as "c-port", col27 as "time-to-first-byte", col28 as "x-edge-detailed-result-type", col29 as "sc-content-type", col30 as "sc-content-len", col31 as "sc-range-start", col32 as "sc-range-end" FROM "<DB名>"."<CloudFrontログテーブル名>" ), scutumblocklogs as ( SELECT * FROM "<DB名>"."<Scutum防御ログ(詳細)テーブル名>" ) select cloudfrontlogs."date",cloudfrontlogs."time",cloudfrontlogs."c-ip",cloudfrontlogs."cs-host",cloudfrontlogs."cs-method", cloudfrontlogs."cs-uri-stem",cloudfrontlogs."cs-user-agent",cloudfrontlogs."x-edge-result-type",cloudfrontlogs."sc-status", scutumblocklogs."category",scutumblocklogs."body" FROM cloudfrontlogs LEFT OUTER JOIN scutumblocklogs ON cloudfrontlogs."x-edge-request-id" = scutumblocklogs."x-amz-cf-id"
実行結果はこのようになります。
レスポンスコード(sc-statusのカラム)が403でscutumでブロックされた場合categoryに攻撃の分類を表示し、またbodyの内容にXSSのペイロードを入れていたので、bodyの内容も表示するようにしました。
その他、クエリーストリングならCloudFrontログのcs-uri-query、ユーザエージェントならCloudFrontログのcs(User-Agent) 、その他のヘッダーなら、防御ログ(詳細)から取得など、一通りのリクエスト、レスポンスの情報が揃っているので、必要に応じてカラムを選択し、分析に繋げていただければと思います。
おわりに
今回はCloudFrontのログとScutumの防御ログの結合についてご紹介いたしました。
前回、前々回のブログも含め、お読みいただきありがとうございました。
ScutumAPIリリース記念で書かせていただいたブログはこれでおしまいですが、書きながら次のアイデアが浮かんだので、またどこかでブログを公開します。
それでは皆様、良いお年をお迎えください。