SQL だけじゃないインジェクション:OS コマンド・NoSQL・HTTP ヘッダーの防ぎ方(IPA・OWASP)

PR
SQL だけじゃないインジェクション:OS コマンド・NoSQL・HTTP ヘッダーの防ぎ方(IPA・OWASP)
この記事は約35分で読めます。

SQL 以外にも、インジェクションは起きます。サーバーから外部のコマンドを呼ぶ、NoSQL に問い合わせる、HTTP のヘッダーやメールを組み立てる、といった場面では、利用者の入力が「データ」のつもりで渡したのに、相手には「命令」として読まれることがあります。IPA(情報処理推進機構)と OWASP(Web アプリの安全を扱う非営利の団体)の文書を合わせると、OS コマンドの防ぎ方の順は、まず「コマンドを呼ばない」、呼ぶなら「シェルを通さずに引数を分けて渡し、値を検証する」です。

シェルは、キーボードで打ったコマンドの文字列を読み取って、実行する OS の仕組みです。引数(ひきすう)は、コマンドに渡す値のことです。この記事は、サーバーから外部のコマンドを呼ぶアプリ、NoSQL を使うアプリ、ヘッダーやメールを組み立てるアプリを作り始めた人向けです。中心は OS コマンドで、NoSQL は 1 節、HTTP ヘッダーとメールヘッダーは短く扱います(2026年10月時点)。

手元の出力は、macOS 14.4.1・Python 3.12.3・Node.js 24.13.1 と 21.7.1 で取りました。シェルは /bin/sh(GNU bash 3.2.57)、wc は macOS のものです。Linux や Windows では見ていないので、エラーの文面は違うことがあります。MongoDB は手元に無く、動かしていません。

この記事には、攻撃に使える入力は載せていません。仕組みは、空白を含むふつうのファイル名 my report.txt と、先頭がハイフンのふつうのファイル名 -notes.txt で見ます。SQL インジェクションの記事で O'Reilly を使ったのと同じ見せ方です。

この記事でわかること
  • SQL 以外のインジェクションも、「データが命令として読まれる」という考え方は同じであること
  • OS コマンド・インジェクションが起きる仕組みと、IPA と OWASP の防ぎ方の順(呼ばない → シェルを通さず引数を分ける → 値を検証する)
  • Python(subprocess)と Node.js(exec と execFile)での書き方と、shell=True・shlex.quote・shell: true の注意
  • 2026年の JPCERT/CC の注意喚起にある OS コマンド・インジェクションの 2 件(FileZen・SiteGuard Server Edition)と、書かれていないこと
  • NoSQL(MongoDB)で、値の型を確かめる理由。HTTP ヘッダー・メールヘッダーでは、ヘッダーの値に改行を入れさせないこと
  1. SQL 以外にもあるインジェクションとは?
  2. OS コマンド・インジェクションとは?何が起きる?
  3. 手元で見る:文字列をつなぐと、ふつうのファイル名が割れる
    1. 試すのは自分の手元だけ
    2. Python:文字列をつなぐ書き方と、リストで渡す書き方
    3. Node.js:exec と execFile
  4. OS コマンドの防ぎ方:呼ばない → 引数を分ける → 値を検証する
    1. いちばん:コマンドを呼ばない
    2. 呼ぶなら:シェルを通さず、コマンドと引数を分ける
    3. 「シェルを通す」に戻してしまう書き方
    4. リストで渡しても、ハイフンで始まる値はオプションと読まれる
    5. 値を検証する:IPA と OWASP で置き場所が違う
    6. 権限を絞る
  5. 2026年に、OS コマンド・インジェクションの注意喚起はあった?(2026年10月時点)
    1. FileZen の件(JPCERT-AT-2026-0004)
    2. SiteGuard Server Edition の件(JPCERT-AT-2026-0032)
    3. この 2 件から、言えないこと
  6. NoSQL インジェクションとは?値の型を確かめて防ぐ
    1. NoSQL でも、インジェクションは起きる
    2. MongoDB の「問題にならない」は、どこまでの話?
    3. 防ぎ方:型を確かめてから、クエリを組む
  7. HTTP ヘッダーとメールヘッダーのインジェクション(短く)
    1. HTTP ヘッダー・インジェクション
    2. メールヘッダー・インジェクション
  8. よくある質問
    1. FileZen や SiteGuard の件は、文字列をつないでシェルに渡したことが原因でしたか?
    2. NoSQL の守り方は、MongoDB 以外のデータベースでも同じですか?
    3. 他人のサービスで、入力を試してもいいですか?
  9. まとめ
  10. 参考資料

SQL 以外にもあるインジェクションとは?

OWASP Top 10:2025(Web アプリの危ない点を並べた一覧の 2025 年版)の「A05:2025 Injection」は、インジェクションを次のように説明しています。信用できない利用者の入力が、インタープリタ(命令を読み取って実行する仕組み。ブラウザー・データベース・コマンドラインなど)に送られ、入力の一部が命令として実行されてしまう、アプリの欠陥です。SQL・NoSQL・OS コマンドなどがあり、「考え方はどのインタープリタでも同じ」と書かれています。

この記事で扱うのは、そのうち OS コマンドと NoSQL、そして IPA が別の章で扱っている HTTP ヘッダーとメールヘッダーです。

