サーバーに画像を安全にアップロードします。 パート2

これは翻訳の2番目の部分です。 最初から読み始める方が良いです。



それでは、前半で説明した方法を適用した後、心配するのをやめることができますか? 残念ながら、ありません。 PHPトランスレータに渡されるファイル拡張子は、サーバーの構成によって異なります。 多くの場合、開発者はWebサーバーの構成を知らず、制御しません。 .htmlおよび.jsファイルがphpとして実行されるような構成のWebサーバーを見ました。 一部のWebアプリケーションでは、.gifまたは.jpegファイルをPHPで解釈する必要があります(これは、グラフやチャートなどの画像がPHP自体によってサーバー上に動的に構築される場合によく起こります)。



PHPがどのファイル拡張子を解釈するかを正確に知っていても、他のアプリケーションがサーバーにインストールされたときにこれが変更されないという保証はありません。 それまでに、サーバーのセキュリティがこれらの変更に依存していることを忘れることができます。



Microsoft IISベースのWebサーバーを使用している場合、留意すべき点がいくつかあります。 Apacheとは異なり、Microsoft IISサーバーは「PUT」リクエストの実行をサポートしています。これにより、ユーザーはPHPをバイパスしてファイルを直接ダウンロードできます。 システム権限によりIISで許可されている場合、PUT要求を使用してファイルをサーバーにアップロードできます(IUSR_MACHINENAMEとして実行されます)。 これは、サービスマネージャーを使用して構成できます。



画像



PHPでファイルをアップロードできるようにするには、ファイルシステムの権限を変更して、ディレクトリを書き込み可能にする必要があります。 IISのアクセス許可でファイルの書き込みが許可されていないことを確認することは非常に重要です。 そうしないと、ユーザーはPUTリクエストを使用して任意のファイルをアップロードでき、PHPで行ったチェックをバイパスできます。



ダウンロードしたファイルへの間接アクセス

ダウンロードディレクトリへの直接アクセスを許可できない場合があります。 これは、このディレクトリがサイトのルートの下にないか、サーバー設定または.htaccessを使用してアクセスが制限されているためである可能性があります。



次の例を考慮してください(upload5.php):



<?php

$uploaddir = '/var/spool/uploads/' ; # Outside of web root

$uploadfile = $uploaddir . basename($_FILES[ 'userfile' ][ 'name' ]);



if (move_uploaded_file($_FILES[ 'userfile' ][ 'tmp_name' ], $uploadfile)) {

echo "File is valid, and was successfully uploaded.\n" ;

} else {

echo "File uploading failed.\n" ;

}

?>



* This source code was highlighted with Source Code Highlighter .






ユーザーは単にファイルにアクセス/アップロード/アップロードすることはできません。このために追加機能を提供する必要があります(view5.php):



<?php

$uploaddir = '/var/spool/uploads/' ;

$name = $_GET[ 'name' ];

readfile($uploaddir.$name);

?>




* This source code was highlighted with Source Code Highlighter .








View5.phpコードには重大な脆弱性があります。 攻撃者はこのコードを使用して、Webサーバーの特権レベルで読み取れるファイルを読み取ることができます。 たとえば、あなたが電話した場合

www.example.com/view5.php?name=../../../etc/passwdでは、パスワードファイルを読み取ることができる場合があります。



このバグは、ファイルへの実際のパスを返す結果関数dirname(realpath())を使用して修正できます。 したがって、このパスが正しくない場合、ファイルは表示されません。 同様に、basename()を使用すると、ファイル名のみが取得され、正しいダウンロードディレクトリに既にアタッチされています。 -注 翻訳者。



ローカルインクルードの使用(ローカルファイルインクルージョン攻撃)

これは、サイトの最悪のセキュリティホールの1つです。 よく知られているので、実際にはほとんど表示されません。 しかし、彼らが言うように-「繰り返しは学習の母です。」 -注 翻訳者。



過去の例では、ダウンロードしたファイルをルートの外部に保存しますが、そこには直接アクセスして実行することはできません。 これは安全ですが、コードに別の脆弱性(インクルードの使用)が存在する場合、攻撃者はそれを利用する可能性があります。 次のコード(local_include.php)を含む他のページがあるとします:



<?php

// ... some code here



if (isset($_COOKIE[ 'lang' ])) {

$lang = $_COOKIE[ 'lang' ];

} elseif (isset($_GET[ 'lang' ])) {

$lang = $_GET[ 'lang' ];

} else {

$lang = 'english' ;

}



include( "language/$lang.php" );



// ... some more code here

?>



* This source code was highlighted with Source Code Highlighter .




これは、多言語Webアプリケーションで通常発生する一般的なコードです。 同様のコードは、ユーザー設定に応じて異なるインクルードファイルを提供できます。



コードにはインクルードの脆弱性があります。 攻撃者は、このページにファイルシステムのファイルを強制的に含めることができます。次に例を示します。



画像



このリクエストにより、local_include.phpに「言語/../../../../../../../../ tmp / phpinfo」が含まれ、実行されます。これは、単に/ tmp / phpinfoです。 攻撃者は既にサーバー側にあるファイルのみを実行できるため、攻撃者の機能は制限されます。



ただし、攻撃者がサイトのルート外でもファイルをダウンロードでき、同様の脆弱性を使用してダウンロードしたファイルの名前と場所を知っている場合、サーバーで任意のコードを実行できます。



以前は、URLでファイルを含めることができるphp設定があったため、サーバー上にないコードを実行できました。 PHPの最新バージョンでは、これは基本的にセキュリティ上の理由から不可能です。 -注 翻訳者





最後に

