A05 インジェクション
値として渡したはずの入力を、インタプリタが命令の一部として読むと成立する。直し方は、命令と値を別の道で渡すことと、出す先の文脈に合わせて符号化することの2つに尽きる。
要点
- 成立の条件は、信頼しない値が文字列の組み立てで命令文に入り、インタプリタがその命令文を構文として解析すること。
- SQLとOSコマンドは、組み立てをやめて、値を別に渡すAPIに替えるのが根本。エスケープは替えられない所の残りに使う。
- XSSは、HTMLの本文、属性、URL、JavaScript、CSSの文脈ごとに処理が違う。1つの関数で全部を済ませない。
- 表名、列名、並び順、コマンド名のように値として渡せない部分は、許可した一覧から選ばせる。
- レビューでは、命令文や出力を文字列で組む箇所と、フレームワークの自動エスケープを外す箇所を探す。
成立の条件
インジェクションは、SQL、シェル、ブラウザのHTMLパーサー、LDAP、式言語など、受け取った文字列を構文として解析する部品の手前で起きる[1][2]。命令文と値を1本の文字列に連結した時点で、どこまでが値かを決めるのはインタプリタになり、アプリの意図は失われる。
| インタプリタ | 組み立ての例 | 値を分けて渡す手段 | CWE |
|---|---|---|---|
| SQL | "... WHERE id = " + id | プリペアドステートメントのバインド | CWE-89 |
| OSのシェル | exec("nslookup " + d) | シェルを通さず、引数を配列で渡す | CWE-78、CWE-88 |
| HTMLパーサー | el.innerHTML = name | textContent、テンプレートの自動エスケープ | 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]。
- 本文と属性 テンプレートエンジンの自動エスケープに任せる。属性の値は必ず引用符で囲む。引用符の無い属性では、空白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]。
悪い例と直した例。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]。
守り方
次は緩和の策で、根本の直し方を済ませたうえで重ねる。
- 入力の検証 型、長さ、形を許可の一覧で確かめる。特殊な文字を正当に使う入力が多いので、これだけでは守れない[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-74 | Improper Neutralization of Special Elements in Output Used by a Downstream Component | インジェクション全体の親 |
| CWE-89 | Improper Neutralization of Special Elements used in an SQL Command | SQLインジェクション |
| CWE-564 | SQL Injection: Hibernate | ORMの抜け道 |
| CWE-79 | Improper Neutralization of Input During Web Page Generation | XSS |
| CWE-116 | Improper Encoding or Escaping of Output | 文脈ごとの符号化 |
| CWE-78 | Improper Neutralization of Special Elements used in an OS Command | OSコマンドインジェクション |
| CWE-88 | Improper Neutralization of Argument Delimiters in a Command | 引数インジェクション |
| CWE-90 | Improper Neutralization of Special Elements used in an LDAP Query | 成立の条件の表 |
| CWE-917 | Improper Neutralization of Special Elements used in an Expression Language Statement | 成立の条件の表 |
| CWE-113 | Improper Neutralization of CRLF Sequences in HTTP Headers | 成立の条件の表 |
参照
- A05:2025 Injection OWASP Top 10:2025
- Injection Prevention Cheat Sheet OWASP Cheat Sheet Series
- SQL Injection Prevention Cheat Sheet OWASP Cheat Sheet Series
- Query Parameterization Cheat Sheet OWASP Cheat Sheet Series
- Cross Site Scripting Prevention Cheat Sheet OWASP Cheat Sheet Series
- DOM based XSS Prevention Cheat Sheet OWASP Cheat Sheet Series
- OS Command Injection Defense Cheat Sheet OWASP Cheat Sheet Series
- CWE-89 MITRE CWE
- CWE-79 MITRE CWE
- CWE-78 MITRE CWE
- CWE-88 MITRE CWE
- 安全なウェブサイトの作り方 IPA。改訂第7版
- Content Security Policy Level 3 W3C
- Trusted Types W3C
- Input Validation Testing OWASP Web Security Testing Guide
- Child process Node.js Documentation
- RFC 4515 LDAP: String Representation of Search Filters IETF