[アップデート]AWS WAFがJA4 フィンガープリントをサポートしました

[アップデート]AWS WAFがJA4 フィンガープリントをサポートしました

Clock Icon2025.03.25

初めに

今月の上旬の話ですがAWS WAFがJA4フィンガープリントに対応しました。

https://aws.amazon.com/about-aws/whats-new/2025/03/aws-waf-ja4-fingerprinting-aggregation-ja3-ja4-fingerprints-rate-based-rules/

これまでもJA3フィンガープリントを用いることで自己複製マルウェア等同一クライアントを識別しこれにより特定の脅威からコンテンツを保護可能でしたが、JA3フィンガープリントはハッシュ値の関係で識別子以上の意味を持たず分析への活用は難しい特性がありました。

今回サポートされるようになったJA4フィンガープリントではハッシュ値のみではなく一部意味のある値が含まれるため、以降TLSレイヤーでの識別子を単なるブロックのみではなく分析に活用することが可能となります。

ベースとなるJA3の詳細はここでは割愛しますが以前対応時にで触れているためこちらもご参照ください。

https://dev.classmethod.jp/articles/aws-waf-support-ja3-fingerprint/

また、今回のアップデートでJA4と合わせてJA3もレートベースルールでも利用できるようになったようです。

JA4とは

JA4はFoxIO社により開発されたJA3の後継となる手法となります。算出される値は異なるものの基本的な概念はJA3から変わりありません。

厳密にはJA4はJA4+(JA4ファミリー)と呼ばれるメソッド群の一部となり、JA4は引き続きJA3同様Client Helloに基づくフィンガープリント(TLSクライアントフィンガープリント)を受け持ちます。

https://github.com/FoxIO-LLC/ja4

今回のAWS WAFのJA4対応もあくまでTLSフィンガープリントであり、本記事執筆時点(2025/03/25)ではその他のJA4ファミリーには未対応なためご注意ください。

https://github.com/salesforce/ja3
JA3 was invented at Salesforce in 2017. However, the project is no longer being actively maintained by Salesforce. Its original creator, John Althouse, maintains the latest in TLS client fingerprinting technology at FoxIO-LLC.

なおJA3はSalesforceのOrg配下、JA4はFoxIOのOrg配下と異なっていますが、JA3側のリポジトリにリンク付きで元々の作成者がこっちで新しいの作ってる旨の記載がリンク付きであったのでJA3提供元公認の正式な後継バージョン扱いとなりそうです。

JA4フィンガープリントは以下に記載のあるような構成となっており、利用プロトコルやTLSバージョン等を特定のフォーマットで記載するブロック(ここは人が識別可能)、Client Helloで送信される暗号スイート群のハッシュ値ブロック、TLS拡張等の情報を含むブロックの3つから構成されます。

https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md

構成としての大きな違いとしては先頭ブロックに意味のある値が含まれたこととなり、これにより脅威パターンの分析や予期される危険パターンからの事前保護等を計画することが可能となります。

A “00” here denotes the lack of ALPN. Note that the presence of ALPN “h2” does not indicate a browser as many IoT devices communicate over HTTP/2. However, the lack of an ALPN may indicate that the client is not a web browser.

またJA4はTLSレイヤーからのフィンガープリントではあるものの、TCP or QUICどちらで来ているかのフラグ、TLS拡張に含まれるALPNも抽出しているため、これ単品でも古いクライアントが多い(古いHTTPバージョン使ってる)等結構色々追えそうな気がします(他のログ項目から追える部分もありますが)。

またハッシュ値部分も暗号スイート・TLS拡張情報の2ブロックに分離されたのみではなく値をソートした後にハッシュ計算が行われるようになった為、これまで対応が難しかった一部パターンにも対応ができるようになりました。

具体的にはGoogle Chromeが持つClient Helloの一部値の順序がランダムに変更される仕様にも対応できるようになり、同一クライアントであれば一貫して同じ値を得ることができます。

https://dev.classmethod.jp/articles/aws-waf-ja3-with-issue/