保護を提供する場合、攻撃者がダウンロードしたファイルの名前を知ることを防ぐことが重要です。 これは、データベース内の元のファイルを保持しながら、ダウンロードしたファイルの名前をランダムに生成することで実行できます。



例を考えてみましょう(upload6.php):



<?php

require_once 'DB.php' ; // We are using PEAR::DB module

$uploaddir = '/var/spool/uploads/' ; // Outside of web root

$uploadfile = tempnam($uploaddir, "upload_" );



if (move_uploaded_file($_FILES[ 'userfile' ][ 'tmp_name' ], $uploadfile)) {

// Saving information about this file in the DB

$db =& DB::connect( "mysql://username:password@localhost/database" );



if (PEAR::isError($db)) {

unlink($uploadfile);

die "Error connecting to the database" ;

}



$res = $db->query( "INSERT INTO uploads SET name=?, original_name=?, mime_type=?" ,

array(basename($uploadfile,

basename($_FILES[ 'userfile' ][ 'name' ]),

$_FILES[ 'userfile' ][ 'type' ]));



if (PEAR::isError($res)) {

unlink($uploadfile);

die "Error saving data to the database. The file was not uploaded" ;

}



$id = $db->getOne( 'SELECT LAST_INSERT_ID() FROM uploads' ); // MySQL specific



echo "File is valid, and was successfully uploaded. You can view it <a

href=\"view6.php?id=$id\">here</a>\n"
;

} else {

echo "File uploading failed.\n" ;

}

?>



* This source code was highlighted with Source Code Highlighter .








ダウンロードしたファイル(view6.php)を表示します。



<?php

require_once 'DB.php' ;

$uploaddir = '/var/spool/uploads/' ;

$id = $_GET[ 'id' ];



if (!is_numeric($id)) {

die( "File id must be numeric" );

}



$db =& DB::connect( "mysql://root@localhost/db" );



if (PEAR::isError($db)) {

die( "Error connecting to the database" );

}



$file = $db->getRow( 'SELECT name, mime_type FROM uploads WHERE id=?' ,

array($id), DB_FETCHMODE_ASSOC);



if (PEAR::isError($file)) {

die( "Error fetching data from the database" );

}



if (is_null($file) || count($file)==0) {

die( "File not found" );

}



header( "Content-Type: " . $file[ 'mime_type' ]);

readfile($uploaddir.$file[ 'name' ]);

?>



* This source code was highlighted with Source Code Highlighter .








ダウンロードされたファイルは、ルートの外部に保存されるため、直接実行できなくなりました。 攻撃者はサーバー上のファイルシステム内のダウンロードされたファイルの名前を見つけることができないため、これらの脆弱性をインクルードの脆弱性で使用することはできません。 ファイルは名前ではなく数値インデックスに関連付けられているため、ファイルの「交差点」には問題があります。 また、SQLクエリにPEAR :: DBを使用することを指摘したいと思います。 SQLでは、クエリ変数の場所として疑問符を使用しています。 ユーザーから受け取ったデータがクエリに転送されると、それらのタイプは自動的に配置され、SQLインジェクションの問題を防ぎます。



ファイルシステムにファイルを保存する代わりに、BLOBフィールドを使用してデータベースに直接ファイルを保存します。 このアプローチにも利点がありますが、ほとんどの場合、膨大なリソース消費のために適用されません。



その他の問題

サーバーにファイルをアップロードするシステムを開発する際に注意すべき点は他にもたくさんあります。

  1. サービス拒否。 ユーザーはいくつかの大きなファイルをアップロードできるため、サーバー上のすべての空き領域を占有します。 これは、ダウンロードされるファイルのサイズと、1日に1人のユーザーからダウンロードされるファイルの数に制限を設定することで解決されます。
  2. パフォーマンス。 最後の例では、ファイルの表示を頻繁に要求するため、表示がボトルネックになる可能性があります。 サーバーの負荷が高い場合は、phpコードを実行できない静的コンテンツの保存のみを目的とした別のサーバーを使用することをお勧めします。 パフォーマンスを改善するもう1つの方法は、適切なヘッダーを発行してサーバー側の静的コンテンツが再処理されるのを防ぐキャッシュプロキシサーバーを使用することです。
  3. アクセス制御。 上記のすべての例では、すべてのユーザーがダウンロードされたファイルを表示できると想定しています。 ただし、ファイルをアップロードしたユーザーのみがファイルを表示できることが必要になる場合があります。 この場合、ダウンロード中にファイルの所有者に関する情報を保存する必要があります。 ファイルを表示するときは、適切なチェックが必要です。


おわりに

サーバーへのファイルアップロードを開発する場合、侵入者の犠牲にならないように、記事で説明されている脆弱性を考慮する必要があります。



ユーザーがファイルに直接アクセスできないようにすることをお勧めします。 これは、ダウンロードしたファイルをサイトのルート外に保存するか、Webサーバー構成を使用してこのディレクトリへのアクセスを拒否することで実行できます。



別の重要なセキュリティ対策は、元のファイル名でサーバーにファイルを保存しないことです。 これにより、脆弱性が存在する場合でもそれらが含まれることを防ぎ、攻撃者のファイル名の操作を不可能にします。



PHPで画像形式をチェックしても、このファイルがphpスクリプトとして実行できないという保証はありません。 正しいイメージを作成できます。これは同時に実行可能なphpスクリプトになります。



Content-Typeやファイル拡張子など、クライアントからのデータはまったく信頼できません。 彼らは非常に簡単に偽造できます。 さらに、実行可能な拡張機能のリストはWebサーバーに完全に依存しており、時間の経過とともに変更されないという保証はありません。



パフォーマンスは非常に重要ですが、ファイルを妥協することなくサーバーに安全にアップロードすることは完全に不可能です。



All Articles