FlawOpen

flawopen.com/リファレンス/MongoDB NoSQL Injection:入力がクエリを変える仕組み

MongoDB NoSQL Injection:入力がクエリを変える仕組み

データベースセキュリティガイド

MongoDB は SQL を実行しないため、SQL 文字列の連結が問題なのではありません。スカラー値を想定する箇所でアプリが未信頼のネストしたオブジェクトを受け取り、MongoDB フィルターに渡すと NoSQL Injection が起こり得ます。これはアプリの入力検証不備であり、MongoDB サーバーの CVE ではありません。

平易な説明:値がルールになる

名前を尋ねるフォームで、係員が記入欄に一連の保管指示まで受け入れてしまう場面を想像してください。データベースは名前を比較せず、指示に従います。期待する値の型を検証し、検索ルールはアプリ側で構築しましょう。

信頼境界:リクエスト JSON から MongoDB フィルターまで

クエリフィルター
MongoDB がフィールド比較として解釈する BSON ドキュメント。明示的に含まれる場合はクエリー演算子も表します。
演算子インジェクション
単一のリテラル値を想定した位置に、ユーザーが制御できる構造をアプリが渡すこと。
スカラー検証
入力がデータベース操作に影響する前に、型、長さ、許可形式を確認すること。
プロジェクション
意図しないデータ露出を抑えるため、サーバーが返却フィールドを選ぶこと。

障害の発生手順

1

エンドポイントはスカラーを想定

API 契約では username は文字列ですが、JSON パーサーは配列、オブジェクト、真偽値、null も表現できます。

2

アプリが型検証を省略

ハンドラーは解析済みの値をサーバー側で作成したフィルターへ直接挿入するため、オブジェクトが文字列ではなくクエリ構造として残ります。

3

MongoDB が受け取った構造を評価

ネストしたドキュメントにクエリー演算子があれば、条件は意図した完全一致と異なる意味になり得ます。影響するレコードはクエリとアプリの認可チェック次第です。

4

応答が露出範囲を決める

返却フィールドとレコード数はプロジェクション、エンドポイントの挙動、DB アカウント権限によって変わります。これだけで認証回避や DB 全体へのアクセスが証明されるわけではありません。

範囲と根拠

MongoDB の公式ドライバーガイドは、エンドポイントが未検証の JSON を BSON に変換してフィルターに使う場合の境界を説明しています。本ガイドはアプリ側の不備を示すもので、すべての MongoDB 環境が脆弱だとは主張せず、サーバー CVE や認証回避手順も示しません。

Node.js の脆弱な例と強化例

VULNERABLE — UNVALIDATED VALUE ENTERS FILTER
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);
  }
});
HARDENED — VALIDATE SHAPE, BUILD FILTER, VERIFY HASH
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 は設定済みのパスワードハッシュライブラリを指します。セッション作成は意図的に対象外です。

修正を確認するチェック

一次資料

関連するセキュリティガイド:セキュリティディレクトリを開く →
関連項目 SQL インジェクション(CWE-89)の仕組みと各言語での防ぎ方 — flawopen.com 数値 ID でも文字列連結で SQL Injection が起きますか? パラメータ化クエリとは何か Prisma $queryRaw は SQL Injection に安全? Railsのwhere()文字列補間は安全? Sequelize query() は SQL Injection に安全? SQLAlchemy text() は SQL Injection に安全?