https://chromestatus.com/feature/5124606246518784
Using a fixed extension order can encourage server implementers to fingerprint Chrome and then assume specific implementation behavior. This can limit ecosystem agility when Chrome implements future modifications to TLS, if the server implementations are not prepared for Chrome to change its ClientHello.

ただこのランダム化に関してはフィンガープリントによる特定挙動の強制を回避する意図?で変更した旨の記載もあったため将来的に対策される可能性もある点注意すべき点ではありそうです。

実際にJA4フィンガープリントを確認してみる。

JA4フィンガープリントのログ出力は特別設定は不要となっておりデフォルトでja4Fingerprint項目が追加されこちらに出力されます。。

{
    "timestamp": 1742895942259,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:us-east-1:xxxxxx:global/webacl/test-waf/xxxxxx",
    "terminatingRuleId": "Default_Action",
    "terminatingRuleType": "REGULAR",
    "action": "ALLOW",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "CF",
    "httpSourceId": "xxxxxx",
    "ruleGroupList": [],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": null,
    "httpRequest": {
        ...
        "headers": [
            {
                "name": "host",
                "value": "xxxxxx.cloudfront.net"
            },
            {
                "name": "user-agent",
                "value": "curl/8.1.2"
            },
            {
                "name": "accept",
                "value": "*/*"
            }
        ],
    },
    "ja3Fingerprint": "375c6162a492dfbf2795909110ce8424",
    "ja4Fingerprint": "t13d4907h2_0d8feac7bc37_7395dae3b2f3"
}

値としてはt13d4907h2_0d8feac7bc37_7395dae3b2f3となりますので、
「TCP」「TLSv1.3」「IP直ではなくドメイン経由でのアクセス」「利用可能な暗号スイート49種」「TLS拡張7種」「HTTP/2」
という情報が得られます。また今回は特定していないのですがハッシュ部分も公開されているパターン一覧があればクライアントをこれだけでも具体的に特定できそうです。

実際にClient Helloパケットを確認したところ想定値通りでしたのでJA4の構成の理解としては誤りはなさそうです。

client-hello-curl

TLSレイヤー直下でTLSv1.3と表示されてるのに赤枠の5行くらい上にVersion: TLS 1.2という記載があって本当にあってる???と一瞬不安になりましたが後方互換のための値でTLSv1.3でもTLSv1.2を示す値が固定ではいるためこれが仕様のようです。

https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2
struct {
ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
Random random;
opaque legacy_session_id<0..32>;
CipherSuite cipher_suites<2..2^16-2>;
opaque legacy_compression_methods<1..2^8-1>;
Extension extensions<8..2^16-1>;
} ClientHello;

legacy_version: In previous versions of TLS, this field was used for
version negotiation and represented the highest version number
supported by the client. Experience has shown that many servers
do not properly implement version negotiation, leading to "version
intolerance" in which the server rejects an otherwise acceptable
ClientHello with a version number higher than it supports. In
TLS 1.3, the client indicates its version preferences in the
"supported_versions" extension (Section 4.2.1) and the
legacy_version field MUST be set to 0x0303, which is the version
number for TLS 1.2. TLS 1.3 ClientHellos are identified as having
a legacy_version of 0x0303 and a supported_versions extension
present with 0x0304 as the highest version indicated therein.
(See Appendix D for details about backward compatibility.)

JA4フィンガープリントを指定したルール

JA4フィンガープリントはアップデート情報に記載の通り通常ルール、レートベースルールいずれでも利用可能となっております。

waf-ja4-match

waf-ja4-rate

ただ現時点の仕様として通常ルールの一致条件はExactly matches string(完全一致)固定のため一部分を使った怪しげなアクセスの検出というのはまだできなさそうです。

現時点でCloudFrontはTLSv1.3のみ許可、TLSv1.2を無効化というのは無理なものの、これを使ったらTLSv1.3だけ許可ってできるのかなぁと考えたので一瞬考えたので残念です(普通そんな用途で使わないと思いますが)。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html

一応先ほどのJA4フィンガープリントに一致した場合BLOCKするように設定してみます。

