一覧SP-23-2 · DDoS・Bot・API保護:要求の分布と仕様

Botの識別とチャレンジ認証

観測に基づく候補の評価と、challenge結果の完全性・適用範囲を分けて説明します。

説明

Bot判定ではUser-Agent、要求頻度、ブラウザーの機能、challengeの結果など複数の信号を使います。一つの文字列やchallenge成功だけで、人の操作か正当な利用かを完全に判断できるわけではありません。

固定の観測表で単一条件の不足を調べます。その後、公開試験鍵で署名したchallenge結果を使い、対象、期限、改変、再利用の条件を実際に検証します。

観測に基づく候補の評価と、challenge結果の完全性・適用範囲を分けて説明します。

1観測信号を並べる

rateは固定期間の要求数、jsは機能観測の有無です。

from sp23 import * from sp02 import show観測信号を並べる db=connect() show(db,"SELECT * FROM signal ORDER BY id")

同じBrowserでも頻度やjsの値が違います。User-Agentは自己申告の文字列で、それだけでは本人確認になりません。

2高頻度の候補を抽出する

教材の比較条件として20を超えるものを取得します。

show高頻度の候補を抽出する(db,"SELECT id,agent,rate FROM signal WHERE rate>20")

s2が出ます。20はこの資料での条件であり、普遍的なBot判定の閾値ではありません。

3JavaScriptの観測がない候補を調べる

js=0を抽出します。

showJavaScriptの観測がない候補を調べる(db,"SELECT id,agent,rate FROM signal WHERE js=0")

MonitorとBrowserの両方が出ます。正常な監視や機能制限のある利用も含み得るため、無条件に悪性と扱いません。

4対象と期限を含む結果を発行する

s7の/checkoutに限り、160まで使える固定結果です。

token=challenge() payload=token.split('.')[0] print(json.loads対象と期限を含む結果を発行する(base64.urlsafe_b64decode(payload+'='*(-len(payload)%4))))

sub、path、exp、nonceを含みます。これはchallenge後の結果トークンの検証例で、画面上の人間判定そのものを実装したものではありません。

5別主体と別操作への流用を拒否する

同じトークンの適用先を変えます。

print(verify別主体と別操作への流用を拒否する(token,150,'s8','/checkout',set())) print(verify(token,150,'s7','/admin',set()))

どちらもbinding_rejectedです。正しい署名があっても、別主体や別操作に使わせません。

6正しい対象への利用と再利用を比較する

使用済みnonceを同じ集合へ記録します。

used=set() print(verify正しい対象への利用と再利用を比較する(token,150,'s7','/checkout',used)) print(verify(token,151,'s7','/checkout',used))

acceptedの後はreplayedです。今回のモデルは単回利用ですが、実製品の再利用・セッション継続条件は仕様に応じて評価します。

7改変と期限の境界を確認する

署名を一文字変えたものと期限160を試します。

changed=token[:-1]+('0' if token[-1]!='0' else '1') print(verify改変と期限の境界を確認する(changed,150,'s7','/checkout',set())) print(verify(token,160,'s7','/checkout',set()))

signature_invalidとexpiredです。challenge結果が検証できても、その後の購入権限や利用量の条件は別に評価します。

まとめ

単一の観測信号だけで悪性と判断しません。

結果トークンの主体・操作・期限を検証します。

challengeの成功と業務上の認可を分けます。

操作履歴と解説を振り返れます。

Python 3.15 / 固定要求列・HMAC・JSON Schema 2020-12

1
from sp23 import * from sp02 import show db=connect() show(db,"SELECT * FROM signal ORDER BY id")
id | agent | rate | js
s1 | Browser | 2 | 1
s2 | Browser | 50 | 1
s3 | Monitor | 1 | 0
s4 | Browser | 3 | 0
2
show(db,"SELECT id,agent,rate FROM signal WHERE rate>20")
id | agent | rate
s2 | Browser | 50
3
show(db,"SELECT id,agent,rate FROM signal WHERE js=0")
id | agent | rate
s3 | Monitor | 1
s4 | Browser | 3
4
token=challenge() payload=token.split('.')[0] print(json.loads(base64.urlsafe_b64decode(payload+'='*(-len(payload)%4))))
{'sub': 's7', 'path': '/checkout', 'exp': 160, 'nonce': 'n7'}
5
print(verify(token,150,'s8','/checkout',set())) print(verify(token,150,'s7','/admin',set()))
binding_rejected
binding_rejected
6
used=set() print(verify(token,150,'s7','/checkout',used)) print(verify(token,151,'s7','/checkout',used))
accepted
replayed
7
changed=token[:-1]+('0' if token[-1]!='0' else '1') print(verify(changed,150,'s7','/checkout',set())) print(verify(token,160,'s7','/checkout',set()))
signature_invalid
expired

演習完了