NoSQLインジェクションの盲点:「文字列を期待した場所にオブジェクトを投げ込む」悪夢
現場でインシデント対応をしていると、「SQLじゃないから大丈夫」という慢心が一番の脆弱性だと痛感する。NoSQL、特にMongoDBのようなJSONベースのドキュメントストアを使っているエンジニアは、SQLの「シングルクォーテーションをエスケープする」という呪縛から解放された気になっているかもしれないが、それは単に「新しい地獄」に足を踏み入れただけだ。
今日は、NoSQLインジェクションの中でも、特に実務で「まさか」と思われがちな型変換の罠について、現場の知見を共有しよう。
—
1. なぜ「オブジェクト」が投げ込まれるとシステムは死ぬのか?
多くのNoSQLデータベースのドライバーやORMは、クライアントからの入力をそのままクエリの条件式として解釈してしまうことがある。
例えば、ログイン機能で username を文字列として受け取ることを想定しているコードを見てほしい。
攻撃者の視点:PoC(Proof of Concept)
攻撃者は、username フィールドに単純な文字列ではなく、「クエリ演算子を含むオブジェクト」を送り込む。
// 攻撃リクエストボディ
{
“username”: {“$gt”: “”},
“password”: {“$gt”: “”}
}
もしサーバー側の実装が、リクエストボディをそのままMongoDBの find() に渡していたらどうなるか? $gt(Greater Than)演算子は「空文字より大きいもの」をすべてマッチさせる。つまり、パスワードを知らなくても、データベース上の先頭ユーザー(多くの場合管理者)のセッションが乗っ取られる。
これはエスケープ処理云々の話ではない。「型が期待通りではない」ことに起因するロジックの崩壊だ。
—
2. 厳格なバリデーションの実装(Node.js / Expressの例)
「とりあえずリクエストボディをパースしてDBに投げる」のは、今日で終わりにしよう。実務では、入力値の型をホワイトリスト形式でガチガチに縛る必要がある。
Node.js環境であれば、Joi や Zod を使ったスキーマバリデーションが必須だ。以下は、型を厳格に制限する実装例だ。
const Joi = require(‘joi’);
// 厳格なスキーマ定義:文字列型であること、最大長を制限することを強制
const loginSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().min(8).required()
});
app.post(‘/login’, async (req, res) => {
// バリデーション実行
const { error, value } = loginSchema.validate(req.body);
if (error) {
// 攻撃の可能性ありとしてログに記録し、詳細なエラーは返さない
console.error(Invalid input format: ${error.message});
return res.status(400).send(‘Invalid request’);
}
// ここで初めて、バリデーション済みの安全な value を使用する
const user = await db.collection(‘users’).findOne({
username: value.username,
password: value.password // 本来はハッシュ化された値で照合すること
});
// … 以下処理
});
ポイント:
Joi.string()を指定することで、攻撃者が送ってきた{ $gt: "" }のようなオブジェクトはバリデーションエラーとなり、DBに到達する前に遮断される。
—
3. インフラ層での防御:WAFによるリクエストの「型」チェック
アプリケーションコードの修正が追いつかないレガシーなシステムの場合、あるいは多層防御を極めるなら、WAF(AWS WAF等)でJSONの構造を監視するのも一つの手だ。
AWS WAFの「JSONボディ検査」機能を使えば、リクエストボディ内の特定のキーに対して、値がオブジェクトになっていないか、あるいは特定の文字列パターンに合致するかをルール化できる。
AWS WAF JSON Matchルール(概念):
Match scope:usernameフィールドCondition: 値が「英数字のみ(^[a-zA-Z0-9]+$)」であること。- これに合致しないリクエストは即座にブロックする。
—
4. セキュリティチーフからの「泥臭い」アドバイス
技術的な実装以上に大切なのは、チーム内の「入力に対する不信感」を標準化することだ。
1. 型変換を許すな: req.body を直接DBクエリに展開するようなコードは、コードレビューで絶対に落とすこと。
2. ログに「型」を出せ: インシデントが発生した際、攻撃者はJSONの深い階層に演算子を隠す。ログには「どのフィールドに」「どんな型が」「何が入ってきたか」を詳細に出す実装を入れておけ。
3. NoSQL固有の機能を制限せよ: MongoDBであれば、$where 演算子(JavaScriptの実行を許可する機能)はセキュリティリスクの塊だ。可能な限り無効化するか、アクセス権限を最小限に絞る設定を行え。
「SQLインジェクション対策は完璧だ」と胸を張るエンジニアほど、NoSQLの型変換攻撃で足元をすくわれる。Webアプリケーションの脆弱性は、常に「仕様の隙間」に潜んでいる。コードを書くときは、常に「このデータがオブジェクトだったら、俺のコードはどう動く?」と自問自答してほしい。
それが、プロのエンジニアがインシデントを未然に防ぐための、唯一にして最強の防壁だ。
コメント