知識マップ: 引数はなぜ壊れるか ── Windowsのコマンドライン引数の規則
記事「引数はなぜ壊れるか ── Windowsのコマンドライン引数の規則」の主張を、概念と関係(エッジ)に分解した知識グラフの全体です。各関係には根拠・確認日・確度が付いています。
Windowsのプロセスには引数の配列は渡らず、CreateProcessに渡した1本のコマンドライン文字列が新しいプロセスに届き(先頭の実行ファイル名はOSがフルパスを補うことがある)、子はGetCommandLineWでそれを取り出す。argvの配列は受け取り側が自分で作るもので、Cランタイムの起動コード、CommandLineToArgvW、.NETランタイムの分割コードが、空白とタブで区切る・二重引用符で囲んだ範囲は区切らない・バックスラッシュは直後に二重引用符が来るときだけ特別扱いする(2n個ならn個と囲みの開閉、2n+1個ならn個と文字としての引用符)という同じ骨格の規則を別々に実装している。先頭のargv[0]だけは引用符で囲めるがエスケープの効かない別規則で、lpApplicationNameを省略すると空白を含むパスの解釈が曖昧になりC:\Program.exeのような別の実行ファイルが起動し得る。この規則の帰結として、末尾がバックスラッシュのパスを引用符で囲むと閉じ引用符が文字に化けて次の引数を巻き込む。組み立て側は、空白か引用符を含むか空の引数を引用符で囲み、引用符の直前のバックスラッシュを2倍+1、末尾のバックスラッシュを2倍にし、引用符にバックスラッシュを前置する1つの関数で足り、.NET Core 2.1以降のProcessStartInfo.ArgumentListがこれを内部で行う。ArgumentListとArgumentsは同時に使えず、.NET Frameworkでは同じ規則の自前関数でArgumentsを組み立てる。中身のある引数の内側で引用符を隣接させる形は受け取り側で解釈が分かれるため生成しない(空の引数を表す引用符2つだけの形は別で、正しい書き方)。往復が保証されるのは、受け取り側がCommandLineToArgvW・Cランタイム・.NETランタイムと同じ分割規則でワイド文字のまま分割し(独自の文法で解釈する相手や途中にシェルのパーサが挟まる場合は対象外)、setargv.obj/wsetargv.objによるワイルドカード展開を有効にしておらず、引数にNUL文字を含まず、組み立てた全体がlpCommandLineの上限に収まる場合で、狭い文字列のmainではコードページで表せない文字が失われる。cmd.exeは独自の構文と引用符の剥がし方を持ち、バッチファイルは生の文字列を受け取るため、外から来た値をバッチに渡すとコマンド注入につながる。コマンドライン長にはCreateProcessの32,767 UTF-16コード単位(終端のnull文字を含む)、cmd.exeの8,191文字などの上限があり、件数に比例して伸びる引数は、相手が読める場合に限り応答ファイルに切り替える。届いた引数は、呼び出し側で組み立てた文字列のログ、Process Explorerや相手の起動時ログで見える相手側の文字列(先頭の実行ファイル名はOSがフルパスを補うことがあり、cmd.exeが挟まればその段が作り直した文字列になる)、相手と同じランタイムで作った引数表示用exeで見る分割後の配列、の3つを順に突き合わせる。呼び出し側・起動時のどちらでコマンドラインをログに残す場合も、機密になり得る引数の値を伏せ字にしてから記録する。
flowchart LR
accTitle: Windowsのコマンドライン引数の規則の知識マップ
accDescr: CreateProcessが引数を1本のコマンドライン文字列として渡し、CommandLineToArgvW・Cランタイム・.NETランタイムがそれぞれ空白・引用符・バックスラッシュの同じ規則で分割すること、その規則が末尾バックスラッシュの巻き込みを生み、組み立て関数とProcessStartInfo.ArgumentListがそれを防ぐこと、lpApplicationNameがProgram.exe問題を防ぐこと、cmd.exeとバッチファイルが引数の変形とコマンド注入の原因になり得ること、応答ファイルが長さの上限を回避すること、引数表示用exeとProcess Explorerで届いた引数を確認できることを含む関係図
windows_command_line_string["コマンドライン文字列(lpCommandLine)"]
command_line_backslash_quote_rule["コマンドライン分割の規則(空白・引用符・バックスラッシュ)"]
argument_quoting_function["引数の引用符付け(組み立て関数)"]
processstartinfo_argumentlist["ProcessStartInfo.ArgumentList"]
createprocess["CreateProcess"]
lpapplicationname["lpApplicationName"]
getcommandlinew["GetCommandLineW"]
commandlinetoargvw["CommandLineToArgvW"]
dotnet_command_line_segmentation[".NETランタイムの引数分割"]
msvc_crt_argument_parsing["Cランタイムの引数分割(argv生成)"]
setargv_wildcard_expansion["setargv.objによるワイルドカード展開"]
argv0_program_name_rule["argv[0](プログラム名)の特別規則"]
trailing_backslash_quote_pitfall["末尾バックスラッシュによる引数の巻き込み"]
doubled_quote_form["囲みの内側の引用符2連続(非空の引数)"]
processstartinfo_arguments_string["ProcessStartInfo.Arguments(手組み文字列)"]
child_process_argument_passing["子プロセスへの引数の受け渡し"]
unquoted_space_path_ambiguity["空白を含む実行ファイルパスの曖昧さ(Program.exe問題)"]
unintended_executable_launch["意図しない実行ファイルの起動"]
bat_file["バッチファイル(bat)"]
batch_file_command_injection["バッチファイル経由のコマンド注入リスク"]
cmd_shell["コマンドシェル(cmd.exe)"]
broken_argument_delivery["引用符消失・空文字列引数の消失"]
response_file["応答ファイル"]
command_line_length_limit["コマンドライン長の上限"]
argument_echo_exe["引数表示用の確認exe"]
process_explorer["Process Explorer"]
command_line_startup_logging["コマンドラインのログ記録(呼び出し側・起動時)"]
secret_disclosure_in_logs["ログへの機密の漏えい"]
credential_in_command_line["コマンドライン引数への機密の混入"]
createprocess -.->|"利用する"| windows_command_line_string
createprocess -.->|"利用する"| lpapplicationname
windows_command_line_string -.->|"で確認できる"| getcommandlinew
commandlinetoargvw -.->|"利用する"| getcommandlinew
dotnet_command_line_segmentation -.->|"利用する"| getcommandlinew
commandlinetoargvw -->|"実装を担う"| command_line_backslash_quote_rule
msvc_crt_argument_parsing -.->|"実装を担う"| command_line_backslash_quote_rule
dotnet_command_line_segmentation -->|"実装を担う"| command_line_backslash_quote_rule
msvc_crt_argument_parsing -->|"で構成できる"| setargv_wildcard_expansion
argv0_program_name_rule -->|"前提とする"| windows_command_line_string
command_line_backslash_quote_rule -.->|"原因になり得る"| trailing_backslash_quote_pitfall
argument_quoting_function -->|"防止する"| trailing_backslash_quote_pitfall
argument_quoting_function -->|"防止する"| doubled_quote_form
processstartinfo_argumentlist -->|"実装を担う"| argument_quoting_function
processstartinfo_arguments_string -.->|"前提とする"| argument_quoting_function
processstartinfo_argumentlist -->|"両立しない"| processstartinfo_arguments_string
processstartinfo_argumentlist -.->|"推奨される対応"| child_process_argument_passing
argument_quoting_function -.->|"推奨される対応"| child_process_argument_passing
lpapplicationname -.->|"防止する"| unquoted_space_path_ambiguity
unquoted_space_path_ambiguity -.->|"原因になり得る"| unintended_executable_launch
bat_file -.->|"原因になり得る"| batch_file_command_injection
cmd_shell -.->|"原因になり得る"| broken_argument_delivery
response_file -.->|"軽減する"| command_line_length_limit
child_process_argument_passing -.->|"で確認できる"| argument_echo_exe
child_process_argument_passing -->|"で確認できる"| process_explorer
command_line_startup_logging -.->|"利用する"| getcommandlinew
command_line_startup_logging -.->|"原因になり得る"| secret_disclosure_in_logs
credential_in_command_line -.->|"原因になり得る"| secret_disclosure_in_logs
概念間の関係(全28件)
図と同じ関係を文章でも列挙します。表示している文と機械可読な意味データ(RDFa)は同じ要素に載っています。確度が「確立した関係」のものは直接の関係として、「条件付きの関係」のものは成立条件つきの言明(rdf:Statement)として表現しています。
- CreateProcessはコマンドライン文字列(lpCommandLine)を利用します。
- CreateProcessはlpApplicationNameを利用します。
- コマンドライン文字列(lpCommandLine)はGetCommandLineWで確認できます。
- CommandLineToArgvWはGetCommandLineWを利用します。
- .NETランタイムの引数分割はGetCommandLineWを利用します。
- CommandLineToArgvWはコマンドライン分割の規則(空白・引用符・バックスラッシュ)の実装を担います。
- Cランタイムの引数分割(argv生成)はコマンドライン分割の規則(空白・引用符・バックスラッシュ)の実装を担います。
- .NETランタイムの引数分割はコマンドライン分割の規則(空白・引用符・バックスラッシュ)の実装を担います。
- Cランタイムの引数分割(argv生成)はsetargv.objによるワイルドカード展開で構成できます。
- argv[0](プログラム名)の特別規則はコマンドライン文字列(lpCommandLine)を前提とします。
- コマンドライン分割の規則(空白・引用符・バックスラッシュ)は末尾バックスラッシュによる引数の巻き込みの原因になることがあります。
- 引数の引用符付け(組み立て関数)は末尾バックスラッシュによる引数の巻き込みを防ぎます。
- 引数の引用符付け(組み立て関数)は囲みの内側の引用符2連続(非空の引数)を防ぎます。
- ProcessStartInfo.ArgumentListは引数の引用符付け(組み立て関数)の実装を担います。
- ProcessStartInfo.Arguments(手組み文字列)は引数の引用符付け(組み立て関数)を前提とします。
- ProcessStartInfo.ArgumentListはProcessStartInfo.Arguments(手組み文字列)と両立しません。
- ProcessStartInfo.ArgumentListは子プロセスへの引数の受け渡しに対する本記事の推奨です。
- 引数の引用符付け(組み立て関数)は子プロセスへの引数の受け渡しに対する本記事の推奨です。
- lpApplicationNameは空白を含む実行ファイルパスの曖昧さ(Program.exe問題)を防ぎます。
- 空白を含む実行ファイルパスの曖昧さ(Program.exe問題)は意図しない実行ファイルの起動の原因になることがあります。
- バッチファイル(bat)はバッチファイル経由のコマンド注入リスクの原因になることがあります。
- コマンドシェル(cmd.exe)は引用符消失・空文字列引数の消失の原因になることがあります。
- 応答ファイルはコマンドライン長の上限を軽減します。
- 子プロセスへの引数の受け渡しは引数表示用の確認exeで確認できます。
- 子プロセスへの引数の受け渡しはProcess Explorerで確認できます。
- コマンドラインのログ記録(呼び出し側・起動時)はGetCommandLineWを利用します。
- コマンドラインのログ記録(呼び出し側・起動時)はログへの機密の漏えいの原因になることがあります。
- コマンドライン引数への機密の混入はログへの機密の漏えいの原因になることがあります。
主要概念の定義
機械可読データ
このデータセットについて
このページはサイトの知識グラフ(_data/knowledge/)から自動生成されています。誤りの指摘はお問い合わせからお願いします。