【入門編】NoSQLインジェクション(MongoDB等)の攻撃パターン – アプリケーションセキュリティ & 安全な開発防御ガイド

「NoSQLは安全」という神話を捨てよう。MongoDBを狙う「インジェクション」の正体

こんにちは。セキュリティの現場で日々、複雑な攻撃と向き合っているエンジニアです。

今日は、最近のモダンな開発現場で当たり前のように使われている「NoSQL(特にMongoDB)」のセキュリティについてお話しします。「SQLインジェクションは有名だけど、NoSQLはJSONでやり取りするから安全だよね?」なんて思っていませんか?

実は、泥棒は「玄関の鍵」の形が変わったことに気づくと、新しいピッキングツールを開発してやってきます。今日は、NoSQLの世界で起きている「演算子インジェクション」という攻撃について、身近な例えを交えて紐解いていきましょう。

—

泥棒は「クエリ」という注文書を書き換える

まず、SQLインジェクションが「文章の一部を書き換えて、意図しない命令をデータベースに実行させる」ものだとしたら、NoSQLインジェクションは「注文書のフォーマットを悪用して、データベースに無理難題を押し付ける」行為です。

例え:家のドアと「合い鍵」の話

あなたが自分の家の玄関(データベース)に、スマートロックを導入したとしましょう。
通常、ユーザーは「IDとパスワード」という特定の鍵を持ってやってきます。

ところが、NoSQLインジェクションを使う攻撃者は、「鍵そのもの」を渡すのではなく、「『この家にあるすべての扉を開けろ』という特殊な指示書」を鍵穴に突っ込むようなことをしてきます。

攻撃の仕組み:演算子という「魔法の言葉」

MongoDBなどのNoSQLでは、データを検索するときに「演算子」と呼ばれる特殊な記号を使います。例えば、「$gt(Greater Than:より大きい)」や「$ne(Not Equal:等しくない)」といったものです。

攻撃者は、ログイン画面のID入力欄などに、普通のIDではなく、このような「魔法の言葉」を混ぜたJSONオブジェクトを送り込みます。

正常なリクエスト(IDが「alice」の人を探す):

{ “username”: “alice”, “password”: “password123” }

攻撃者が送り込むリクエスト(「IDが『何か』より大きい」という命令):

{ “username”: {“$gt”: “”}, “password”: {“$gt”: “”} }

この「$gt: “”(空文字より大きい)」という演算子は、「どんなデータでもヒットする(=常にTrue)」という性質を持っています。これを受け取ったデータベースは、「パスワードが空文字より大きいユーザーを教えて!」と解釈し、最初のユーザー(多くの場合、管理者)でログインを許可してしまうのです。まさに「合い鍵」なしでドアが開いてしまう瞬間です。

—

どうすれば防げるのか?:三つの鉄壁

「じゃあ、もうNoSQLは怖くて使えないの?」というと、決してそんなことはありません。家の防犯と同じで、対策を正しく行えば泥棒は諦めて去っていきます。

1. 入力を「ただの文字列」として扱う(型チェック)

一番の防御は、「入力されたものをそのままJSONに混ぜない」ことです。
攻撃者はJSONの「オブジェクト」を送り込んでくるので、コード側で「これは文字列であるべきだ」と厳密にチェックしましょう。

// 悪い例:リクエストをそのままクエリに突っ込む
db.users.find(req.body);

// 良い例:明示的に文字列としてキャストする
const username = String(req.body.username);
const password = String(req.body.password);

db.users.find({ username: username, password: password });

これだけで、もし攻撃者が {"$gt": ""} を送ってきても、それは「{$gt: ""} という名前のユーザー」を探そうとするだけになり、攻撃は失敗します。

2. 外部ライブラリでクエリをサニタイズする

自分で全てをバリデーションするのは大変ですよね。そんな時は、信頼できる「サニタイズ(無害化)ライブラリ」を使いましょう。mongo-sanitize のようなツールは、JSONのキーに「$」が含まれていると自動的に取り除いてくれます。

const sanitize = require(‘mongo-sanitize’);

// 入力値を掃除してからクエリを実行する
const safeQuery = sanitize(req.body);
db.users.find(safeQuery);

3. アプリケーションの権限を最小限に絞る

もし万が一、突破されてしまった時のために、データベースのユーザー権限を最小限にしておきましょう。「全データを消去できる権限」をWebサーバーに持たせる必要はありません。「ログイン照会だけできるユーザー」をデータベース内に作成し、アプリにはその権限だけを与えてください。

—

最後に:セキュリティは「終わりなき旅」です

NoSQLインジェクションは、技術の進化とともに手口も巧妙になっています。しかし、本質は「ユーザーからの入力を信用しすぎないこと」という、セキュリティの基本に集約されます。

今日から皆さんのコードを振り返ってみてください。「この入力欄は、本当に想定した形(文字列や数字)で届いているだろうか?」という視点を持つだけで、皆さんはもう立派なセキュリティエンジニアの第一歩を踏み出しています。

泥棒は、鍵のかかっていない家よりも、鍵がかかっていて面倒そうな家を避けるものです。まずは今日学んだ「型チェック」から、ぜひ現場で試してみてくださいね。

それでは、また次の記事でお会いしましょう!

コメント

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