MongoDBの「$where」は時限爆弾だ。今すぐコードを捨てろ。
現場でコードレビューをしていると、未だに「あ、これ便利だから」という理由で $where 演算子を使っているコードに出くわす。言っておくが、それは「自らサーバーの鍵を開けて泥棒を招き入れている」のと同義だ。
SQLインジェクションは耳にタコができるほど聞かされているだろうが、NoSQLの世界、特にMongoDBにおけるJavaScript実行リスクは、意外と「盲点」になっている。今日は、なぜ $where がセキュリティのタブーなのか、そしてどうやってモダンな設計に置き換えるべきか、泥臭い現実を交えて解説する。
—
1. なぜ「$where」が地獄の入り口なのか
MongoDBの $where 演算子は、クエリの一部としてサーバーサイドで任意のJavaScriptコードを実行できる機能だ。直感的に書けるため、複雑な条件分岐をさせたいエンジニアが飛びつきやすい。
しかし、ここにユーザーからの入力をそのまま連結してしまったらどうなるか。攻撃者は JavaScript の構文を悪用し、データベース内の全データを吐き出させたり、最悪の場合、OSコマンドを実行したりする攻撃を仕掛けてくる。
攻撃のPoC(概念実証)
例えば、ユーザーIDで検索するAPIで、こんなコードを書いているとしよう。
// 脆弱な例:ユーザー入力をそのまま文字列連結している
db.users.find({ $where: “this.username == ‘” + userInput + “‘” });
攻撃者が userInput に ' || '1'=='1 を送り込んだらどうなるか。生成されるクエリは this.username == '' || '1'=='1' となり、評価は常に true。全ユーザーの個人情報が、一瞬で攻撃者の手元に渡る。
さらに恐ろしいのは、sleep() や無限ループを仕込んでDoS攻撃を誘発したり、サーバーのメモリを枯渇させたりすることが極めて容易だという点だ。
—
2. 実践:セキュアな設計への切り替え
解決策はシンプルだ。「$where を使うな」。これに尽きる。
MongoDBには、演算子を組み合わせたリッチなクエリ言語が用意されている。JavaScriptを動かす必要など、99.9%のケースで存在しない。
非推奨:JavaScript実行(危険)
// 以前の書き方(絶対NG)
db.collection.find({ $where: “this.price < " + userLimit });
推奨:MongoDB標準演算子を使用(安全)
// 安全な書き方($lt演算子を使う)
db.collection.find({ price: { $lt: userLimit } });
もし、どうしても複雑なロジックが必要なら、アプリケーション層(PHPやPython)でクエリを構築し、データベースには「データ」だけを渡すように設計を変更すべきだ。
—
3. 実装サンプル:バリデーションで防御を固める
コードレベルでの修正に加え、入力値のバリデーションは必須だ。Pythonの Pydantic を使った、堅牢なデータ受け取りの例を紹介する。
from pydantic import BaseModel, Field, validator
ユーザー入力の厳格なスキーマ定義
class UserQuery(BaseModel):
# 型を強制し、文字列連結を防ぐ
limit: int = Field(…, gt=0, lt=1000)
@validator(‘limit’)
def check_limit(cls, v):
# ビジネスロジックに基づいた制限(例:最大1000件まで)
if v > 1000:
raise ValueError(“検索件数が多すぎます”)
return v
データベース操作(安全)
def get_data(limit: int):
# クエリは演算子オブジェクトとして構築する
query = {“price”: {“$lt”: limit}}
return db.collection.find(query)
—
4. インフラレベルでの多層防御(WAF/IAM)
開発者がうっかりミスをしても、それを止めるのがインフラの役割だ。
- WAFでのシグネチャ検知:
AWS WAFなどを使用している場合、「JavaScriptのインジェクション」を狙ったキーワード($where, sleep, this., function など)をリクエストボディから検知するルールを適用しておくこと。
- MongoDBの権限最小化:
アプリケーションから接続するDBユーザーには、eval 権限を絶対に与えてはいけない。dbAdmin や root でアプリを動かすのは、金庫の鍵をドアノブにぶら下げているのと同じだ。
MongoDBの権限設定の考え方(最小権限の原則)
アプリ用ユーザーには必要なコレクションへの read/write のみ許可し、
管理系コマンド(evalやwhereの実行を伴うもの)は拒否する。
roles:
- role: “readWrite”
db: “production_db”
—
最後に:セキュリティは「作法」だ
インジェクション攻撃を防ぐのは、高度な魔法ではない。「ユーザーの入力をコードとして扱わない」という、エンジニアとしての基本的な作法を守れるかどうかの問題だ。
「便利だから」という甘い誘惑に負けて $where を使うのは、エンジニアの怠慢だ。コードは常に「最悪の攻撃者が操作している」という前提で書け。それが、君が担当するプロダクトと、そこに集まるユーザーの信頼を守るための唯一の道だ。
現場でまた $where を見つけたら、容赦なくその場でリファクタリングしてくれ。それがチームの守りを固める最短のルートになるはずだ。
コメント