差し込まれる先IPA「安全なウェブサイトの作り方」の章この記事の節
OS コマンド1.2 OS コマンド・インジェクション2〜5 つめの節(中心)
NoSQL第1章の 11 項目には無いNoSQL の節
HTTP のヘッダー1.7 HTTP ヘッダ・インジェクションヘッダーの節
メールのヘッダー1.8 メールヘッダ・インジェクションヘッダーの節

SQL はSQL インジェクションの記事、XSS(Web ページに命令を紛れ込ませる攻撃)はXSS と CSRF の記事で扱っています。

防ぎ方の方針は、SQL と同じです。A05 は、いちばんの防ぎ方を「データを、命令やクエリから分けておくこと」とし、望ましいのは安全な API(インタープリタを使わない、値を別に渡せる口を使う、ORM に移る)だと書いています。分けられないときの許可リストの入力検証は「完全な守りではない」とし、検証やエスケープは「間違えやすい」とも書いています。

OWASP の Injection Prevention Cheat Sheet(対策をまとめた文書)は、この欠陥は、コードを読めば見つけやすいが、テストでは見つけにくい、とも書いています。

OS コマンド・インジェクションとは?何が起きる?

IPA は、次のように説明しています。Web アプリによっては、外部からの攻撃で、Web サーバーの OS コマンドを不正に実行されてしまう問題があります。この問題が「OS コマンド・インジェクションの脆弱性」で、それを悪用した攻撃が「OS コマンド・インジェクション攻撃」です。

OWASP のテスト手引き(WSTG)は、利用者の入力が検証も無害化もされずに OS のコマンドへ直に渡されると起きる、と書いています。サーバーで任意のコマンドを実行されて、不正なデータの閲覧や、サーバーの乗っ取りにつながりうる、という説明です。

IPA は、運営している組織やサイトの性質を問わず、外部のプログラムを呼び出せる関数などを使う Web アプリに注意が必要だと書いています。IPA の例は、Perl の open()・system()、PHP の exec()・shell_exec()・popen() などで、Python や Node.js は出てきません。この 2 つは、あとの節で公式の文書を見ます。

IPA が挙げる被害は、サーバー内のファイルの閲覧・改ざん・削除、不正なシステム操作(アカウントの追加など)、不正なプログラムのダウンロードと実行、他のシステムへの攻撃の踏み台(迷惑メールの送信など)です。

OWASP は、乗っ取られたプロセスが「最小権限の原則」に従っておらず、攻撃者のコマンドが特別な権限で動いてしまうと、被害が大きくなる、とも書いています。最小権限の原則は、仕事に要る最小限の権限だけを持たせる考え方です。

OWASP は、OS コマンド・インジェクションはすべて、引数のインジェクションでもある、と書いています。利用者の入力が、コマンドの引数として渡されうるからです。この話は、あとの節で -notes.txt という無害なファイル名で見ます。

手元で見る:文字列をつなぐと、ふつうのファイル名が割れる

ここからは、手元の出力で仕組みを見ます。題材は、「フォームで受け取ったファイル名のファイルの大きさを、wc -c コマンドで数えて返す」機能です。wc -c は、ファイルの大きさをバイト(データの大きさの単位)で数えるコマンドです。手元には、中身が hello と改行だけ(6 バイト)の my report.txt というファイルを用意しました。空白を含む、ふつうのファイル名です。

試すのは自分の手元だけ

試すのは、自分で作った手元のプログラムだけです。他人のサービスで入力を試してはいけません。不正アクセス行為の禁止等に関する法律(e-Gov 法令検索で読めます)は、第三条で「何人も、不正アクセス行為をしてはならない。」と定めています。定義は第二条第四項にあります。他人のサービスで入力を試すことがこれに当たるかどうかは、この記事では書きません。解釈を示した公的な文を、開いていないからです。

Python:文字列をつなぐ書き方と、リストで渡す書き方

まず、Python の subprocess(外部のコマンドを動かす標準の部品)です。文字列をつないで、shell=True(シェルを通す指定)で渡す書き方と、コマンドと引数をリストにして渡す書き方を比べます。

import subprocess

name = "my report.txt"  # フォームから来たファイル名のつもり(無害な値)
with open(name, "w") as f:
    f.write("hello\n")

# 1) 文字列をつないでシェルに渡す(よくない書き方)
cmd = "wc -c " + name
print("1) shell=True:", repr(cmd))
r = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print("   returncode:", r.returncode)
print("   stdout:", repr(r.stdout))
print("   stderr:", repr(r.stderr))

# 2) リストで渡す(シェルを通さない)
args = ["wc", "-c", name]
print("2) list:", args)
r = subprocess.run(args, capture_output=True, text=True)
print("   returncode:", r.returncode)
print("   stdout:", repr(r.stdout))
print("   stderr:", repr(r.stderr))

(流したファイルには、このあと出てくる 3) の shlex.quote の例も入っています。)出力は次のとおりです。

1) shell=True: 'wc -c my report.txt'
   returncode: 1
   stdout: '       0 total\n'
   stderr: 'wc: my: open: No such file or directory\nwc: report.txt: open: No such file or directory\n'
2) list: ['wc', '-c', 'my report.txt']
   returncode: 0
   stdout: '       6 my report.txt\n'
   stderr: ''

1) は失敗しました。wc は my と report.txt という 2 つのファイルを数えようとして、どちらも開けませんでした(0 total は、その合計です)。シェルが、空白を引数の区切りとして読んで、1 つのファイル名を 2 つに割ったからです。Python の文書も、shell=True のとき、文字列は「シェルの入力欄に打つのとまったく同じ形」でなければならず、空白のあるファイル名のクォート(引用符で囲むこと)も含む、と書いています。2) はリストで渡したので、1 つのファイル名のまま届き、6 バイトが返りました。

