SSTエンジニアブログ

SSTのエンジニアによるWebセキュリティの技術を中心としたエンジニアブログです。

ScutumAPIリリース記念 第三弾 CloudFrontログとScutumの防御ログを結合してみた。

はじめに

こんにちは、研究開発の宇田川です。
過去2回と防御ログ(リスト)と防御ログ(詳細)についてAWSを使用した自動取得の方法をご紹介しました。

techblog.securesky-tech.com

techblog.securesky-tech.com

今回は、CloudFrontのログとScutumの防御ログの結合についてご紹介いたします。

経緯

キャッシュによる配信でWEBサーバーの負荷軽減や世界中にあるエッジロケーションを使用したレイテンシ低減等のメリットからScutumの前段にAWSのCDNサービスであるCloudFrontを配置する構成は多くのお客様で導入されています。

f:id:woodykedner:20201214150246p:plain:w500

CloudFrontでもログの取得が可能で標準ログ、今年の夏ぐらいにリリースされたリアルタイムログの取得が可能となっています。 docs.aws.amazon.com docs.aws.amazon.com

200レスポンスの場合、CloudFrontのログを見れば、それがキャッシュによりCloudFrontから返されたものかWEBサーバにリクエストが到達し、WEBサーバにより返されたものか確認することが可能です。
例えば、CloudFrontのログの「x-edge-response-result-type」で「Hit」ならCloudFrontのキャッシュによるレスポンス、「Miss」であればWEBサーバによるレスポンスみたいに認識することができます。

docs.aws.amazon.com

ただ、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クエリを実行できる状態にします。

f:id:woodykedner:20201215171502p:plain:w800

防御ログ(詳細)の取得の部分以外の構成については下記の通りです。

CloudFrontのログをAthenaで読むためにリネームするLambda

こちらのブログの内容をがっつり使っているので、まず目を通していただればと思います。

dev.classmethod.jp

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クローラーの設定とデータカタログを作成してみてください。

techblog.securesky-tech.com

注意点として、無いログからデータカタログは作成できないので、「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"

実行結果はこのようになります。

f:id:woodykedner:20201218111649p:plain

レスポンスコード(sc-statusのカラム)が403でscutumでブロックされた場合categoryに攻撃の分類を表示し、またbodyの内容にXSSのペイロードを入れていたので、bodyの内容も表示するようにしました。
その他、クエリーストリングならCloudFrontログのcs-uri-query、ユーザエージェントならCloudFrontログのcs(User-Agent) 、その他のヘッダーなら、防御ログ(詳細)から取得など、一通りのリクエスト、レスポンスの情報が揃っているので、必要に応じてカラムを選択し、分析に繋げていただければと思います。

おわりに

今回はCloudFrontのログとScutumの防御ログの結合についてご紹介いたしました。
前回、前々回のブログも含め、お読みいただきありがとうございました。
ScutumAPIリリース記念で書かせていただいたブログはこれでおしまいですが、書きながら次のアイデアが浮かんだので、またどこかでブログを公開します。

それでは皆様、良いお年をお迎えください。