【実務・中級編】NoSQLインジェクションの仕組みとMongoDBにおける演算子フィルタリング – アプリケーションセキュリティ & 安全な開発防御ガイド

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)。

セキュリティは、「動いているから大丈夫」という慢心が一番の脆弱性になる。あなたの書くコードが、攻撃者にとっての「攻略不可の壁」になることを期待している。

コメント

タイトルとURLをコピーしました