これは攻撃ではなく、ふつうのファイル名で動かなくなる例です。ただ、シェルが特別に読む文字は空白だけではありません。OWASP の Cheat Sheet は、シェルのメタ文字(シェルが特別な意味で読む文字)として、アンパサンド・縦棒・セミコロン・ドル記号・大なり・小なり・バッククォート・バックスラッシュ・感嘆符・一重引用符・二重引用符・左右のかっこの 13 個を挙げ、空白も同じ側に並べています。この記事では、これらを使った入力は載せません。空白だけで、同じ仕組みが見えるからです。

Node.js:exec と execFile

Node.js では、child_process の exec がシェルを起動してコマンドを動かし、execFile は既定ではシェルを起動せず、コマンドを直に起動します。

import { exec, execFile } from 'node:child_process';
import { writeFileSync } from 'node:fs';

const name = 'my report.txt'; // フォームから来たファイル名のつもり(無害な値)
writeFileSync(name, 'hello\n');

const show = (label, err, stdout, stderr) => {
  console.log(label);
  console.log('   error.code:', err ? err.code : null);
  console.log('   stdout:', JSON.stringify(stdout));
  console.log('   stderr:', JSON.stringify(stderr));
};

// 1) exec: 文字列をつないでシェルに渡す(よくない書き方)
exec('wc -c ' + name, (err, stdout, stderr) => {
  show('1) exec: ' + JSON.stringify('wc -c ' + name), err, stdout, stderr);

  // 2) execFile: プログラムと引数を分けて渡す(シェルを通さない)
  execFile('wc', ['-c', name], (err2, stdout2, stderr2) => {
    show('2) execFile: ' + JSON.stringify(['wc', '-c', name]), err2, stdout2, stderr2);
  });
});

Node.js 24.13.1 での出力は、次のとおりです。21.7.1 でも、1 字も違わない同じ出力でした。

1) exec: "wc -c my report.txt"
   error.code: 1
   stdout: "       0 total\n"
   stderr: "wc: my: open: No such file or directory\nwc: report.txt: open: No such file or directory\n"
2) execFile: ["wc","-c","my report.txt"]
   error.code: null
   stdout: "       6 my report.txt\n"
   stderr: ""

exec は Python の 1) と同じく 2 つのファイルと読まれて失敗し、execFile は 6 バイトを返しました。Node.js の文書は、加工していない利用者の入力を exec に渡してはならない、シェルのメタ文字を含む入力で任意のコマンドを実行されうる、とはっきり書いています。

OS コマンドの防ぎ方:呼ばない → 引数を分ける → 値を検証する

IPA と OWASP の文書を合わせると、防ぎ方の順番は、この節の見出しのとおりです。

いちばん:コマンドを呼ばない

OWASP の Cheat Sheet は、いちばんの防ぎ方を「OS のコマンドを直に呼ばないこと」としています。言語に組み込みのライブラリ関数は、決められた仕事以外をさせられないので、よい代わりになります(例:system("mkdir /dir_name") の代わりに mkdir())。

IPA の根本的解決は、線の引き方が少し違い、「シェルを起動できる言語の機能を使わない」です。たとえば Perl の open 関数は、ファイルのパスに「|」(パイプ)を使うと OS コマンドを実行できるので、外からの入力を渡すのは危険で、シェルを起動しない sysopen で代えられます。

さきほどの題材(ファイルの大きさを知りたい)なら、wc -c を呼ぶ必要はありません。Python の os.path.getsize は、パスの大きさをバイトで返す関数です(ファイルが無いか読めなければ OSError)。

$ python3 -c 'import os; print(os.path.getsize("my report.txt"))'
6

呼ぶなら:シェルを通さず、コマンドと引数を分ける

利用者の入力を含むコマンドがどうしても避けられないとき、OWASP は 2 層で守るよう書いています。1 層目は、データと命令を自動で分ける仕組み(パラメーター化)です。この記事では、リストで渡す subprocess と execFile がこれに当たるものとして見ます(OWASP の例は Java の ProcessBuilder です)。2 層目の入力の検証は、あとの小節で見ます。

シェルを通す(1 つの文字列で渡す)シェルを通さない(コマンドと引数を分ける)
Pythonsubprocess.run("…", shell=True)subprocess.run(["wc", "-c", name])
Node.jsexec("…")execFile("wc", ["-c", name])

Python の文書は、subprocess は自分からシェルを呼ばないので、シェルのメタ文字を含むすべての文字を、子のプロセスに安全に渡せる、と書いています。args はリストで渡すのが望ましく、そうするとモジュールが必要なエスケープやクォートをしてくれます。実行するプログラムは、フルパス(/usr/bin/wc のような省略しない場所の指定)にするのがいちばん確かです。

Node.js の文書は、execFile はシェルを起動せず、指定した実行ファイルを直に起動する、と書いています。その代わり、入出力のリダイレクト(> などで出力先を切り替えること)や、ワイルドカード展開(*.txt を該当するファイル名の並びに置き換えること)は使えません。

「シェルを通す」に戻してしまう書き方

Python の shell=True:Python の文書は、シェルを明示して呼ぶなら、すべての空白とメタ文字を正しくクォートするのはアプリの責任だ、と書いています。

