目的
Cloudflare Turnstile を触って理解します。
成果物 @ GitHub => kyouheicf/turnstile-demo
Cloudflare Turnstile の特徴
- Javascript タグを挿入するだけで
challenges.cloudflare.com
と通信してチャレンジをおこなってくれる - Cloudflare にゾーン登録して CDN を採用しなくても使える
- Managed Challenge と同じ仕組みで、なるべくキャプチャを解く必要がないよう、機械学習も組み込み、高速なユーザ体験を提供する
ウィジェットタイプ
ページ内に配置するウィジェットのタイプを以下の中から選択できる。ほとんどの場合、Managed で問題ないでしょう。
- Managed (recommended)
- いくつかの計算要求等の結果によって、インタラクティブな操作をマネージドで要求することがある
- Non-Interactive
- インタラクティブな操作要求はない
- Invisible
- ページ内のウィジェットは不可視の状態でチャレンジを実行
サイト作成
1ウィジェットを配置するにあたり、サイト作成時に取得できる sitekey (public) と secret key (private) が必要です。
ローカルでテストできるように localhost
や 127.0.0.1
をドメインに追加しておくと良いです。
https://developers.cloudflare.com/turnstile/frequently-asked-questions/#can-i-use-turnstile-when-developing-locally
以下に提供するダミーのサイトキーは、localhostを含むどのドメインからでも使用できます。
Cloudflareは、本番で使用するサイトキーはローカルドメイン(localhost、127.0.0.1)を許可しないことを推奨していますが、ユーザは許可するドメインのリストにローカルドメインを追加することができます。
Implicit / Explicit レンダリング
以下のコードを参考に、Visual Studio Code でプレビューが確認できます。
disabled属性を使えば、ウィジェットの動きに連動してボタンを有効化・無効化することも可能です。
Implicit レンダリング
div class="cf-turnstile"
が決めうちで、コード量が少なくシンプルに記述できます。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Turnstile Demo</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form action="posttest.php" method="post">
Username: <input type="text" name="username" /> <br>
Blood Group: <input type="text" name="bloodgroup" /> <br>
<!-- The Turnstile widget will be injected in the following div. -->
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY" data-callback="javascriptCallback" data-language="ja"></div>
<!-- end. -->
<input type="submit" id="submit" disabled />
</form>
<script>
window.javascriptCallback = function () {
document.getElementById('submit').disabled = false;
console.log(`Challenge Success.`);
}
</script>
</body>
</html>
Explicit レンダリング
render()
を使ってウィジェットのレンダリングすることができますが、ある時点までチャレンジの実行を延期したい場合には、 execute
モードを使用して、チャレンジが実行され、トークンが生成されるタイミングを制御できたり、と柔軟性があります。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Turnstile Demo</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
</head>
<body>
<form action="posttest.php" method="post">
Username: <input type="text" name="username" /> <br>
Blood Group: <input type="text" name="bloodgroup" /> <br>
<!-- The Turnstile widget will be injected in the following div. -->
<div id="turnstileWidget"></div>
<!-- end. -->
<input type="submit" id="submit" disabled />
</form>
<script>
// This function is called when the Turnstile script is loaded and ready to be used.
// The function name matches the "onload=..." parameter.
// if using synchronous loading, will be called once the DOM is ready
window.onloadTurnstileCallback = function () {
turnstile.render('#turnstileWidget', {
sitekey: 'YOUR_SITE_KEY',
theme: 'auto',
language: 'ja',
callback: function (token) {
document.getElementById('submit').disabled = false;
console.log(`Challenge Success. Token === ${token}`);
},
});
}
</script>
</body>
</html>
Server-side validation
ウィジェットのレスポンスを cf-turnstile-response
として受け取れることができ、その内容を使って有効性を検証 siteverify
することができます。
https://developers.cloudflare.com/turnstile/frequently-asked-questions/#why-does-a-turnstile-token-need-to-be-verified-using-siteverify
Turnstileは、暗号的に保護されたトークンを作成するフロントエンド・ウィジェットである。しかし、顧客は自分のエンドでトークンの有効性をチェックすることはできません。
トークンが攻撃者によって偽造されたものでないこと、またはまだ消費されていないことを確認するために、顧客はCloudflareのsiteverify APIを使用してトークンの有効性をチェックする必要があります。
https://developers.cloudflare.com/turnstile/frequently-asked-questions/#can-the-front-end-use-siteverify
siteverifyAPIは、認証に使われたsecretkeyを明らかにするかもしれないので、フロントエンドから呼び出してはならない。攻撃者は単純にフロントエンドを変更してsiteverifyチェックを全く行わないようにし、Turnstileを無効にしてしまうかもしれない。
以下のページを用意し、Submit します。
<html>
<body>
Welcome <?php echo $_POST["username"]; ?> </br>
Your blood group is: <?php echo $_POST["bloodgroup"]; ?> </br></br>
cf-turnstile-response (Token) is: <?php echo $_POST["cf-turnstile-response"]; ?> </br></br>
siteverify result is: <?php
$data = [];
$data['secret'] = 'YOUR_SECRET_KEY';
$data['response'] = $_POST["cf-turnstile-response"];
// 送信データをURLエンコード
$data = http_build_query($data, "", "&");
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://challenges.cloudflare.com/turnstile/v0/siteverify",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}?>
</body>
</html>
以下のような結果が得られ、サーバーサイドでシークレットキーを使った検証ができることが確認できました。
{
"success": true,
"error-codes": [],
"challenge_ts": "2023-09-13T16:03:09.099Z",
"hostname": "grey.example.com",
"action": "",
"cdata": "",
"metadata": {
"interactive": false
}
}
Solve Rate
Analytics 画面で Solve Rate を確認することができます。
Solve Rate は時間の経過とともに比較的安定すると予想されますが、解決率の変化は、ボットからの攻撃を受けているなど、ウェブサイト上で何かが起こっていることを示します。
https://developers.cloudflare.com/turnstile/frequently-asked-questions/#what-is-visitor-solve-rate
Visitor Solve Rate は、発行されたチャレンジと比較して、発行されたが必ずしもサイト検証されていないトークンの割合です。
https://developers.cloudflare.com/turnstile/frequently-asked-questions/#what-is-api-solve-rate
API Solve Rate(API解決率)は、発行されたトークンと比較してサイト検証されたトークンの割合である。
まとめ
導入も容易なため、非常に便利に使えそうなことがわかりました。
以下にもある通り、いろいろなツールに組み込んで使えそうです。
また、compat=recaptcha
のオプションも用意されていたり、移行もシームレスにおこなえそうです。