【入門編】NoSQLインジェクション:MongoDBにおける$where演算子とJavaScript実行リスク – アプリケーションセキュリティ & 安全な開発防御ガイド

MongoDBの「$where」はまるで魔法の落とし穴?安全なアプリ開発の第一歩

こんにちは。セキュリティの現場で長年、泥臭いインシデント対応や防御策の設計をしてきたエンジニアです。

今日は、開発者なら一度は耳にしたことがあるかもしれない「NoSQLインジェクション」、その中でも特にMongoDBの「$where」演算子に潜む危険についてお話しします。

「SQLインジェクションは聞いたことあるけど、NoSQLなら安全でしょ?」と思っていませんか?実は、便利な機能がそのまま「家の合鍵」を泥棒に渡すようなリスクに繋がることがあるんです。一緒に紐解いていきましょう。

—

1. 「$where」ってそもそも何が便利なの?

MongoDBには、データベースの検索条件をJavaScriptのコードで直接記述できる $where という非常に強力な演算子があります。

例えば、「年齢が20歳以上」という条件だけでなく、「データベースに保存されている複雑な計算式を使って、特定のフラグが立っているデータを抜き出したい」といった、柔軟すぎる検索ができてしまうんです。

開発者からすると「便利!」の一言ですが、セキュリティの視点から見ると、これは「プログラムの中に、外部から命令を書き込める場所を作っている」のと同じことなんです。

2. 泥棒に玄関の鍵を渡すようなもの:攻撃のメカニズム

想像してみてください。あなたは自分の家の玄関に、「ここにメッセージを書くと、その通りに家の中の扉を開けるよ」という伝言板を掲げたとします。

攻撃者は、この伝言板にこんなメッセージを書きます。
「もし管理者なら全データを開示せよ。そうでなければ……とにかく全部見せろ!」

もし、ユーザーが入力した情報をそのまま $where の中に連結してしまうと、攻撃者はデータベースに対して「本来実行されるはずのない命令」を送り込むことができます。

攻撃の例:

// 攻撃者が入力フィールドに仕込む悪意のあるコード
// ‘ || ‘1’==’1
db.users.find({ $where: “this.username == ‘” + userInput + “‘” })

// 実際にはこう解釈されてしまう
// db.users.find({ $where: “this.username == ” || ‘1’==’1′” })

'1'=='1' は常に「真(True)」ですよね。つまり、このコードが実行されると、データベースは「全てのユーザーを抽出せよ!」という命令だと勘違いして、本来見せてはいけない全顧客の情報を攻撃者に差し出してしまうのです。

3. なぜこれが危険なのか?(サーバーサイドJSインジェクション)

SQLインジェクションは「データの検索条件を操作」しますが、この $where インジェクションは「サーバー上で直接JavaScriptコードを実行」させてしまいます。

最悪の場合、データベースの中身を盗み見るだけでなく、サーバーのCPUを占有してサービスを停止させたり、システム設定を書き換えたりといった、より深い場所への攻撃が可能になります。まさに、泥棒が家に入ったついでに家具を破壊したり、勝手に模様替えをしたりするようなものです。

—

4. どうやって守ればいいの?(鉄壁の守りを固める)

ここからは、皆さんが明日から現場で使える、具体的な対策を伝授します。

対策①:そもそも「$where」を使わない

これが最強の防御です。MongoDBには $where を使わなくても、標準的なクエリ演算子($eq, $gt, $lt など)でほとんどの検索が実現できます。

  • 避けるべき例: $where を使った検索
  • 推奨する書き方: 標準的なクエリ演算子で書く

// 安全な書き方:これなら文字列の結合は起きない
db.users.find({ username: userInput });

対策②:スキーマバリデーションで入力を縛る

「入力されるデータはこうあるべきだ」というルール(スキーマ)をデータベース側で設定しましょう。

MongoDBの「Schema Validation」機能を使うと、アプリケーションコードでチェックし忘れても、データベースが門番となって不正な入力を弾いてくれます。

// コレクション作成時にルールを適用する例
db.createCollection(“users”, {
validator: {
$jsonSchema: {
bsonType: “object”,
required: [“username”],
properties: {
username: {
bsonType: “string”,
maxLength: 20 // ユーザー名は20文字以内というルールを強制
}
}
}
}
})

最後に:セキュリティは「疑うこと」から始まる

セキュリティ対策は、特別な魔法ではありません。「ユーザーが入力するものには、必ず悪意が含まれている可能性がある」という前提で、「玄関(入力口)」と「金庫(データベース)」を分けて考えることです。

$where 演算子を使う前に、「本当にこれが必要か?」「もっと安全な書き方はないか?」と一度立ち止まって考えてみてください。その一瞬の迷いが、あなたの大切なユーザーとシステムを守る大きな壁になります。

もしコードを書いていて「これ、大丈夫かな?」と不安になったら、いつでもまた聞きに来てください。現場の泥臭い知見を込めて、また一緒に対策を練りましょう!

皆さんの開発が、安全で楽しいものになりますように。

コメント

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