NoSQLインジェクション:MongoDBの「演算子」が引き起こす認証崩壊の罠
現場でコードレビューをしていると、今でもたまに遭遇するのが「SQLインジェクションには気を使っているが、NoSQLはクリーンだと思っている」というエンジニアだ。
結論から言うと、MongoDBのようなドキュメント指向DBにおいて、アプリケーション層のバリデーションを怠ったクエリ構築は、SQLインジェクションと同等、あるいはそれ以上に致命的な結果を招く。特に、Webフレームワークが提供する「JSONオブジェクトの自動パース」をそのままDBクエリに投げ込んでいる場合、認証バイパスは一瞬で成立する。
今日は、なぜこの脆弱性が生まれるのか、そして現場でどうやってこれを「徹底的に」封じ込めるかを解説する。
—
1. 攻撃者が狙う「型」の盲点
MongoDBのクエリはJSON形式で記述される。ここで悪用されるのが、演算子による型操作だ。
例えば、ログイン機能でユーザーからの入力をそのままフィルタとして渡す、よくあるコードを想像してほしい。
// 脆弱な実装例 (Node.js/Express)
app.post(‘/login’, async (req, res) => {
const user = await db.collection(‘users’).findOne({
username: req.body.username,
password: req.body.password
});
// …ログイン処理
});
正常なリクエストは { "username": "alice", "password": "password123" } だ。しかし、攻撃者はここに「オブジェクト」を注入する。
// 攻撃リクエストの例
{
“username”: “admin”,
“password”: { “$gt”: “” }
}
このリクエストが送られると、クエリは以下のようになる。
db.collection(‘users’).findOne({
“username”: “admin”,
“password”: { “$gt”: “” } // 「空文字より大きいパスワード」にマッチする
});
結果、パスワードが何であろうと、$gt(Greater Than)演算子が「空文字より大きい」という条件を真にしてしまい、攻撃者はパスワードを知ることなく管理者のセッションを手に入れてしまう。これがNoSQLインジェクションの基本にして最大の脅威だ。
—
2. なぜ「型検証」が防御の要なのか
この攻撃を防ぐための第一歩は、「入力は常に文字列として扱う」という鉄則をコードに強制することだ。
多くの開発者がやりがちなミスは、req.body をそのままDBのフィルタに突っ込むことにある。これを防ぐには、入力値に対して厳格な型変換を行う必要がある。
【セキュアな実装サンプル:Node.js + Express】
ライブラリに頼るのもいいが、まずは生のプリミティブ型で制御する感覚を養ってほしい。
const express = require(‘express’);
const app = express();
app.post(‘/login’, async (req, res) => {
// 1. 入力を明示的に文字列としてキャストする
// これにより、攻撃者がオブジェクトを送り込んでも、
// “[object Object]” という文字列に変換され、クエリが失敗する
const username = String(req.body.username || ”);
const password = String(req.body.password || ”);
// 2. 念のため空チェック
if (!username || !password) {
return res.status(400).send(‘不正なリクエストです’);
}
const user = await db.collection(‘users’).findOne({
username: username,
password: password
});
if (user) {
res.send(‘ログイン成功’);
} else {
res.status(401).send(‘認証失敗’);
}
});
この「キャスト」を行うだけで、演算子による攻撃は無効化される。単純だが、これが現場で最も見落とされる泥臭い防御策だ。
—
3. 多層防御の切り札:WAFでのリクエスト監視
コード側での修正が完了していることが前提だが、組織として「万が一」に備えるなら、WAF(Web Application Firewall)で怪しいJSONオブジェクトのパターンを弾く設定を入れるべきだ。
例えば、AWS WAFを使用している場合、リクエストボディ内のJSONに $gt, $ne, $regex などの演算子が含まれていないかをチェックするルールを作成する。
AWS WAF JSON Match設定例(概念):
- Scope: Request Body
- Field:
/password/(パスワードフィールドを対象) - Match Type: Regex Pattern
(\$gt|\$ne|\$regex|\$where) - Action: Block
※ただし、WAFはあくまで「予防線」だ。ビジネスロジックで扱うパラメータに演算子が含まれる正当なケースがある場合は、誤検知(False Positive)を避けるため、アプリケーション側のバリデーションを優先すべきだ。
—
最後に:セキュリティは「性悪説」で設計せよ
NoSQLインジェクションを防ぐためのチェックリストをまとめた。明日からチームのコードを見直す際の指針にしてほしい。
1. 入力の型を信用しない: req.body や $_POST をそのままDBクエリに渡さない。必ず String() やバリデーションライブラリ(JoiやZodなど)を通してプリミティブ型に変換する。
2. 演算子の無効化: 必要であれば、MongoDBのオプションで演算子の利用を制限する、あるいはアプリケーション層でホワイトリスト形式のバリデーションを徹底する。
3. 最小権限の原則: Webアプリケーション用のDBユーザーには、$where 演算子(JavaScript実行機能)の実行権限を与えない。これはMongoDBの設定で無効化しておくべきだ(security.javascriptEnabled: false)。
セキュリティは、「動いているから大丈夫」という慢心が一番の脆弱性になる。あなたの書くコードが、攻撃者にとっての「攻略不可の壁」になることを期待している。
コメント