A05 インジェクション

値として渡したはずの入力を、インタプリタが命令の一部として読むと成立する。直し方は、命令と値を別の道で渡すことと、出す先の文脈に合わせて符号化することの2つに尽きる。

OWASP Top 10:2025 A05:2025 Injection。対応づけられたCWEは37個、CVEは62,445件で、10個のカテゴリの中で最も多い[1]。2026年11月時点の記載で書いています。

要点

  • 成立の条件は、信頼しない値が文字列の組み立てで命令文に入り、インタプリタがその命令文を構文として解析すること。
  • SQLとOSコマンドは、組み立てをやめて、値を別に渡すAPIに替えるのが根本。エスケープは替えられない所の残りに使う。
  • XSSは、HTMLの本文、属性、URL、JavaScript、CSSの文脈ごとに処理が違う。1つの関数で全部を済ませない。
  • 表名、列名、並び順、コマンド名のように値として渡せない部分は、許可した一覧から選ばせる。
  • レビューでは、命令文や出力を文字列で組む箇所と、フレームワークの自動エスケープを外す箇所を探す。

成立の条件

インジェクションは、SQL、シェル、ブラウザのHTMLパーサー、LDAP、式言語など、受け取った文字列を構文として解析する部品の手前で起きる[1][2]。命令文と値を1本の文字列に連結した時点で、どこまでが値かを決めるのはインタプリタになり、アプリの意図は失われる。

連結した命令文では値の一部が構文として読まれ、値を別に渡すと構文は値より先に確定する 組み立てる 入力 x' OR '1'='1 文字列で連結 命令文と値が1本になる SQLの構文解析 値の一部が条件式として読まれる WHERE status = 'x' OR '1'='1' 値を別に渡す 入力 x' OR '1'='1 プレースホルダー 命令文は $1 のまま送る SQLの構文解析 構文は値を見る前に確定する WHERE status = $1 $1 には x' OR '1'='1 という文字列が入るだけ
図1 組み立てた命令文と、値を分けて渡した命令文の違い
インタプリタ組み立ての例値を分けて渡す手段CWE
SQL"... WHERE id = " + idプリペアドステートメントのバインドCWE-89
OSのシェルexec("nslookup " + d)シェルを通さず、引数を配列で渡すCWE-78、CWE-88
HTMLパーサーel.innerHTML = nametextContent、テンプレートの自動エスケープCWE-79
LDAP検索フィルターの文字列に利用者名を連結フィルター用のエスケープ。RFC 4515 の符号化[17]CWE-90
式言語、テンプレートテンプレートの本文に入力を埋めて評価するテンプレートは固定し、入力は変数として渡すCWE-917、CWE-94
HTTPヘッダー改行を含む値をヘッダーに入れるヘッダーを組むAPIで改行を拒むCWE-113

SQLインジェクション

プリペアドステートメントでは、命令文の構文が値を受け取る前に確定する。値がどんな文字列でも、構文の位置は動かない[3][4]。

悪い例と直した例。架空の注文管理の一覧

// 悪い例 値を命令文に連結する
const sql = "SELECT * FROM orders WHERE user_id = " + userId +
            " AND status = '" + status + "'";
db.query(sql);

// 直した例 命令文は固定し、値は配列で別に渡す
db.query("SELECT * FROM orders WHERE user_id = $1 AND status = $2",
         [userId, status]);

プレースホルダーに置けるのは値だけで、表名、列名、ASC と DESC は置けない。並び替えの列を利用者に選ばせるときは、許可した一覧から引く。識別子をエスケープで守ろうとしない[1][3]。

// 識別子は許可した一覧から選ぶ。一覧に無ければ既定の列にする
const SORT = { date: "created_at", total: "total_amount" };
const col = SORT[req.query.sort] ?? "created_at";
const dir = req.query.dir === "asc" ? "ASC" : "DESC";
db.query(`SELECT * FROM orders WHERE user_id = $1 ORDER BY ${col} ${dir}`, [userId]);
  • ORMの抜け道 ORMを使っても、生のSQLや条件の文字列を受け取るメソッドに連結した値を渡せば同じことが起きる。HQLの連結も同じ[1]。
  • ストアドプロシージャ 中で命令文を連結して動的に実行していれば、呼び出し側でバインドしても防げない[1]。
  • LIKE の値 バインドしても % と _ はワイルドカードとして働く。インジェクションではないが、検索の意図が変わる。必要なら ESCAPE 句で無効にする。
  • DBの権限 アプリの接続ユーザーに、要らない表への権限と、DDLの権限を持たせない。漏れたときの被害を絞る緩和策で、根本の直し方の代わりにはならない[3]。

