境界線の消失:NoSQLインジェクションが暴く「型安全」の幻想
多くのエンジニアがSQLインジェクション(SQLi)を過去の遺物だと高を括っている間に、我々が守るべきデータ層は「JSON」という名の柔軟な言語に侵食されている。MongoDBをはじめとするNoSQLデータベースは、スキーマレスという自由を手に入れた代償として、ロジックの境界線を曖昧にしてしまった。
今回は、単なる「入力チェックをしましょう」といった教科書的な話はしない。パケット構造の深淵から、アプリケーションの型システムがなぜNoSQLインジェクションを許容してしまうのか、そのメカニズムとアーキテクチャレベルの防衛論を紐解く。
—
1. 攻撃の解剖:演算子という名の「トロイの木馬」
NoSQLインジェクションの本質は、ユーザー入力を単なる「値」としてではなく、「演算子(Operator)」として解釈させてしまう点にある。
例えば、Node.js環境で以下のような認証ロジックを書いたことはないだろうか。
// 脆弱な認証クエリの例
const query = {
username: req.body.username,
password: req.body.password
};
const user = await db.collection(‘users’).findOne(query);
攻撃者が req.body.password に {"$gt": ""} というJSONを投げ込んだ瞬間、クエリは password: { $gt: "" } となる。これは「パスワードが空文字より大きい(=存在する)」という条件に書き換わり、認証が完封される。
なぜこれが防げないのか
この脆弱性の根源は、「入力の型(Type)が期待通りであると盲信していること」にある。プロトコル層ではJSONとしてパースされるが、データベースドライバ層に渡る際、MongoDBのクエリ演算子がそのまま「命令」として評価されてしまう。メモリ上でのオブジェクトのシリアライズ・デシリアライズの過程で、単なる文字列だったはずの入力が、構造化された制御命令へと昇華してしまうのだ。
—
2. 防御のアーキテクチャ:入力の「正規化」から「構造的強制」へ
一般的なバリデーション(if (!req.body.password) ...)では不十分だ。攻撃者は型を偽装する。我々が取るべきは、「入力の強制的な型変換」である。
推奨される防御実装(Mongoose/Joi等の活用)
入力値を、サーバー側で必ず「プリミティブな型」にキャストすること。
// 堅牢な実装の例
const schema = Joi.object({
username: Joi.string().alphanum().required(),
// パスワードは強制的に文字列として扱う。オブジェクトの混入を拒否する
password: Joi.string().required()
});
const { error, value } = schema.validate(req.body);
if (error) throw new Error(“Invalid input structure”);
// この時点でvalue.passwordは確実に文字列であることが保証されている
const user = await db.collection(‘users’).findOne({
username: value.username,
password: value.password
});
—
3. 生成AI時代の新たな脅威:プロンプトインジェクションとの交差点
現在、多くのアプリケーションがLLMとNoSQLを直結させている。ここで恐ろしいのは、ユーザーがLLMに対して入力した「自然言語による指示」が、結果としてNoSQLへのクエリとして生成されるケースだ。
LLMの出力が直接データベースドライバに渡るアーキテクチャは、極めて危険な「バックドア」を自ら開いているに等しい。
ガードレイルの設計指針
1. 中間層の分離: LLMが生成するクエリは、必ず一度「抽象構文木(AST)」として解析し、許可された演算子($eqのみ等)以外を物理的に排除するフィルタリング層を通すこと。
2. 特権分離: アプリケーションとデータベースの接続には、最小権限の原則を適用した専用のサービスアカウントを割り当てる。たとえインジェクションが成功しても、$where 演算子によるJavaScript実行や、コレクションの削除といった破壊的動作をOSレベルで遮断する。
—
4. 監査の観点:低レイヤからの検知
チーフホワイトハッカーとしてインフラを監査する際、私はアプリケーションログよりも「ドライバレベルのクエリ発行ログ」を注視する。
- 異常なクエリ構造の検知:
findOneに渡されるクエリオブジェクトの中に$gt,$ne,$regexなどの演算子が含まれていないか。これらをSIEM(SplunkやDatadog等)でアラート監視する。 - 通信プロトコル解析: MongoDBのWire Protocolをパケットキャプチャし、クエリのバイナリ構造を解析する。通常は期待されない複雑なネスト構造を持つクエリは、ほぼ間違いなく攻撃の兆候だ。
—
最後に:防御は「信頼の破壊」から始まる
NoSQLインジェクションを防ぐ唯一の道は、「外部からの入力は、いかなる形式(JSONであれバイナリであれ)であっても、プログラムの制御フローを乗っ取る意図を持つ可能性がある」と前提することだ。
クリーンなコードを書くことも重要だが、アーキテクトとしては「データがどこで解釈され、どこで命令になるのか」という、メモリと通信の境界線を冷徹に見極める必要がある。
型システムを信じるな。バリデーションを過信するな。データがプログラムに変換されるその瞬間に、全ての防壁を設置せよ。それこそが、この混沌としたサイバー空間で生き残る唯一の流儀だ。
コメント