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
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
show(db,"SELECT id,agent,rate FROM signal WHERE rate>20")id | agent | rate s2 | Browser | 50
show(db,"SELECT id,agent,rate FROM signal WHERE js=0")id | agent | rate s3 | Monitor | 1 s4 | Browser | 3
token=challenge()
payload=token.split('.')[0]
print(json.loads(base64.urlsafe_b64decode(payload+'='*(-len(payload)%4)))){'sub': 's7', 'path': '/checkout', 'exp': 160, 'nonce': 'n7'}print(verify(token,150,'s8','/checkout',set()))
print(verify(token,150,'s7','/admin',set()))binding_rejected binding_rejected
used=set()
print(verify(token,150,'s7','/checkout',used))
print(verify(token,151,'s7','/checkout',used))accepted replayed
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
演習完了