Python の shlex.quote:文字列を、シェルの 1 つのトークン(区切りの 1 まとまり)として安全に使える形にする関数です。さきほどの 1) を、これでつなぐ書き方に直すと、次のようになりました。

import shlex

cmd = "wc -c " + shlex.quote(name)
print("3) shell=True + shlex.quote:", repr(cmd))
r = subprocess.run(cmd, shell=True, capture_output=True, text=True)
3) shell=True + shlex.quote: "wc -c 'my report.txt'"
   returncode: 0
   stdout: '       6 my report.txt\n'
   stderr: ''

コマンドは wc -c 'my report.txt' と、ファイル名が引用符で囲まれて、6 バイトが返りました。ただし、主役にはしません。Python の文書は、shlex.quote を「リストを使えないときのため」のものとし、Unix のシェル用だけに作られていて、Windows など別のシェルでは正しいと限らず、コマンド・インジェクションを招きうる、と書いています。そして、引数をリストで渡す関数を考えるよう勧めています。OWASP も、OS ごとのエスケープ(PHP の escapeshellarg() など)を守りの 2 番目に置き、コマンド・インジェクションは防げても、コマンドに 1 つの引数は渡せてしまう、と書いています。

Node.js の shell: true:execFile にオプション { shell: true } を付けて、引数を配列で渡すと、配列の値はエスケープされず、空白でつながれるだけです。手元で確かめました。

execFile('wc', ['-c', name], { shell: true }, (err, stdout, stderr) => {
  console.log('3) execFile + shell:true: ' + JSON.stringify(['wc', '-c', name]));
  console.log('   error.code:', err ? err.code : null);
  console.log('   stdout:', JSON.stringify(stdout));
  console.log('   stderr:', JSON.stringify(stderr));
});

Node.js 24.13.1 の出力です。警告の行の先頭のプロセス番号と、続く案内の 1 行は省きました。

[DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.
3) execFile + shell:true: ["wc","-c","my report.txt"]
   error.code: 1
   stdout: "       0 total\n"
   stderr: "wc: my: open: No such file or directory\nwc: report.txt: open: No such file or directory\n"

配列で渡したのに、exec と同じようにファイル名が 2 つに割れて失敗しました。Node.js 21.7.1 では警告は出ず、同じく失敗しました。Node.js の文書は、これを DEP0190(非推奨の番号)として、v24.0.0 で実行時の非推奨(警告が出る)にした、と書いています。値がエスケープされず、空白でつながれるだけなので、シェル・インジェクションを招きうる、という理由です。

リストで渡しても、ハイフンで始まる値はオプションと読まれる

シェルを通さなくても、もう 1 つ気をつけることがあります。オプションは、-c のように先頭にハイフン(-)を付けて、コマンドの動きを変える指定です。コマンドによっては、引数の並びの中でハイフンで始まるものを、オプションと読みます。先頭がハイフンのふつうのファイル名 -notes.txt で確かめました。

import subprocess

name = "-notes.txt"  # 先頭がハイフンの、ふつうのファイル名(無害な値)
with open(name, "w") as f:
    f.write("hello\n")

# リストで渡しても、先頭がハイフンだと wc はオプションとして読む
for args in (["wc", "-c", name], ["wc", "-c", "--", name]):
    r = subprocess.run(args, capture_output=True, text=True)
    print(args)
    print("   returncode:", r.returncode)
    print("   stdout:", repr(r.stdout))
    print("   stderr:", repr(r.stderr))
['wc', '-c', '-notes.txt']
   returncode: 1
   stdout: ''
   stderr: 'wc: illegal option -- n\nusage: wc [-Lclmw] [file ...]\n'
['wc', '-c', '--', '-notes.txt']
   returncode: 0
   stdout: '       6 -notes.txt\n'
   stderr: ''

リストで渡したのに、wc は -notes.txt の -n をオプションと読み、失敗しました。-- を挟むと、それより後ろはファイル名として読まれて、6 バイトが返りました。このエラーの文面は macOS の wc のものです。GNU(Linux)の wc は見ていません。

OWASP が「すべて引数のインジェクションでもある」と書いているのは、この仕組みの延長にある、というのがこの記事の読み方です。対策として OWASP は、対応しているコマンドなら -- でオプションの読み取りを終わらせる、と書いています(対応していないコマンドもあります)。また、引数を分けても、引数の検証の代わりにはならない、とも書いています。

値を検証する:IPA と OWASP で置き場所が違う

分けて渡したうえで、値の検証をします。IPA の保険的対策は、引数を作るすべての変数をチェックして、あらかじめ許可した処理だけを実行することです。勧めるのは「ホワイトリスト方式」(許可リスト方式)で、許可する文字の組み合わせを洗い出し、それ以外は引数へ渡さずに処理を中止します。数値のはずなら、数字だけの文字列かを調べます。悪用される記号(「|」「<」「>」など)を洗い出す「ブラックリスト方式」は、漏れが出うるので、お勧めできないと書かれています。

OWASP は、同じ検証を、パラメーター化の次の「2 層目」に置いています。コマンドは、許可したコマンドの一覧と照らし、引数は、許可リストか、許す文字と最大の長さを決めた正規表現で調べます。その正規表現には、メタ文字と空白を入れません。例は、小文字と数字だけ・3〜10 文字の ^[a-z0-9]{3,10}$ です。この例だと、my report.txt は通りません(空白とドットを含み、13 文字で長さも超えます)。何を許すかはアプリごとに決めますが、OWASP は、空白は正規表現に入れないと書いています。

