安全対策の相談

脆弱性 · 2026年11月2日

OSコマンドインジェクションとは?仕組みと開発者がとるべき対策

OSコマンドインジェクションの仕組みと被害、シェルを起動しない実装、引数の分離と許可リストによる対策を、IPA・OWASP・MITRE・CISAの資料をもとにNode.jsのコード例つきでWeb開発者向けに解説します。

OSコマンドインジェクションは、Webアプリがサーバー上でコマンドを実行するときに、外部からの入力によってコマンドの中身を変えられてしまう脆弱性です。成立すると、アプリの権限でサーバーを操作されるおそれがあります。対策の中心は、コマンドを呼ばずに言語の標準機能やライブラリで処理することです。呼ぶ場合はシェルを介さず、引数を分けて渡し、入力を許可リストで検証します。

OSコマンドインジェクションとは

MITREのCWE-78は、外部の影響を受けた入力でOSコマンドを組み立てるときに、コマンドを変えうる特殊な要素を適切に無害化していない状態と定義しています。IPAの「安全なウェブサイトの作り方」も、外部からの攻撃でWebサーバーのOSコマンドが不正に実行される問題として取り上げています。

CWE-78には、大きく2つの形があります。

CWE-78の2つの形

形内容
決まったプログラムの引数に入力を使うアプリが実行するプログラムは固定だが、入力の中の区切りによって別のコマンドまで実行される
入力で実行するプログラムやコマンドが決まる入力によって、実行するプログラムやコマンド全体が決まってしまう
出典:MITRE CWE-78
9位2025 CWE Top 25でのCWE-78の順位
20件CWE-78に対応づけられた、悪用が確認された脆弱性(KEV)の数
5位OSコマンドインジェクションを含むA05 InjectionのOWASP Top 10:2025での順位
出典:MITRE 2025 CWE Top 25、OWASP Top 10:2025 A05 Injection

CISAとFBIは2024年7月、ネットワーク機器の脆弱性を悪用した攻撃を受けて、OSコマンドインジェクションをなくすよう製品の開発元に呼びかける注意喚起を出しました。Webアプリだけでなく、機器の管理画面にも関わる問題です。

起きる仕組みと被害

問題になるのは、入力を文字列としてつないだコマンドをシェルに渡す実装です。シェルは受け取った文字列全体を解釈するため、データのつもりの入力に含まれる記号が、コマンドの区切りや制御の指示として扱われることがあります。

脆弱性が生まれる処理の流れ

  1. 利用者の入力を受け取るホスト名やファイル名など
  2. 入力をつないでコマンド文字列を作るデータと命令の境界がなくなる
  3. シェルが文字列全体を解釈する入力の一部が命令として扱われる
  4. アプリの権限で実行される想定外の処理まで動く

IPAが挙げる脅威は次のとおりです。アプリの権限でサーバーを操作されるため、影響は情報の閲覧だけにとどまりません。

想定される被害

分類例
情報とファイルサーバー内ファイルの閲覧・改ざん・削除、重要情報の漏えい
システムの操作意図しないシャットダウン、利用者アカウントの追加や変更
不正プログラムマルウェアのダウンロードと実行、バックドアの設置
他への攻撃踏み台としての悪用、サービス不能攻撃、迷惑メールの送信
出典:IPA「安全なウェブサイトの作り方」OSコマンド・インジェクション

IPAは、運営主体やサイトの性質に関係なく、外部プログラムを呼び出せる関数を使っているアプリが対象になると説明しています。

対策1:OSコマンドを呼ばない

OWASPが第一の防御に挙げるのは、OSコマンドを直接呼ばないことです。CISAも、Pythonでフォルダを作るならコマンドではなく os.mkdir() を使う例を示しています。

たとえばホスト名からIPアドレスを調べるだけなら、Node.jsの標準モジュールで済みます。

import { lookup } from "node:dns/promises";

const resolveHost = async ({ hostname }) => {
  const { address } = await lookup(hostname);

  return { address };
};

画像の変換、圧縮、メール送信なども、コマンドの代わりに使えるライブラリがないかを先に確かめます。

対策2:シェルを介さず引数を分ける

コマンドがどうしても必要なら、シェルを起動しない関数で、プログラムと引数を別々に渡します。

避ける書き方

  • 入力をつないだ文字列を実行する
  • シェルを起動する関数を使う
  • 例:Node.jsの exec() に入力を含む文字列を渡す

使う書き方

  • プログラム名をコードに固定する
  • 引数を配列で1つずつ渡す
  • 例:Node.jsの execFile() に引数の配列を渡す
import { execFile } from "node:child_process";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);
const hostnamePattern = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;

