AWS WAFのChallengeアクションの動作を見てみる

2023.06.07

初めに

先日AWS WAFのCAPTCHAの機能を従来通りの動作と合わせどのような動作をするか確認してみました。

その際に設定項目としてChallengeの機能が気になって調べたものの、機能が比較的新しいせいか(2022年10月頃)自分の調べかたの問題か具体的にどのようなものかいまいち情報が出てきませんでした。

説明を読んでも具体的な流れが何となくのでのイメージとなったため動作を確認してみます。

Challengeアクション概要

https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/waf-captcha-and-challenge.html
Challenge は、サイレントチャレンジを実行して、クライアントセッションがボットではなく、ブラウザであることを検証する必要があります。検証は、エンドユーザーの関与なしでバックグラウンドで実行されます。これは、CAPTCHA パズルでエンドユーザーのエクスペリエンスに悪影響を与えることなく、無効だと思われるクライアントを検証する場合に適したオプションです。

ドキュメント上には上記のような記載がされており、Challengeアクションが指定されている場合何らかの方法でブラウザであるかどうか区別するためのアクションのようです。

またその仕組みとしてははCAPTCHAアクションと異なりユーザ側に操作を求めるものではなく自動的に行われるため、ユーザ側に負荷をかけないというのが1つのポイントになります。

https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/waf-application-integration.html
インテリジェントな脅威に対応した統合 API は、AWS WAF サイレントブラウザのチャレンジを使用しているため、クライアントで有効なトークンを取得した後にのみ、保護されたリソースに対するログイン試行やその他の呼び出しが許可されるようになっています。API は、クライアントアプリケーションセッションのトークン認証を管理し、クライアントに関する情報を収集して、ボットによる操作か、人間による操作かを判断します。

仕組み自体は同ページの記載によるとインテリジェントな脅威と類似する機能らしく、参照してみると何らかのトークンを識別に利用しそれを持ってアクセスを許可しているようです。

実際のアクセスを見てみる

/index.htmlへのアクセスをすべてChallengeアクションを行うように設定しています。

index.htmlの中身は以下のとおりです。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
<body>
    <div>
        <span>Hello World!</span>
    </div>
</body>
</html>

こちらにCookieやブラウザキャッシュがない状態でアクセスすると以下のようなアクセスが行われました。

まず最初にindex.htmlがHTTPステータスコード202で返却された後、再度HTTPステータスコード200にてindex.htmlが返却されています。

表示上は一瞬白画面が表示されたのちに本来のコンテンツが読み込まれる様に見えます。

なお後述しますが202の返却の後に一度リロードが行われているため、永続ログで取得していないとWAFがないとき同様に通常通りのアクセスのみに見えますので試そうと思っている方はご注意ください。

同様に一度HTTPステータスコード202アクセスを受け取ったのち一定の間はこの202アクセス相当のアクセスは発生せず通常通りのアクセスのみになります。

curlで直接アクセスした場合は202のアクセスのみで止まってしまいます。

$ curl http://xxxxx.cloudfront.net/index.html --verbose
*   Trying 18.65.141.16:80...
* Connected to xxxxx.cloudfront.net (18.65.141.16) port 80 (#0)
> GET /index.html HTTP/1.1
> Host: xxxxx.cloudfront.net
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 202 Accepted
< Server: CloudFront
< Date: Wed, 07 Jun 2023 04:49:42 GMT
< Content-Length: 0
< Connection: keep-alive
< x-amzn-waf-action: challenge
< Cache-Control: no-store, max-age=0
< Content-Type: text/html; charset=UTF-8
< X-Cache: Error from cloudfront
< Via: 1.1 xxxxx.cloudfront.net (CloudFront)
< X-Amz-Cf-Pop: NRT51-P1
< X-Amz-Cf-Id: xxxxx
<
* Connection #0 to host xxxxx.cloudfront.net left intact

curlのレスポンスを見るとレスポンスボディが特にないためchallenge.jsverfyはどこから読み込まれているのかとなりましたが、ブラウザの開発者モードで202となるアクセスのレスポンスボディを除くと中身が存在しないわけではなく以下の内容が含まれておりました。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Human Verification</title>
    <style>
        body {
            font-family: "Arial";
        }
    </style>
    <script type="text/javascript">
    window.awsWafCookieDomainList = [];
    </script>
    <script src="https://xxxx.ap-northeast-1.token.awswaf.com/xxxx/xxxx/xxxx/xxxx.js"></script>
</head>
<body>
    <div id="challenge-container"></div>
    <script type="text/javascript">
        AwsWafIntegration.checkForceRefresh().then((forceRefresh) => {
            if (forceRefresh) {
                AwsWafIntegration.forceRefreshToken().then(() => {
                    window.location.reload(true);
                });
            } else {
                AwsWafIntegration.getToken().then(() => {
                    window.location.reload(true);
                });
            }
        });
    </script>
    <noscript>
        <h1>JavaScript is disabled</h1>
        In order to continue, we need to verify that you're not a robot.
        This requires JavaScript. Enable JavaScript and then reload the page.
    </noscript>
</body>
</html>

スクリプトの中身は非常に長く難読化されてるので解読は難しそうですが、このchallenge.jsverfyの流れは以前CAPTCHA認証を試した時(「初めに」に記載のリンクを参照)の認証後の流れとほとんど似ているので手動の認証を1段挟むかどうかの違いはあるものの似たような処理を行っていそうです。

同じであればaws-waf-tokenが含まれているかと思い確認してみたところ、アクセス前にCookieを空にしているのにも関わらず200を返却するindex.htmlのアクセス時にはaws-waf-tokenをCookieのパラメータとして送信していることが確認できます。

試しにaws-waf-tokenを削除すると上記の様に再度202レスポンスを受け取るアクセスが発生しているので、最終的にコンテンツを返却するかどうかはこの値で制御を行っていそうです。

流れとしては以下のようなものとなりそうです。

  1. 初回アクセス時(aws-waf-tokenの値が不正もしくは空)の場合WAFが検証用のコンテンツを代わりに返却する
  2. クライアント側がそこに含まれるスクリプトを実行し検証トークンであるaws-waf-tokenを取得しCookieに書き込む
  3. リロードすることでaws-waf-tokenを含むリクエストを行うことで検証に成功し本来のコンテンツが取得される

セキュリティ上の観点から公開されることはないかと思いますが、2.で実行されるスクリプトでは何らかの判定処理は別途実行している様でブラウザからアクセスした場合であっても時折アクセスができないケースが見られました。

※ 白画面表示になる時もあれば何らかのアラートが出るケースもあり場合により異なりそうです

終わりに

今回はChallengeアクションの挙動を確認してみました。

ユーザかどうかを判別するための仕組みがChallengeアクションでは自動実行となり、CHAPTCHAアクションでは人間が行うという違いはありますが、怪しげなアクションに対して追加認証を行うという意味では概ね似た様な利用用途となりそうです。

認証自動で行なってくれるのであればユーザ側に負荷がかからないのであればCAPTCHAいらないのでは?と思う方がいらっしゃるかもしれませんが、当然検証が人力ではなく自動となる分誤検知のリスクが出てくるのでそれが許されるかどうかを考えた上で選択する必要が出てきます。

実際にはbot control等の別ルールと組み合わせた上で念の為の確認で使うからよっぽどなことない限り大丈夫、と割り切ることもできますが、ケースバイケースになりますのでもしもの誤検知が発生した場合に許容できるケースなのかというのは十分に検討した上でご利用いただければと思います。