検証の置き場所は、IPA では保険的対策、OWASP では 2 層目で、A05 は「完全な守りではない」と書いています。WSTG は、拒む記号の一覧は手だが全部を思いつくのは難しい、と書いています。どの文書も「許可リストのほうがよい」では一致し、「検証だけで足りる」と書く文書は、ありません。この記事の順番(呼ばない → 引数を分ける → 検証する)は、これと合わせたものです。

権限を絞る

OWASP は、追加の守りとして、アプリを仕事に要る最小の権限で動かし、できるなら、1 つの仕事だけに使う権限を絞った別のアカウントを作るよう書いています。DB の権限を絞る考え方は、SQL インジェクションの記事の権限の節にあります。

2026年に、OS コマンド・インジェクションの注意喚起はあった?(2026年10月時点)

JPCERT/CC(日本のセキュリティの情報を扱う組織)の 2026 年の注意喚起は、2026年10月21日に一覧を開いた時点で 32 件あります。そのうち、題に「OS コマンドインジェクション」がある注意喚起は、FileZen と SiteGuard Server Edition の 2 件です。「SQL インジェクション」が題にある 2 件は、SQL インジェクションの記事で扱っています。

どちらも、開発者が悪用を確認している、と JPCERT/CC は伝えています。ここでは、JPCERT/CC と JVN(日本で使われているソフトウェアなどの脆弱性対策情報のデータベース)に書かれていることだけを並べます。

