NoSQLインジェクション:JSONの裏側で「論理」が崩壊する瞬間
昨今の開発現場では、RDBの厳格なスキーマから解放され、柔軟なMongoDBなどを採用するケースが増えた。だが、自由度が高いということは、それだけ「守るべき境界線」を設計者自身が定義しなければならないということを意味する。
多くのエンジニアが「SQLインジェクションは知っているが、NoSQLなら大丈夫だろう」と高を括っている。しかし、これが最も危険な油断だ。SQLとNoSQLでは、攻撃の「文法」が違うだけで、本質的な脆弱性の正体はどちらも「データの境界を侵食されること」にある。
今日は、MongoDBを例に、なぜ彼らが認証を突破し、データベースの中身を根こそぎ抜き取れるのか、その「論理の罠」を解き明かそう。
—
1. なぜ「$gt」や「$ne」で認証が突破されるのか
SQLインジェクションが「文字列の連結」を悪用するのに対し、NoSQLインジェクションは「JSONオブジェクトの構造」を悪用する。
例えば、ユーザーログイン処理で以下のようなクエリを生成しているコードを見かけないだろうか?
// 脆弱な実装例(Node.js/Express)
db.collection(‘users’).find({
username: req.body.username,
password: req.body.password
});
このコードの設計者は、「username」と「password」に文字列が入ってくることを期待している。しかし、攻撃者はここに演算子を含むオブジェクトを送り込む。
攻撃PoC:論理のすり替え
攻撃者がパスワード入力欄に {"$ne": null} というJSONを送り込んだらどうなるか?
{
“username”: “admin”,
“password”: { “$ne”: null }
}
MongoDBにおいて $ne は「not equal(〜と等しくない)」を意味する。結果、クエリは「ユーザー名がadminで、かつパスワードがnullではないユーザー」を検索することになり、パスワードを知らなくても認証をパスしてしまう。これがインジェクションの正体だ。
—
2. セキュアな実装:入力値の型を「強制」する
この脆弱性を突かれる最大の原因は、「受信したJSONをそのままクエリに渡していること」にある。開発者の怠慢と言われても反論できないレベルの初歩的なミスだ。
対策は至ってシンプル。「型を強制的に固定すること」だ。
JavaScript/Node.js のセキュアな実装サンプル
ライブラリ(mongo-sanitize等)を使うのも手だが、まずは入力を厳格にバリデーションする癖をつけよう。
const express = require(‘express’);
const app = express();
app.post(‘/login’, (req, res) => {
// 1. 入力値が文字列であることを明示的に保証する
const username = String(req.body.username);
const password = String(req.body.password);
// 2. さらにバリデーションライブラリ(Joi等)を通すのが理想
// ここでは単純化のため、型変換のみを行う
db.collection(‘users’).findOne({
username: username,
password: password
}, (err, user) => {
if (user) {
res.send(“ログイン成功”);
} else {
res.status(401).send(“認証失敗”);
}
});
});
ポイント: String() を通すことで、{ "$ne": null } のようなオブジェクトは "[object Object]" という単なる文字列に変換される。これで攻撃用演算子は無力化される。
—
3. インフラ・レイヤーでの防御:WAFという防波堤
アプリ側の改修が完了するまでの間、あるいは多層防御の観点から、WAF(Web Application Firewall)の設定も必須だ。特にAWS WAFなどを使用している場合、JSON内の演算子を検知するルールを適用しよう。
AWS WAF(JSON Body Inspection)の考え方
JSONボディに対する検査を有効にし、以下のシグネチャを監視するルールを構築する。
- 監視対象:
$.(すべてのキー) - パターンマッチ: 以下の正規表現をブロック対象にする
\$gt\$ne\$where(JavaScript実行を許す非常に危険な演算子)\$regex
これらを検知した瞬間に403を返すように設定しておけば、開発チームが修正している間の強力な盾になる。
—
最後に:セキュリティは「性悪説」で設計せよ
現場で多くのインシデントを見てきたが、被害を受けるシステムの共通点は「ユーザーの入力値は、期待した通りにやってくる」という性善説に立脚した設計をしていることだ。
1. 入力の型を疑え: 文字列を期待する場所にオブジェクトを投げ込まれたらどうなるか?常に最悪のケースを想定してバリデーションする。
2. $where は封印せよ: MongoDBのクエリ内でJavaScriptコードを実行する $where 演算子は、原則として使用禁止にすべきだ。これを使った時点で、脆弱性のリスクは何倍にも跳ね上がる。
3. 最小権限の原則: アプリケーションがデータベースに接続するユーザー権限は、必要なコレクションに対する操作権限のみに絞り込むこと。
セキュリティは一度設定して終わりではない。技術の進化と共に攻撃手法も巧妙化する。だが、本質的な「境界の防御」さえ守り抜けば、どんなに複雑なNoSQL環境でも強固な要塞を築くことは可能だ。
君たちの書くコードが、明日の信頼を支える。今日から「入力値の厳格な型チェック」をコードレビューの必須項目に加えてみてほしい。
コメント