XSS

XSSは、ブラウザのHTMLパーサーとJavaScriptエンジンへのインジェクション。値が入る位置ごとに、構文を壊す文字と、必要な処理が違う[5]。

同じ値でも、HTMLの本文、属性、URL、スクリプト、スタイルの位置ごとに必要な処理が違う テンプレート <p>{v}</p> <input value="{v}"> <a href="{v}"> <script>let s = {v};</script> <div style="color:{v}"> 本文 & < > " ' を実体参照に 属性 必ず引用符で囲み、属性用に符号化 URL http と https だけ許し、そのうえで符号化 スクリプト 値を置かない。data属性から読む スタイル 値を置かない。許可した値を選ぶ
図2 値が入る文脈と、それぞれに要る処理
  • 本文と属性 テンプレートエンジンの自動エスケープに任せる。属性の値は必ず引用符で囲む。引用符の無い属性では、空白1つで次の属性が始まる[5]。
  • URL HTMLの符号化だけでは javascript: のスキームを止められない。URL で解析し、スキームを許可の一覧で確かめる。クエリの値は encodeURIComponent で符号化してから属性に入れる[5]。
  • スクリプトとスタイル <script> と <style> の中、イベントハンドラー属性、HTMLのコメントは、符号化で守り切れない文脈とされる。値はそこに置かず、data属性や JSON の要素から読む[5]。
  • DOM クライアントで組むときも同じ。innerHTML、outerHTML、document.write、eval に値を渡さない[6]。

悪い例と直した例。URLのクエリから表示名を出す架空の画面

// 悪い例 文字列をHTMLとして解析させる
document.querySelector("#name").innerHTML = params.get("name");
link.href = profile.website;  // javascript: で始まる値も通る

// 直した例 文字列は文字列のまま入れ、URLはスキームを確かめる
document.querySelector("#name").textContent = params.get("name");
const u = new URL(profile.website, location.origin);
if (u.protocol === "https:" || u.protocol === "http:") link.href = u.href;
  • 自動エスケープを外す箇所 dangerouslySetInnerHTML、Angular の bypassSecurityTrust で始まる関数、Lit の unsafeHTML は、フレームワークの守りの外に出る箇所。レビューではここを全部挙げる[5]。
  • 利用者が書くHTML 本文としてHTMLを許すなら、符号化でなく無害化になる。OWASPは DOMPurify を勧め、無害化のあとに文字列を書き換えないこと、ライブラリを更新し続けることを求める[5]。

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

シェルに文字列を渡すと、;、|、$()、改行などが命令の区切りとして解釈される。シェルを通さず、実行するプログラムと引数の配列を直接渡せば、値は1つの引数のまま届く[7][16]。

シェルを通すと区切り文字で2つ目のコマンドが走り、引数の配列で渡すと値は1つの引数になる 入力 d の値 example.com; id exec("nslookup " + d) 1本の文字列をシェルへ /bin/sh ; で命令を区切る nslookup example.com 2つ目のコマンドが走る execFile("nslookup", [d]) プログラムと引数の配列 execve シェルを通らない 引数は1つだけ 名前の解決に失敗して終わる 残る弱点 値が - で始まると、その引数はオプションとして読まれる 引数インジェクション CWE-88。-- で区切るか、値の形を許可の一覧で確かめる
図3 シェルを通す呼び出しと、引数の配列で渡す呼び出し

悪い例と直した例。Node.js の child_process。exec はシェルを起動し、execFile は既定でシェルを起動しない[16]

// 悪い例
exec("nslookup " + req.query.domain, cb);

// 直した例 1 外部コマンドを呼ばず、言語のライブラリで済ませる
const addrs = await dns.promises.resolve4(domain);

// 直した例 2 呼ぶしかないとき。形を確かめ、シェルを通さず配列で渡す
if (!/^[a-z0-9.-]{1,253}$/.test(domain) || domain.startsWith("-")) throw new Error("bad");
execFile("/usr/bin/nslookup", [domain], cb);
  • OWASPの順は、まずOSのコマンドを呼ばないこと、次に呼ぶならパラメータ化と許可の一覧の検証[7]。
  • シェルの引用符で包むエスケープは、区切り文字を止めても、1つの引数として好きな値を渡すことは止めない。OWASPは、すべてのOSコマンドインジェクションは引数インジェクションでもある、と書く[7]。
  • execFile や spawn でも、shell: true を付けた時点でシェルを通る[16]。

守り方

