表面的には単純に見える Shell コマンドには、基本的なシステム操作からリモートサーバーアクセスまで、あらゆるものに影響を与える危険なセキュリティ脆弱性が隠されている。最近の技術分析により、 Unix 系システムがコマンド実行を処理する方法、特に system()
などの関数や SSH などのツールにおける根本的な欠陥について、コミュニティで激しい議論が巻き起こっている。
核心的な問題は、これらのシステムがコマンドを処理する際にコードとデータを混在させることにある。プログラムが別のコマンドを実行する必要がある場合、多くの場合、コマンド名とユーザー入力を単一のテキスト文字列に結合する。このアプローチにより、攻撃者がデータではなくコマンドとして解釈される特別な入力を作成することで、悪意のあるコードを注入する機会が生まれる。
コマンド実行の隠れた危険性
最も問題のある関数は system()
で、これはコマンドを直接 shell に渡して処理させる。これにより、ユーザー入力内の特殊文字が shell コマンドとして解釈される可能性があるため、セキュリティリスクが生じる。例えば、プログラムがセミコロンやバッククォートを含むファイル名を処理しようとした場合、これらの文字が意図しないコマンド実行を引き起こす可能性がある。
コミュニティの専門家は、 system()
を完全に避け、 exec()
ファミリー関数や posix_spawn()
などのより安全な代替手段を使用することを強く推奨している。これらの代替手段により、プログラムは shell を介さずに直接コマンドを実行できるため、コマンドインジェクション攻撃のリスクが排除される。
技術的注記: system()
- コマンドをシステム shell に渡して実行する C ライブラリ関数; exec()
- 現在のプロセスを新しいプログラムで置き換える関数ファミリー; posix_spawn()
- 新しいプロセスを安全に作成するための標準化された関数
脆弱な関数とより安全な代替手段:
脆弱な関数 | リスクレベル | より安全な代替手段 | プラットフォーム |
---|---|---|---|
system() |
高 | exec() , posix_spawn() |
Unix/Linux |
SSH マルチ引数コマンド | 中 | 単一引用符文字列または shlex.quote() |
クロスプラットフォーム |
Shell バッククォート | 高 | 直接関数呼び出し | Unix/Linux |
ユーザー入力を伴う popen() |
高 | 引数配列を使用した subprocess |
クロスプラットフォーム |
SSH の引数結合問題
SSH は、隠れた argv 結合動作により、特に厄介なケースを提示している。 ssh server command arg1 arg2
のようなコマンドを実行すると、 SSH はサーバー名の後のすべての引数を自動的に単一の文字列に結合してから、リモート shell に送信する。これは、引数内のスペースや特殊文字がコマンド区切り文字として誤解釈される可能性があることを意味する。
コミュニティでは、 shell クォート関数を使用した回避策が開発されている。 Python では、開発者は shlex.quote()
を使用して引数を適切にエスケープできる。 Bash ユーザーは、 printf '%q'
または新しい ${var@Q}
構文を使用して、変数を SSH に渡す前に安全にクォートできる。
「補間はセキュリティ問題ではなく、問題はユーザーがデータをクォートしていないことです。」
しかし、これらの解決策では、開発者が一貫してそれらを適用することを覚えている必要があり、経験豊富なプログラマーでさえ、これらの安全対策を忘れることがある。
言語別シェルクォート解決策:
- Python:
shlex.quote()
およびshlex.join()
関数 - Bash:
printf '%q'
コマンドまたは${var@Q}
パラメータ展開 - Ruby: 引数エスケープのための
.shellescape
メソッド - 一般的な方法:
--
セパレータを使用して引数がオプションとして解釈されることを防ぐ
Windows がすべてを悪化させる
Windows システムでは状況がさらに複雑になる。引数を別々の文字列として渡すことができる Unix 系システムとは異なり、 Windows アプリケーションはコマンドライン引数を単一の文字列として受け取り、各プログラムが個別に解析する必要がある。これは、異なるアプリケーションが異なる解析ルールを使用する可能性があるため、 Windows プログラムに引数を安全に渡す汎用的な方法が存在しないことを意味する。
この根本的な違いにより、 Rust などのプログラミング言語でセキュリティ脆弱性が発生し、 Windows プラットフォームでのコマンド実行に関するセキュリティアドバイザリを発行する必要があった。
今後の道筋
開発コミュニティは解決策について分かれたままである。一部の人々は、直接的なプログラム実行を支持して、 shell ベースのコマンド実行を完全に放棄することを提唱している。他の人々は、プログラム間での出力のパイプ処理や shell ワイルドカードの使用など、正当な使用例を指摘して、 shell 機能は排除するには有用すぎると主張している。
現代のプログラミング言語は、安全なコマンド実行のためのより良い API を提供することで、これらの問題に対処し始めている。しかし、レガシーシステムや既存のコードベースは問題のあるアプローチを使用し続けており、継続的なセキュリティリスクを生み出している。
この議論は、ソフトウェアセキュリティにおけるより広範な課題を浮き彫りにしている:利便性と機能性を安全性とバランスさせることである。安全な代替手段は存在するが、多くの場合、より複雑なコードとより深い技術的知識が必要であり、開発者がより単純だがリスクの高いアプローチを使用し続ける可能性が高い。
参考: The curious case of shell commands, or how this bug is required by POSIX