% curl https://xxx.cloudfront.net --verbose
*   Trying xxx.xxx.xxx.xxx:443...
* Connected to xxx.cloudfront.net (xxx.xxx.xxx.xxx) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=*.cloudfront.net
*  start date: Jul 30 00:00:00 2024 GMT
*  expire date: Jul  3 23:59:59 2025 GMT
*  subjectAltName: host "xxx.cloudfront.net" matched cert's "*.cloudfront.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
*  SSL certificate verify ok.
* using HTTP/2
* h2 [:method: GET]
* h2 [:scheme: https]
* h2 [:authority: xxx.cloudfront.net]
* h2 [:path: /]
* h2 [user-agent: curl/8.1.2]
* h2 [accept: */*]
* Using Stream ID: 1 (easy handle 0x11f011400)
> GET / HTTP/2
> Host: xxx.cloudfront.net
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/2 403
< server: CloudFront
< date: Tue, 25 Mar 2025 10:31:11 GMT
< content-type: text/html
< content-length: 919
< x-cache: Error from cloudfront
< via: 1.1 xxxxxxx.cloudfront.net (CloudFront)
< x-amz-cf-pop: KIX56-C1
< x-amz-cf-id: xxxxxx==
<
<!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: xxxxxxxx==
</PRE>
<ADDRESS>
</ADDRESS>
* Connection #0 to host xxx.cloudfront.net left intact
</BODY></HTML>

TLSv1.2に落として通信することでTLSのバージョンだけではなく利用可能な暗号スイート等々が変動されるため、同じバージョンのcurlクライアントでもこちらではアクセスすることができます(404は別起因です...)。

curl https://xxx.cloudfront.net --tls-max 1.2 --verbose
*   Trying xxx.xxx.xxx.xxx:443...
* Connected to xxx.cloudfront.net (xxx.xxx.xxx.xxx) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=*.cloudfront.net
*  start date: Jul 30 00:00:00 2024 GMT
*  expire date: Jul  3 23:59:59 2025 GMT
*  subjectAltName: host "xxx.cloudfront.net" matched cert's "*.cloudfront.net"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
*  SSL certificate verify ok.
* using HTTP/2
* h2 [:method: GET]
* h2 [:scheme: https]
* h2 [:authority: xxx.cloudfront.net]
* h2 [:path: /]
* h2 [user-agent: curl/8.1.2]
* h2 [accept: */*]
* Using Stream ID: 1 (easy handle 0x12c013400)
> GET / HTTP/2
> Host: xxx.cloudfront.net
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/2 404
< content-type: application/xml
< server: AmazonS3
< date: Tue, 25 Mar 2025 10:33:37 GMT
< x-cache: Error from cloudfront
< via: 1.1 xxxxxx.cloudfront.net (CloudFront)
< x-amz-cf-pop: KIX56-C1
< x-amz-cf-id: xxxxxx
<
<?xml version="1.0" encoding="UTF-8"?>
* Connection #0 to host xxx.cloudfront.net left intact
<Error><Code>NotFound</Code><Message>The resource you requested does not exist</Message><RequestId>xxxxxx</RequestId><HostId>xxxxxx</HostId></Error>

終わりに

WAFのJA4対応の確認を兼ねてJA4がどのようなものか確認してみました。

脅威からの保護という意味でJA3に比べてカバーできる範囲が広くなったというのもありますが、JA4では意味のある値がある程度まとまって付与されることになったことでうまく使えば脅威に対しての分析ができるようになったのが大きいのではないかなと思います。
部分一致ができればもう少し色々活用できそうなのでそこは今後に期待かなと思います。

なお今回は触れなかったのですが2024/10頃にCloudFrontが先行してJA4に対応しています。

https://aws.amazon.com/about-aws/whats-new/2024/10/amazon-cloudfront-ja4-fingerprinting/

CloudFront側の場合JA4をCloudFront FunctionsやLambda@Edgeでこねくり回せるので、色々制御したい場合はこちらの方がよいかもしれません(現時点)。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.