[update] Amazon CloudFrontでJA3 fingerprint headersをサポートしました!

CloudFrontでTLS接続時のパケット内容のハッシュ値であるJA3 Fingerprintヘッダが利用可能になりました。既知のマルウェアやボットなど悪意のあるクライアントをこのヘッダを利用してブロックする、といったことが可能となります。
2022.11.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。AWS re:Invent 2022がはじまり、アップデート盛りだくさんになっていますね!そんな中ですが、re:Invent 2022前に発表されていたアップデートもしっかりおさえておこうのコーナーです。はい。

本エントリでお届けするアップデートはこちら!AWSのCDNサービスであるAmazon CloudFrontでJA3 fingerprint headersをサポートしました。(2022/11/17付でAWS What's Newにポストされたアップデート情報となります。)

JA3 Fingerprint!?

お恥ずかしながら、筆者は本アップデートをチェックするまでJA3 Fingerprintというものを把握できていませんでした。TLS(SSL/TLS)クライアントとの接続時のClient Helloパケット内容、具体的にはそのTLSバージョンや暗号方式、拡張機能リストなどをもとにMD5ハッシュ値を算出してこれをJA3 Fingerprintと呼ぶとのことです。

クライアントアプリケーションが同一であれば、User-AgentヘッダやIPアドレスの情報などに関わらず同一(同一種)の値となるようで、これを用いることで既知のマルウェアや悪意のあるボットであるかどうかの判断が可能になります。例えばこちらにはTorクライアント、Trickbotマルウェア、そしてEmotetマルウェアそれぞれのJA3 Fingerprintが公開されています。またあらかじめ接続するクライアントアプリケーションのJA3 Fingerprintがわかっていれば、そのクライアントのみを接続許可するといった利用もできるようです。

今回のCloudFrontのアップデートでは、CloudFront側でこのJA3 Fingerprintを算出、それをCloudFront-Viewer-JA3-Finfgerprintというヘッダとしてオリジンに転送可能となった、ということになります。またオリジンに転送してオリジン側の処理で使用するほか、CloudFront FunctionsやLambda@Edgeでも利用可能です。

CloudFrontでJA3 fingerprint headersを使ってみた

それでは実際にこのCloudFront-Viewer-JA3-Fingerprintヘッダについて、CloudFrontで設定を行い使ってみたいと思います。使い方はほかのCloudFront HTTPヘッダーの利用方法と同様です。Origin request policyにCloudFront-Viewer-JA3-Fingerprintヘッダを追加する必要がある点に注意しましょう。

オリジンサーバの準備

オリジンサーバとして内部でApache HTTP ServerとPHPを動作させたEC2インスタンスを準備しました。また以下のファイル(index.php)を配置しておきます。デフォルトルート(/)にアクセスすれば、アクセス時のリクエストヘッダ情報が参照できる状態としました。

index.php

<?php

foreach (getallheaders() as $name => $value) {
    echo "<p>";
    echo "$name: $value";
    echo "</p>\n";
}

?>

Origin request policyの作成

続いてCloudFront側の設定です。まずはOrigin request policyを作成していきます。CloudFrontマネジメントコンソールのPoliciesのページの「Origin request」タブから、[Create origin request policy]ボタンで進みます。NameとDescriptionを適切に設定します。

Origin request settingsの項目ではHeadersで「All viewer headers and the following CloudFront headers」を選択しました。(All viewer headersは任意ですが、CloudFront headersを選択する必要があります。)Add headerでOrigin requestに含めるCloudFront headersとして、CloudFront-Viewer-JA3-Fingerprintを選択します。

[Create]ボタンで作成します。

Distributionの作成

Origin request policyが作成できたら、このOrigin request policyを使用するDistributionを作成していきます。ポイントとなる「Cache key and origin requests」の項目のみまとめます。

「Cache key and origin requests」の項目で「Cache policy and origin request policy (recommended)」を選択します。Cache policyは今回は検証目的ということで「CachingDisabled」を選択しました。Origin request policyで先ほど作成したCloudFront-Viewer-JA3-FingerprintをCloudFront headersに含めたもの指定します。(ここでは「CloudFrontViewerJA3FingerprintHeader」。)

JA3 Fingerprintヘッダの値の確認

CloudFront Distributionが作成できたら、このドメイン名にアクセスしてみます。まずはGoogle Chromeでのアクセスです。CloudFront-Viewer-JA3-Fingerprintヘッダの値として、cd08e31494f9531f560d64c695473da9が表示されました。

プロファイルを変更しても同じ値となりました。(Chromeのテーマカラーで別プロファイルと察してください。)

ただし、タイミングによっては別のJA3 Fingerprintの値となる場合もありました。0d69ff451640d67ee8b5122752834766という値ですね。

以下にmacOSならびにLinuxでのJA3ハッシュ値がまとめられているのですが、同じGoogle Chromeブラウザ(クライアントアプリケーション)でも複数のハッシュ値を取りうるもの、と理解しています。(そして今回得られたJA3 Fingerprintがこのリスト中にありませんが、OSやブラウザバージョン等によっても変わる可能性があるものかと考えます。)

続いてSafariブラウザでも確認してみましょう。773906b0efdefa24a7f2b8eb6985bf37という値となりました。Safariブラウザでは確認した限り、値が変わることはありませんでした。

curlコマンドでも確認してみましょう。375c6162a492dfbf2795909110ce8424という値でした。

% curl https://d370xxxxxxxxxx.cloudfront.net
<p>Host: d370xxxxxxxxxx.cloudfront.net</p>
<p>User-Agent: curl/7.79.1</p>
<p>X-Amz-Cf-Id: L4N2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxXg==</p>
<p>Connection: Keep-Alive</p>
<p>Via: 2.0 0b3axxxxxxxxxxxxxxxxxxxxxxxx6fd6.cloudfront.net (CloudFront)</p>
<p>X-Forwarded-For: 2axx:xxxx:xxxx:xx::xx:x30</p>
<p>Accept: */*</p>
<p>CloudFront-Viewer-JA3-Fingerprint: 375c6162a492dfbf2795909110ce8424</p>

興味深い点として、例えばGoogle Chromeブラウザでデベロッパーツールを使ってリクエストから「Copy all as cURL」を行います。これで得られたcurlコマンドをターミナルで実行すると、User-Agentヘッダなど可能な限りGoogle Chromeブラウザでのリクエストと同じものがcurlコマンドでリクエストできるわけですが、JA3 Fingerprintの値は異なる結果(curlコマンドでのリクエストの場合はあくまでcurlコマンドでのJA3 Fingerprint値)となりました。

% curl 'https://d370xxxxxxxxxx.cloudfront.net/' \
  -H 'authority: d370xxxxxxxxxx.cloudfront.net' \
  -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' \
  -H 'accept-language: ja,en-US;q=0.9,en;q=0.8' \
  -H 'sec-ch-ua: "Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: document' \
  -H 'sec-fetch-mode: navigate' \
  -H 'sec-fetch-site: none' \
  -H 'sec-fetch-user: ?1' \
  -H 'upgrade-insecure-requests: 1' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' \
  --compressed
<p>Host: d370xxxxxxxxxx.cloudfront.net</p>
<p>User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36</p>
<p>X-Amz-Cf-Id: Qmdwxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyw==</p>
<p>Connection: Keep-Alive</p>
<p>Via: 2.0 d1faxxxxxxxxxxxxxxxxxxxxxxxx31ba.cloudfront.net (CloudFront)</p>
<p>X-Forwarded-For: 2axx:xxxx:xxxx:xx::xx:x30</p>
<p>Accept-Language: ja,en-US;q=0.9,en;q=0.8</p>
<p>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9</p>
<p>Accept-Encoding: deflate, gzip</p>
<p>authority: d370xxxxxxxxxx.cloudfront.net</p>
<p>sec-ch-ua: "Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"</p>
<p>sec-ch-ua-mobile: ?0</p>
<p>sec-ch-ua-platform: "macOS"</p>
<p>sec-fetch-dest: document</p>
<p>sec-fetch-mode: navigate</p>
<p>sec-fetch-site: none</p>
<p>sec-fetch-user: ?1</p>
<p>upgrade-insecure-requests: 1</p>
<p>CloudFront-Viewer-JA3-Fingerprint: 375c6162a492dfbf2795909110ce8424</p>

TLSを用いないHTTPリクエストではCloudFront-Viewer-JA3-Fingerprintヘッダは転送されない

CloudFront DistributionにCloudFront-Viewer-JA3-Fingerprintヘッダをリクエストに含めるよう設定したOrigin request policyを設定し、実際にオリジン側で転送されたCloudFront-Viewer-JA3-Fingerprintヘッダを確認してみました。ところで、JA3 FingerprintについてはTLSクライアントとの接続時のパケット内容をハッシュ値としたもの、ということでした。それではTLS(SSL/TLS)を用いない、HTTPSではないHTTPでの通信の場合、CloudFront-Viewer-JA3-Fingerprintヘッダはどうなるでしょうか。答えはCloudFrontからヘッダとして転送されない、となります。(Developer Guideにも、この旨記載がありますね。)

% curl http://d370xxxxxxxxxx.cloudfront.net
<p>Host: d370xxxxxxxxxx.cloudfront.net</p>
<p>User-Agent: curl/7.79.1</p>
<p>X-Amz-Cf-Id: 6p02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxfg==</p>
<p>Connection: Keep-Alive</p>
<p>Via: 1.1 45e3xxxxxxxxxxxxxxxxxxxxxxxx8d0a.cloudfront.net (CloudFront)</p>
<p>X-Forwarded-For: 2axx:xxxx:xxxx:xx::xx:x30</p>
<p>Accept: */*</p>

まとめ

Amazon CloudFrontで新たにサポートしたJA3 Fingerprintヘッダについて、実際にCloudFrontに設定しつつ、JA3 Fingerprintそのものも含めて確認してみました。今回は実際にマルウェアやボットのような悪意のあるリクエストがどのようなJA3 Fingerprintとなるか、といった確認はできませんでしたが、公開されている情報などをもとにしてこれらのJA3 Fingerprintハッシュ値を持つリクエストをブロックする、といったことが可能となります。本来想定している正常なリクエストと、異常な(悪意を持っていると思われる)リクエスト、それぞれの傾向を分析すると行った場合にも使用できそうですね。使用目的をきちんと理解し、有効に活用していきたいと思いました。