flawopen.com/リファレンス/MongoDB NoSQL Injection:入力がクエリを変える仕組み
MongoDB NoSQL Injection:入力がクエリを変える仕組み
MongoDB は SQL を実行しないため、SQL 文字列の連結が問題なのではありません。スカラー値を想定する箇所でアプリが未信頼のネストしたオブジェクトを受け取り、MongoDB フィルターに渡すと NoSQL Injection が起こり得ます。これはアプリの入力検証不備であり、MongoDB サーバーの CVE ではありません。
平易な説明:値がルールになる
名前を尋ねるフォームで、係員が記入欄に一連の保管指示まで受け入れてしまう場面を想像してください。データベースは名前を比較せず、指示に従います。期待する値の型を検証し、検索ルールはアプリ側で構築しましょう。
信頼境界:リクエスト JSON から MongoDB フィルターまで
- クエリフィルター
- MongoDB がフィールド比較として解釈する BSON ドキュメント。明示的に含まれる場合はクエリー演算子も表します。
- 演算子インジェクション
- 単一のリテラル値を想定した位置に、ユーザーが制御できる構造をアプリが渡すこと。
- スカラー検証
- 入力がデータベース操作に影響する前に、型、長さ、許可形式を確認すること。
- プロジェクション
- 意図しないデータ露出を抑えるため、サーバーが返却フィールドを選ぶこと。
障害の発生手順
エンドポイントはスカラーを想定
API 契約では username は文字列ですが、JSON パーサーは配列、オブジェクト、真偽値、null も表現できます。
アプリが型検証を省略
ハンドラーは解析済みの値をサーバー側で作成したフィルターへ直接挿入するため、オブジェクトが文字列ではなくクエリ構造として残ります。
MongoDB が受け取った構造を評価
ネストしたドキュメントにクエリー演算子があれば、条件は意図した完全一致と異なる意味になり得ます。影響するレコードはクエリとアプリの認可チェック次第です。
応答が露出範囲を決める
返却フィールドとレコード数はプロジェクション、エンドポイントの挙動、DB アカウント権限によって変わります。これだけで認証回避や DB 全体へのアクセスが証明されるわけではありません。
MongoDB の公式ドライバーガイドは、エンドポイントが未検証の JSON を BSON に変換してフィルターに使う場合の境界を説明しています。本ガイドはアプリ側の不備を示すもので、すべての MongoDB 環境が脆弱だとは主張せず、サーバー CVE や認証回避手順も示しません。
Node.js の脆弱な例と強化例
app.post("/users/lookup", async (req, res, next) => {
try {
// The route expects a string, but req.body.username is untrusted JSON.
const user = await users.findOne({ username: req.body.username });
res.json(user ? { username: user.username } : null);
} catch (error) {
next(error);
}
});
const loginSchema = z.object({
username: z.string().trim().min(1).max(128),
password: z.string().min(1).max(1024)
}).strict();
app.post("/login", async (req, res, next) => {
try {
const parsed = loginSchema.safeParse(req.body);
if (!parsed.success) return res.sendStatus(400);
const { username, password } = parsed.data;
// The application owns the filter key; input supplies only a string value.
const user = await users.findOne(
{ username },
{ projection: { _id: 1, username: 1, passwordHash: 1 } }
);
const passwordMatches = user &&
await argon2.verify(user.passwordHash, password);
if (!passwordMatches) return res.sendStatus(401);
return res.sendStatus(204); // Session creation is application-specific.
} catch (error) {
next(error);
}
});
強化した処理では、データベースへアクセスする前に文字列以外のフィールドを拒否し、クライアントからフィルターオブジェクトを受け取りません。フィルターの固定キーはアプリケーション側で組み立て、選択したフィールドだけを返し、ユーザー検索後にパスワードハッシュを検証します。この例では z は Zod、users は MongoDB コレクション、argon2 は設定済みのパスワードハッシュライブラリを指します。セッション作成は意図的に対象外です。
修正を確認するチェック
- ✓通常の文字列を送り、ハンドラーがアプリ所有の正確なフィールドだけを検索することを確認する。
- ✓オブジェクト、配列、数値、真偽値、null、欠落フィールド、長すぎる文字列、未知のプロパティを送り、MongoDB 呼び出し前に schema が拒否することを確認する。
- ✓DB 呼び出しがサーバー構築のフィルターを受け取り、クライアントのフィルターや生の JSON 断片を受け取らないことを確認する。
- ✓プロジェクションがパスワードハッシュなど無関係なフィールドを除外し、DB ロールが必要な権限だけを持つことを確認する。
- ✓Argon2id または bcrypt でパスワードをハッシュし、アプリで照合する。送信されたパスワードを MongoDB フィルターに使わない。
一次資料
- MongoDB — クライアントライブラリのベストプラクティス:JSON を BSON に変換する前に未信頼入力を検証
- OWASP — NoSQL セキュリティチートシート
- MongoDB — クエリフィルターの作成(C# ドライバー):検証はアプリの責任