const queryDns = async ({ hostname }) => {
  if (hostname.length > 253 || !hostnamePattern.test(hostname)) {
    return { error: "invalid_hostname" };
  }

  const { stdout } = await execFileAsync("/usr/bin/nslookup", [hostname]);

  return { output: stdout };
};

Node.jsの公式ドキュメントは、exec() に無害化していない入力を渡してはならないと警告しています。execFile() は既定ではシェルを起動しません。ただし、shell オプションを有効にすると同じ警告の対象になります。

言語ごとに注意する点は次のとおりです。

言語別の注意点

言語使う機能注意点
Node.jsexecFile()、spawn()exec() や shell オプションに信頼できない入力を渡さない
Pythonsubprocess に引数のリストを渡すshell=True では空白や特殊文字の扱いがアプリの責任になる
JavaProcessBuilder、Runtime.exec(String[])コマンドと引数を別の要素で渡す
.NETProcessStartInfo.ArgumentListUseShellExecute = false にし、cmd.exe や sh -c に入力を渡さない
PHPescapeshellarg()コマンド名とオプションはコードに固定する
出典:Node.js・Python公式ドキュメント、OWASP OS Command Injection Defense Cheat Sheet

対策3:入力を許可リストで検証する

引数を分けても、入力の検証は必要です。OWASPは、シェルに解釈されなくても、入力がプログラムのオプションとして扱われる「引数インジェクション」が残ると説明しています。影響は、実行するプログラムによって情報漏えいから遠隔コード実行まで幅があります。

  1. 使えるコマンドを固定する

    実行できるプログラムを許可リストで決め、入力から選ばせません。必要な処理が複数あるなら、コード内で決めた処理名に対応づけます。

  2. 引数の形式を厳しく決める

    文字種と長さを許可リストか正規表現で指定します。OWASPは、シェルの特殊文字や空白を許さないよう勧めています。

  3. オプションの解釈を止める

    対象のプログラムが対応していれば、-- でオプションの解析を終わらせます。ただし、プログラムによって動きが違うので、個別に確認します。

  4. 合わない入力は処理を止める

    IPAは、許可していない文字が見つかったら引数に渡さず処理を中止する方法を示しています。

IPAは、危険な記号だけを禁止するブラックリスト方式を、検査漏れの可能性があるため推奨していません。MITREも、入力検証は多層防御として有用だが、それだけでは不十分だとしています。

対策4:被害を小さくする

ここまでの対策に漏れがあった場合に備え、実行環境の権限も絞ります。OWASPは、アプリを必要最小限の権限で実行し、できれば単一の作業専用に権限を絞ったアカウントを使うよう勧めています。MITREも、最小権限に加えて、サンドボックスなどの隔離された環境を挙げています。

  • Webアプリをroot権限や管理者権限で動かさない
  • コマンド実行用に権限を絞ったアカウントを分ける
  • エラーメッセージにコマンドの内容や内部情報を出さない

自分のコードで確かめる

まず、コマンドを実行する関数の呼び出しをコードから探します。IPAはPerlの open()、system()、eval()、PHPの exec()、passthru()、shell_exec()、system()、popen() などを例に挙げています。見つけたら、次の順で確かめます。

  1. 標準機能やライブラリに置き換えられないか
  2. シェルを介さず、引数を分けて渡しているか
  3. 外部入力が届くなら、許可リストで検証しているか

依存ライブラリがコマンドを実行している場合もあります。診断は、自分たちが管理するテスト環境で行ってください。

よくある質問

OSコマンドインジェクションとSQLインジェクションの違いは何ですか?

どちらも入力が命令として解釈される脆弱性ですが、解釈する先が違います。SQLインジェクションではデータベース、OSコマンドインジェクションではOSのシェルなどが解釈します。OWASP Top 10:2025では、どちらもA05 Injectionに含まれています。SQLインジェクションの解説もあわせて確認してください。

入力をエスケープすれば十分ですか?

十分とは言えません。OWASPは、エスケープしても引数を追加される余地が残る場合があると説明しています。まずはコマンドを呼ばない方法を検討し、呼ぶ場合はシェルを介さない実装と許可リストでの検証を組み合わせます。

WAFがあれば対策は不要ですか?

不要にはなりません。CISAとFBIが求めているのは、開発の段階でこの種類の脆弱性をなくすことです。WAFを使う場合も、アプリ側の修正の代わりにはしないでください。

出典: IPA「安全なウェブサイトの作り方」OSコマンド・インジェクション、OWASP OS Command Injection Defense Cheat Sheet、MITRE CWE-78、MITRE 2025 CWE Top 25、OWASP Top 10:2025 A05 Injection、CISA Secure by Design Alert、Node.js child_process、Python subprocess

セキュリティ道 編集部Web セキュリティの解説と、最近の情報流出のまとめ

次に読む