FileZen の件(JPCERT-AT-2026-0004)

  • JPCERT/CC の注意喚起は 2026年2月13日付で、FileZen の OS コマンドインジェクションの脆弱性(CVE-2026-25108)です
  • 悪用されると、製品にログオンした利用者が、任意の OS コマンドを実行する可能性があります。開発者は、悪用を確認しているとのことです
  • JVN(JVN#84622767)は、弱点の種類を OS コマンドインジェクション(CWE-78)とし、ログインしている利用者が細工した HTTP リクエストを送ると、任意の OS コマンドを実行されうる、と書いています。CVSS 4.0(脆弱性の深刻さを点数にした指標)の基本値は 8.7 です。FileZen S は影響を受けません
  • 影響を受けるのは「FileZen ウイルスチェックオプション」が有効なときで、直った版は FileZen V5.0.11 です。V4.x 系のファームウェア(機器に組み込むソフト)の提供は終わっていて、V5.0.11 に上げるよう書かれています
  • 注意喚起は、開発者が対策を 1月13日にリリースしたものの、注意喚起の時点でも、影響を受けうる製品の国内での利用を確認している、と書いています。機器が侵害されていないかの調査も、検討するよう書かれています
  • 攻撃を受けた可能性があるときは、攻撃者が有効なアカウント情報を手に入れている可能性があるため、アップデートに加えて、全利用者のパスワード変更を検討するよう勧められています

SiteGuard Server Edition の件(JPCERT-AT-2026-0032)

  • JPCERT/CC の注意喚起は 2026年10月14日付で、SiteGuard Server Edition の OS コマンドインジェクションの脆弱性(CVE-2026-106931)です
  • 悪用されると、製品が動いているサーバー上で、root または SYSTEM の権限で、任意の OS コマンドが実行される可能性があります。開発者は、悪用を確認しているとのことです。JPCERT/CC は、この製品が国内で広く使われていることを確認しています
  • JVN(JVN#88474777)によると、SiteGuard Server Edition は、Web サーバーに組み込んで使う WAF(Web アプリへの攻撃を見張る製品)です。脆弱性は統計機能にあり、種類は OS コマンドインジェクション(CWE-78)、CVSS 4.0 の基本値は 8.5 です
  • 影響を受けるのは、既定で有効な統計機能が有効なときで、統計機能を無効にすれば避けられます
  • 直った版は、Apache 版・Nginx 版・IIS 版が Ver8.00 Update5、FreeBSD の Apache 版・Nginx 版が Ver7.00 Update6 です。上げにくい利用者向けに、統計処理の対象ファイルを差し替えるパッチも提供されています
  • アップデートの注意:直った版に上げると、侵害の痕跡が消えるおそれがあると、開発者が報告しています。管理画面で「診断情報」を取得して、攻撃の痕跡を残してから上げるよう、勧められています

この 2 件から、言えないこと

  • コードのどこで、どうコマンドを組み立てていたかは、どの文書にも書かれていません。 「文字列をつないでシェルに渡していたのが原因」とは書かれていないので、この記事も書きません。my report.txt の例は、仕組みを説明するためのもので、2 件を再現したものではありません
  • 被害を受けた組織や件数は、どの文書にも書かれていません
  • SiteGuard の件が、ネットワークの外から届く攻撃かどうかは、書きません。JPCERT/CC の文は「製品が動作するサーバー上で、root または SYSTEM の権限で、任意の OS コマンドが実行される可能性」までです
  • 「root または SYSTEM の権限で動いていたから被害が大きい」とも、書きません。どの文書も、そのようなつながりを書いていません。最小権限(先ほどの「権限を絞る」)とは並べて読めますが、因果としては読まないでください

自分で使っている製品に、同じ種類の告知が出たときは、提供元と JPCERT/CC の最新の情報を確認し、直った版に上げます。SiteGuard の件のように、上げる前の手順が示されることもあるので、告知で確かめてください。

NoSQL インジェクションとは?値の型を確かめて防ぐ

NoSQL は、SQL の書き方を使わないデータベースの総称です。MongoDB が代表的な例で、OWASP のテスト手引き(WSTG)も、例は MongoDB で書いています。

JPCERT/CC の注意喚起 JPCERT-AT-2026-0030 を読み解いた記事で見たとおり、2026年10月の注意喚起は、画面ではできない内部 API への攻撃の例に、「NoSQL インジェクションによるブラインド探索でアカウント情報を特定する」を挙げています。これ以上の説明はなく、どのデータベースで、どんな手口かは書かれていません。この記事も書きません。全体の点検はWeb 開発のセキュリティの基本の記事にあります。

NoSQL でも、インジェクションは起きる

OWASP の NoSQL Security Cheat Sheet は、NoSQL インジェクションを、信用できない入力から、クエリのオブジェクトやクエリの文字列を安全でない形で組み立てること、と説明しています。WSTG は、SQL の書き方を使っていなくても、インジェクションを受けうると書き、手続き型の言語(命令を順に書く言語)の中で動きうるので、影響は SQL インジェクションより大きくなりうる、とも書いています。

WSTG は、HTML の特別な文字を取り除いても、JSON を使う API への攻撃は防げない(JSON では特別な文字が別だから)、と書いています。NoSQL のデータベースには共通の言葉が無く、ある例がほかに当てはまるとは限らないので、この節は MongoDB の話です。ほかのデータベースは、それぞれの公式の文書で確かめてください。

MongoDB の「問題にならない」は、どこまでの話?

MongoDB の FAQ(よくある質問)には、「SQL やクエリのインジェクションにどう対処しているか」という項目があります。答えは、クライアントのプログラムは、文字列ではなく BSON(MongoDB が使うデータの形式)のオブジェクトとしてクエリを組み立てるので、「伝統的な SQL インジェクションは問題にならない」というものです。特別な文字を含んでも、クエリが何にも当たらないだけで、削除に変えることはできない、とも書かれています。FAQ は「細かい点は下に」と続けています。

この一文だけを読んで「MongoDB はインジェクションの心配が無い」と受け取ってはいけません。同じ MongoDB の Client Libraries Best Practices(クライアントのライブラリを使ってアプリを作るときの指針)と、OWASP は、別の場面を書いています。

  • JSON の文字列を BSON に変える便利な関数の結果を、検証しないまま、クエリ・更新・コマンドに渡すと、攻撃者がその操作の意味を変えられる
  • 文字列が来るはずの欄にオブジェクトを送られると、MongoDB は、その中の欄を、値ではなくクエリの演算子($where や $regex のように $ で始まる、条件や処理を表す指定)として読むことがある。Best Practices の例では、名前で 1 件に絞るはずのフィルターが、意図より多くの文書に当たり、データが余分に見える
  • 信用できない入力をつないで作った JSON の文字列も、同じ危険がある

FAQ の「問題にならない」は、文字列をつなぐ昔ながらの SQL インジェクションの話で、値そのものがオブジェクトのときは別です。送る JSON の形は、この記事では書きません。

防ぎ方:型を確かめてから、クエリを組む

OWASP は、クエリのオブジェクトを組み立てても、信用できない値そのものがオブジェクトなら、演算子の注入は防げない、と書いています。対策は、フィルター(条件の指定)を組む前に、期待する型(文字列などの単一の値)かを確かめ、利用者から来たオブジェクトを、そのまま欄の値として通さないことです。

手元で、この型の確かめを動かしました。データベースにはつながず、JSON.parse(JSON の文字列を JavaScript の値に直す関数)で取り出した name が文字列ならフィルターを組み、そうでなければ断ります。2 つ目の入力は、文字列のつもりの欄に来た、無害なオブジェクトです。

const bodies = [
  '{"name":"Taro"}',                 // ふつうの文字列
  '{"name":{"first":"Taro"}}',       // 文字列のつもりの欄に、オブジェクトが来た
];
for (const body of bodies) {
  const { name } = JSON.parse(body);
  try {
    if (typeof name !== 'string') throw new Error('Invalid name');
    const filter = { name };
    console.log(body, '-> filter', JSON.stringify(filter));
  } catch (e) {
    console.log(body, '-> rejected:', e.message, '(typeof', typeof name + ')');
  }
}

Node.js 21.7.1 での出力です。

{"name":"Taro"} -> filter {"name":"Taro"}
{"name":{"first":"Taro"}} -> rejected: Invalid name (typeof object)

文字列は通り、オブジェクトは断られました。手元の確かめは、型を確かめるところまでです。MongoDB にはつないでおらず、演算子が通る・止まるという動きは、手元では見ていません。OWASP は、PyMongo(Python から MongoDB を使う部品)の安全な例も、isinstance(email_input, str) で型を確かめてから find_one に渡す形で示しています(手元では動かしていません)。

型の確かめのほかに、次のことが書かれています。

  • クエリビルダーや型のある文書の API を使う:ビルダーは利用者の値を「パースする文字列の一部」ではなく「値」として扱うので、値がクエリの形を変えられません。SQL のプレースホルダーと同じ考え方です。BSON に変える前に、型を確かめて検証すること、JSON Schema などの検証の層を挟むことも勧められています
  • クライアントから来た生の JSON の断片を、クエリとして実行しない:クライアントが決める演算子($where・$regex・$expr など)は、どうしても要って検証した場合のほかは許さない、と OWASP は書いています
  • 「$ を含むものを拒む」だけにしない:OWASP は、リクエストの本文に $ で始まる語が入っていたら拒む、という検査を、拒む一覧であって演算子の許可リストではない、と書いています。そのうえ、$ で始まるふつうの文字列の値まで拒み、許す欄や型の検証もしない、とも書いています。クエリを組む前に、欄ごとの検証をします
  • 文字列をつないで、クエリを作らない:OWASP の文書は、信用できない入力を文字列でつないでクエリを作り、eval(文字列をコードとして実行する機能)で動かす例を、「絶対にしない」書き方として載せています
  • サーバーで JavaScript を実行する機能を、使わないなら切る:$where・$function・$accumulator・mapReduce は、サーバーで JavaScript を実行します。利用者の入力でこれらの式を作ると、サーバーがその入力をコードとして実行するので、アプリの eval に信用できない入力を渡すのと同じ種類の危険がある、と書かれています。使わないなら、設定の security.javascriptEnabled を false にするか、--noscripting を付けて起動します
  • アプリの接続に、root や管理者の DB アカウントを使わない:用途別(管理用・バックアップ用・読み取り専用・アプリ用)に、別のユーザーを使います

HTTP ヘッダーとメールヘッダーのインジェクション(短く)

どちらも、ヘッダー(名前と値の組を、1 行ずつ並べた部分)の値に、利用者の入力を入れるときの話です。ヘッダーは改行で区切られる作りなので、入力に改行(CR と LF という文字)が入ると、区切りが変わってしまいます。IPA は、どちらも改行コードが原因だと書いています。

HTTP ヘッダー・インジェクション

IPA は、HTTP のレスポンス(サーバーからの応答)のヘッダーの値を、外から渡された値で作る Web アプリに注意を促しています。例は、リダイレクト先の URL を Location ヘッダーに入れる場合や、入力された名前を Set-Cookie ヘッダーに入れる場合です。出力の処理に問題があると、任意のヘッダーの追加、任意の本文の作成、複数のレスポンスの作り出し(「HTTP レスポンス分割」)をされてしまいます。

脅威は、XSS と同じ脅威、任意の Cookie の発行、キャッシュサーバーのキャッシュ汚染です。キャッシュ汚染は、影響を受ける対象が広く、永続的だと書かれています。

IPA の根本的解決は、ヘッダーを直に出力せず、実行環境や言語に用意された、ヘッダー出力用の API を使うことです。ヘッダーの構造は単純ではないので、自分で作らないほうがよい、という理由です。ただし、API が改行を正しく扱わない実行環境もあり、そのときは、修正パッチを当てるか、改行を許さない処理を自分で書きます。保険的対策は、外からの入力のすべてから改行を消すことですが、TEXTAREA(複数行の入力欄)のように改行を含みうる入力では、正しく動かなくなります。

WSTG も、CR と LF を含む入力を拒むか取り除くこと、Location などの URL は URL エンコードすること、生のレスポンスの文字列を自分で組まず、フレームワークのヘッダー設定の関数(setHeader() など)を使うことを挙げています。今の環境は、既定で止めることが多い、とも書かれています。

Node.js の http.validateHeaderValue で、手元で確かめました。res.setHeader() と同じ検証をする関数で、HTTP モジュールは自動でヘッダーを検証するので、前もって呼ぶ必要はありません。ここでは検証だけを取り出して、値は Taro と改行を含む値の 2 つで見ます。

"Taro" -> ok
(改行を含む値) -> ERR_INVALID_CHAR Invalid character in header content ["X-Name"]

2 行目の値は、流したときは JSON の形で表示されましたが、ここでは「改行を含む値」と書き換えました。改行を含む値は、エラー(code は ERR_INVALID_CHAR)で断られました。Node.js 21.7.1 と 24.13.1 で同じ出力でした。IPA の「ヘッダー出力用の API を使う」の、手元での確かめです。

メールヘッダー・インジェクション

IPA は、次のように説明しています。利用者が入力した申し込みやアンケートの内容を、特定のアドレスにメールで送る機能で、実装によっては、外部の利用者が宛先を自由に指定できてしまうことがあります。この脆弱性が「メールヘッダ・インジェクション」で、脅威は「メールの第三者中継」(迷惑メールの送信への悪用)です。「問い合わせページ」や「アンケート」が当てはまります。

原因は、To・Cc・Bcc・Subject(件名)などのヘッダーの内容が外からの入力に依存するとき、改行コードが差し込まれることです。任意のヘッダーの挿入、本文の改変、任意の宛先へのメール送信に悪用されます。

IPA の対策は、次のとおりです。

  • ヘッダーを固定値にして、外からの入力はすべてメール本文に出力する(根本的解決)
  • 固定値にできない(件名を変えたい、など)なら、言語のメール送信用 API を使う。API によっては改行の扱いが不適切なので、その場合は、修正された版を使うか、改行を許さない処理を自分で書く
  • HTML の hidden パラメーター(画面に出ない入力欄)に宛先を入れない。IPA は、そうした実装の届出があったとして、「論外」の例に挙げています
  • 保険的対策として、外からの入力のすべてから、改行コードを削除する

Python の標準ライブラリ email で、件名に改行を入れたときの動きを、手元で確かめました。EmailMessage の Subject に、Hello と改行を含む値を、順に入れます。メールは送らず、組み立てただけです。

'Hello' -> ok
(改行を含む値) -> ValueError: Header values may not contain linefeed or carriage return characters

こちらも、2 行目の値は「改行を含む値」と書き換えました。Python 3.12.3 では ValueError で断られました。Python の文書は、email.policy の header_store_parse が、値に CR か LF が入っていれば ValueError を出す、と書いています。3.13 から入った、おかしな折り返しのヘッダーを書かずにエラーにする設定(verify_generated_headers)は、手元が 3.12.3 なので見ていません。

よくある質問

FileZen や SiteGuard の件は、文字列をつないでシェルに渡したことが原因でしたか?

JPCERT/CC と JVN の文書には、書かれていません。書かれているのは、FileZen は製品にログオンした利用者が OS コマンドを実行する可能性があること、SiteGuard Server Edition は統計機能に脆弱性があること、までです。my report.txt の例は、仕組みの説明で、2 件の再現ではありません。

NoSQL の守り方は、MongoDB 以外のデータベースでも同じですか?

同じとは言えません。WSTG は、NoSQL のデータベースには共通の言葉が無く、ある例がほかに当てはまるとは限らない、と書いています。この記事は MongoDB の話で、ほかのデータベースの公式の文書は開いていません。

他人のサービスで、入力を試してもいいですか?

試さないでください。不正アクセス行為の禁止等に関する法律は、第三条で「何人も、不正アクセス行為をしてはならない。」と定めています。他人のサービスで入力を試すことが、これに当たるかどうかは、この記事では書きません(前の「試すのは自分の手元だけ」と同じです)。自分で作ったアプリと、自分で用意したファイルの中だけで試してください。

自分のアプリで確かめること(チェックリスト)
  • 外部のコマンドを呼んでいる所を洗い出し、言語の組み込みの関数やライブラリで代えられないか確かめたか(→「いちばん:コマンドを呼ばない」)
  • コマンドを呼ぶ所で、1 つの文字列で渡さず(shell=True・exec など)、コマンドと引数を分けて渡しているか(→「呼ぶなら:シェルを通さず、コマンドと引数を分ける」)
  • 分けて渡したつもりで、シェルを通す書き方(shell=True・shell: true・shlex.quote だけに頼る)に戻っていないか(→「「シェルを通す」に戻してしまう書き方」)
  • 利用者から来た値がハイフンで始まっても、オプションと読まれないようにしているか。-- に対応しているコマンドか確かめたか(→「リストで渡しても、ハイフンで始まる値はオプションと読まれる」)
  • 渡す値を、許可リストで検証しているか。拒む記号の一覧だけに頼っていないか(→「値を検証する:IPA と OWASP で置き場所が違う」)
  • コマンドを動かすアプリの権限を、仕事に要る最小限に絞っているか(→「権限を絞る」)
  • NoSQL に渡す前に、受け取った値が期待する型か確かめ、利用者から来たオブジェクトを、そのまま欄の値にしていないか。サーバーで JavaScript を実行する機能を、使わないなら切っているか(→「防ぎ方:型を確かめてから、クエリを組む」)
  • ヘッダーやメールの宛先・件名に利用者の入力を入れるとき、言語のヘッダー用・メール送信用の API を使い、改行を含む値を通していないか(→「HTTP ヘッダーとメールヘッダーのインジェクション(短く)」)

まとめ

  • SQL 以外にも、OS コマンド・NoSQL・ヘッダーなどにインジェクションがあります。OWASP は、考え方はどれも同じ(データが命令として読まれる)と書いています
  • OS コマンドは、「コマンドを呼ばない → 呼ぶならシェルを通さず、コマンドと引数を分ける → 渡す値を検証する」の順で防ぎます(IPA と OWASP の文書を合わせた順です)
  • 手元では、文字列をつないでシェルに渡すと、my report.txt が 2 つに割れ、リスト(Python)や execFile(Node.js)で分けて渡すと割れませんでした。コマンドを呼ばずに os.path.getsize を使う方法もあります
  • shell=True・shell: true・shlex.quote は、シェルを通す側の書き方です。Node.js は、shell: true に配列を渡す書き方を DEP0190 として非推奨にしています
  • リストで渡しても、先頭がハイフンの値は、コマンドによってはオプションと読まれます。-- が使えるコマンドでは、-- を挟みます
  • 検証は許可リストが基本ですが、どの文書も「検証だけで足りる」とは書いていません。権限を絞るのは、追加の守りです
  • 2026年の FileZen(直った版 V5.0.11)と SiteGuard Server Edition(直った版 Ver8.00 Update5・Ver7.00 Update6)の件は、原因のコードも、被害の件数も、書かれていません。文字列をつないでいたことが原因とも、root の権限で動いていたから被害が大きいとも、読まないでください
  • NoSQL(MongoDB)は、値の型を確かめてから、クエリを組みます。FAQ の「伝統的な SQL インジェクションは問題にならない」だけを読んで、安心しないでください
  • HTTP ヘッダーとメールヘッダーは、値に改行を入れさせないのが要点です。メールヘッダーは固定値にし、できなければ言語の API を使う。HTTP ヘッダーは言語のヘッダー用の API を使う。

関連する記事として、SQL の場合はSQL インジェクションの記事、API の認可は認証と認可の記事、2026年10月の注意喚起の読み解きはJPCERT/CC の注意喚起 JPCERT-AT-2026-0030 の記事、全体の点検はWeb 開発のセキュリティの基本の記事があります。

参考資料

※この記事は、公式の資料をもとに AI(Claude)で下書きし、2026年10月21日時点の公式の資料と照らして確かめてから公開しています。誤りに気づいたらお問い合わせからお知らせください。

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