根本の直し方
  • 命令と値を分けて渡すAPIを使う。SQLはバインド、OSは外部コマンドをやめるか引数の配列、HTMLはテンプレートの自動エスケープと textContent[1][2]。
  • 値として渡せない識別子、コマンド名、URLのスキームは、許可した一覧から選ぶ。
  • 出力は、出す先の文脈に合わせて符号化する。符号化で守り切れない文脈には値を置かない[5]。
  • 安全な呼び方を、チームの共通部品と規約に固定する。生のSQL、シェル、innerHTML を使う箇所はレビューの必須の対象にする。

次は緩和の策で、根本の直し方を済ませたうえで重ねる。

  • 入力の検証 型、長さ、形を許可の一覧で確かめる。特殊な文字を正当に使う入力が多いので、これだけでは守れない[1]。
  • CSP nonce か hash で読み込むスクリプトを絞り、インラインのスクリプトを止める。XSSが残ったときの被害を減らす[13]。
  • Trusted Types require-trusted-types-for 'script' で、innerHTML などの危ない代入先に生の文字列を渡せなくする[14]。
  • 最小の権限 DBの接続ユーザー、コマンドを動かすプロセスの権限を絞る。Cookieに HttpOnly を付け、スクリプトからセッションIDを読めなくする。

診断で見る所

  • 組み立ての箇所 文字列の連結やテンプレートリテラルで、SQL、コマンド、HTML、LDAPのフィルター、式言語を組む所。query(、exec(、innerHTML、eval( と、ORMの生のSQLを受け取るメソッドから探す。
  • 識別子の入力 並び替え、絞り込みの列、帳票の列の指定。値としてバインドできないので、連結されていることが多い[1]。
  • 保存してから出す値 登録した名前やコメントが、管理画面、メール、CSVの出力、ログの閲覧画面など、別の画面で出る所。入口で検証しても、出す側で符号化していなければ通る。
  • 守りの外に出る箇所 自動エスケープを外す関数、shell: true、テンプレートの文字列そのものを入力から作る所。
  • 動的な確かめ OWASP WSTG の入力検証の試験の章が、SQL、コマンド、XSS、ORMの試し方を並べる[15]。IPAの安全なウェブサイトの作り方は、SQLインジェクション、OSコマンド・インジェクション、XSSごとに、根本的解決と保険的対策を分けて示す[12]。

関連するCWE

A05には37個のCWEが対応づけられている[1]。設計とレビューで主に見るものを挙げる。中心の4つの定義と例は、MITREの各ページにある[8][9][10][11]。

CWE名前このページの節
CWE-74Improper Neutralization of Special Elements in Output Used by a Downstream Componentインジェクション全体の親
CWE-89Improper Neutralization of Special Elements used in an SQL CommandSQLインジェクション
CWE-564SQL Injection: HibernateORMの抜け道
CWE-79Improper Neutralization of Input During Web Page GenerationXSS
CWE-116Improper Encoding or Escaping of Output文脈ごとの符号化
CWE-78Improper Neutralization of Special Elements used in an OS CommandOSコマンドインジェクション
CWE-88Improper Neutralization of Argument Delimiters in a Command引数インジェクション
CWE-90Improper Neutralization of Special Elements used in an LDAP Query成立の条件の表
CWE-917Improper Neutralization of Special Elements used in an Expression Language Statement成立の条件の表
CWE-113Improper Neutralization of CRLF Sequences in HTTP Headers成立の条件の表

参照

  1. A05:2025 Injection OWASP Top 10:2025
  2. Injection Prevention Cheat Sheet OWASP Cheat Sheet Series
  3. SQL Injection Prevention Cheat Sheet OWASP Cheat Sheet Series
  4. Query Parameterization Cheat Sheet OWASP Cheat Sheet Series
  5. Cross Site Scripting Prevention Cheat Sheet OWASP Cheat Sheet Series
  6. DOM based XSS Prevention Cheat Sheet OWASP Cheat Sheet Series
  7. OS Command Injection Defense Cheat Sheet OWASP Cheat Sheet Series
  8. CWE-89 MITRE CWE
  9. CWE-79 MITRE CWE
  10. CWE-78 MITRE CWE
  11. CWE-88 MITRE CWE
  12. 安全なウェブサイトの作り方 IPA。改訂第7版
  13. Content Security Policy Level 3 W3C
  14. Trusted Types W3C
  15. Input Validation Testing OWASP Web Security Testing Guide
  16. Child process Node.js Documentation
  17. RFC 4515 LDAP: String Representation of Search Filters IETF