「MongoDBに移行したからSQLインジェクションの心配はない」──そう安心しているシステムが、実際には似たような攻撃で認証を突破される事例が増えています。
NoSQLデータベースはSQLを使わないため「SQLインジェクション」は通じません。しかし独自のクエリ構文には、それに対応した別のインジェクション脆弱性が潜んでいます。これが「NoSQLインジェクション」です。OWASPも継続的に注意を呼びかけており、MongoDBやCouchDBを利用するWebアプリケーションでの被害事例が国内外で報告されています。
この記事では、NoSQLインジェクションの仕組み・MongoDBを例にした具体的な攻撃パターン・PHPやNode.jsでの防御実装について、現場で使えるレベルで解説します。
NoSQLインジェクションとは?なぜ危険なのか
NoSQLインジェクションとは、MongoDB・Cassandra・Redis・CouchDBなどのNoSQLデータベースに対して、不正なクエリを注入する攻撃手法です。
SQLインジェクションがSQLの構文を悪用するように、NoSQLインジェクションではそれぞれのNoSQLデータベース固有のクエリ言語・演算子・データ形式(JSONなど)を悪用します。攻撃者がSQLをまったく知らなくても試行できる点が、見過ごされやすい理由の一つです。
主な被害パターンは次のとおりです。
・認証バイパス: ログイン処理を迂回し、正しいパスワードなしでアカウントに侵入される
・データ抜き取り: 本来アクセスできないはずのデータが全件流出する
・データ改ざん・削除: データベースの内容を不正に書き換えられる
・サービス停止(DoS): 重いJavaScript処理をクエリに埋め込んでサーバーを過負荷にする
OWASPのAPI Security Top 10でも「Injection」として言及されており、REST APIバックエンドにMongoDBを採用するシステムでは特に注意が必要です。
MongoDBを標的にした攻撃の仕組み
現在最も普及しているNoSQLデータベースはMongoDBです。以下では防御目的でMongoDBへの攻撃パターンを解説します。
1. 認証バイパス($neオペレーターの悪用)
Node.js(Express)でユーザー認証を実装する際、フォーム入力をそのままMongoDBクエリに渡すコードを書くと次のようになります。
# 脆弱な実装例(Node.js / Express) # req.body.username と req.body.password をそのまま渡している const user = await db.collection('users').findOne({ username: req.body.username, password: req.body.password });
この実装に対して攻撃者が次のようなJSONボディを送信したとします。
# 攻撃ペイロード(POSTボディをJSON形式で送信) { "username": "admin", "password": { "$ne": "" } }
$neは「等しくない(not equal)」を意味するMongoDBの演算子です。このクエリは「usernameがadminで、passwordが空文字以外の任意の値」を持つドキュメントを検索するように変化します。結果として、正しいパスワードを知らなくてもログインが成功してしまうことがあります。
同様に、$gt(より大きい)・$regex(正規表現)・$exists(フィールドが存在する)といった演算子も悪用されます。
2. $whereオペレーターによるJavaScript実行
MongoDBの$where演算子はクエリ条件にJavaScript式を記述できる機能です。この演算子に外部入力が混入すると、データベース内でJavaScriptを任意実行できる状態になります。
# $where悪用例(概念) # 攻撃者がusernameパラメーターに以下を注入 "'; while(true){} //" # → MongoDBがJavaScriptを評価し、無限ループでサービスが停止する
さらにsleep()関数を使った「タイムベース・ブラインドNoSQLインジェクション」によって、応答時間の差からデータ内容を一文字ずつ推定できます。MongoDBバージョン5.0以降では$whereは非推奨となっています。既存のコードで使用していないか確認が必要です。
3. URLパラメーターからのオペレーター注入
Node.jsのExpressはデフォルトで、クエリ文字列に含まれるブラケット記法を自動的にオブジェクトに変換します。
# 攻撃者が以下のURLにアクセスする GET /users?username[$ne]=&password[$ne]= # Expressが自動変換すると次のオブジェクトになる # { username: { '$ne': '' }, password: { '$ne': '' } } # → 全ユーザーが一致する条件となり、最初のドキュメントが返る
GETリクエスト(URLパラメーター)経由でも認証バイパスが成立する点は、見落とされやすい脆弱性です。
具体的な防御手順
1. 入力値の型チェックを最初の関門にする
最も基本的な防御は、受け取ったパラメーターが期待する型(文字列・数値など)かどうかを必ず検証することです。オブジェクト型のデータが来た時点で即座にエラーを返します。
# 型チェックを加えた実装例(Node.js) const username = req.body.username; const password = req.body.password; # 文字列以外が来た場合は即座に400エラーを返す if (typeof username !== 'string' || typeof password !== 'string') { return res.status(400).json({ error: 'Invalid input' }); } const user = await db.collection('users').findOne({ username: username, password: password });
2. mongo-sanitizeでオペレーターを除去する
Node.jsのサードパーティライブラリ「mongo-sanitize」を使うと、外部入力に含まれる$で始まるMongoDBオペレーターを自動的に除去できます。
# インストール npm install mongo-sanitize # 使用例(Express) const sanitize = require('mongo-sanitize'); const username = sanitize(req.body.username); const password = sanitize(req.body.password); # $で始まるキーがオブジェクト内に含まれていた場合、そのキーを除去する # { "$ne": "" } → {} に変換される
PHPの場合は「php-mongo-sanitizer」等のライブラリが利用できます。入力値をそのままクエリに渡す前に必ずサニタイズする習慣をコードレビューのチェックリストに追加しましょう。
3. MongooseなどのODMでスキーマ定義を活用する
Node.jsのMongooseのように、スキーマ定義に基づいてデータ型・バリデーションを自動適用するODM(Object-Document Mapper)を活用すると、予期しない型のデータがクエリに混入するリスクを大幅に下げられます。スキーマでString型を定義したフィールドには、オブジェクトが渡されても自動的に変換または拒否されます。
4. $whereを使わず、Aggregation Pipelineへ移行する
コードベースで$whereを検索し、見つかった箇所はMongoDBの集計パイプライン(Aggregation Pipeline)や$expr演算子を使った書き直しを検討してください。MongoDBはサーバーサイドJavaScriptの実行をデフォルトで無効化するオプション(--noscripting)も提供しています。
# MongoDB起動時にサーバーサイドJS実行を無効化する(mongod.confに設定) security: javascriptEnabled: false
5. データベースユーザーに最小権限を適用する
アプリケーションが使用するMongoDBユーザーには、必要最小限のロールのみを付与します。読み取り専用の機能にはreadロール、書き込みが必要な機能にのみreadWriteロールを割り当て、dbAdminやrootロールをアプリケーションユーザーに与えないことが重要です。
Linuxサーバーのファイル権限管理と同じ考え方です。権限の詳細については、姉妹サイトLinuxMaster.JPでも解説しています。
中小企業でも今日からできること
MongoDBを使っているシステムがある場合、まず以下のチェックから始めましょう。費用はほとんどかかりません。
・コードの即時検索: grep -rn '\$where' をプロジェクトルートで実行し、危険な演算子を探す
・npm audit実行: npm audit または composer audit を定期実行して依存ライブラリの既知脆弱性を確認する
・ログイン処理のコードレビュー: フォーム入力を型チェックなしにMongoDBクエリへ渡している箇所がないか確認する
・WAFルールの確認: 利用しているWAF(クラウドFlare・AWS WAF等)にNoSQLインジェクション検知ルールが含まれているか確認する
・MongoDBバージョン確認: バージョン5.0以上へのアップグレードを検討し、セキュリティパッチを最新に保つ
開発担当が社内にいない場合でも、外部に発注しているシステムの開発会社に「NoSQLインジェクション対策は実施しているか」と確認するだけで抑止力になります。
よくある誤解と注意点
【誤解1】「公式ドライバーを使っているから安全」
MongoDBの公式ドライバーはSQLインジェクションのような構文レベルの攻撃を防ぎますが、アプリケーション側でユーザー入力をそのままクエリオブジェクトとして渡す実装をしていると、NoSQLインジェクションは成立します。ドライバーの安全性とアプリケーションの実装の安全性は別の話です。
【誤解2】「Firebaseを使えば大丈夫」
Firebase Realtime DatabaseやFirestoreでも、セキュリティルールの設定が不十分だと認証済みユーザーが本来アクセスできないデータにアクセスできる状態になります。「クラウドが管理してくれる」という安心感は、セキュリティルール設計の疎かさにつながりやすいため注意が必要です。
【誤解3】「型変換で自動的に安全になる」
フレームワークによっては外部入力をMongoDBクエリに渡す前に型変換を試みるものがあります。しかし型変換は完全な防御策ではなく、依然として演算子インジェクションが通る場合があります。バリデーション・サニタイズ・型チェックを組み合わせた多層防御が重要です。
【注意】攻撃の試みは不正アクセス禁止法の対象
学習目的であっても、他者のシステムに対してNoSQLインジェクションを試みることは不正アクセス禁止法に抵触します。検証は必ず自社の開発環境・ローカル環境で行ってください。詳細は法律の専門家にご確認ください。
本記事のまとめ
| 攻撃パターン | 主な悪用演算子 | 対策 |
|---|---|---|
| 認証バイパス | $ne・$gt・$regex | 入力の型チェック + mongo-sanitize |
| JavaScriptインジェクション | $where | $whereを廃止 + javascriptEnabled: false |
| URLパラメーター経由 | ブラケット記法で任意演算子 | GETパラメーターの型チェックを徹底 |
| ブラインドインジェクション | $where + sleep() | サーバーサイドJS実行の無効化 |
「SQLを使っていないから安全」という思い込みが、NoSQLインジェクションを見過ごす最大の原因です。MongoDBを始めとするNoSQLデータベースを使うすべてのシステムで、入力バリデーション・オペレーターのサニタイズ・最小権限の適用を必ず実施してください。
