PHPは開発者に非常に大きな柔軟性を与えますが、これがセキュリティ問題の潜在的な危険をもたらすことにもなります。最近、これまでの問題をまとめる必要があり、ここで記事の翻訳を借りつつ、自分自身の開発経験からの感想も加えてまとめたいと思います。

はじめに
インターネットサービスを開発する際は、常にセキュリティの考え方を念頭に置き、開発するコードに反映させる必要があります。PHPスクリプト言語はセキュリティ問題に関心を持ちません。特に経験の浅い開発者のほとんどにとってはそうです。お金や取引に関わる問題を扱うときは、セキュリティ問題の考慮に特に注意する必要があります。例えば、フォーラムやショッピングカートなどを開発する場合です。
セキュリティ保護の一般的なポイント
フォームを信用しない
一般的なJavaScriptフロントエンド検証では、ユーザーの行動を知ることができません。例えば、ブラウザのJavaScriptエンジンを無効にして、悪意のあるデータをPOSTでサーバーに送信することができます。サーバー側で検証を行い、各PHPスクリプトに渡されたデータを検証して、XSS攻撃やSQLインジェクションを防ぐ必要があります。
ユーザーを信用しない
ウェブサイトが受け取るすべてのデータには悪意のあるコードが含まれていると仮定し、隠れた脅威が存在することを前提に、すべてのデータをクリーンアップする必要があります。
グローバル変数を無効にする
php.iniファイルで以下の設定を行います:
register_globals = Off
この設定オプションが有効になると、大きなセキュリティ上の危険が生じます。例えば、受け取ったデータをデータベースに挿入するprocess.phpというスクリプトファイルがある場合、ユーザー入力データを受け取るフォームは次のようになる可能性があります:
<input name="username" type="text" size="15" maxlength="64">
このように、データをprocess.phpに送信すると、PHPは$username変数を登録し、この変数のデータをprocess.phpに送信します。また、任意のPOSTまたはGETリクエストパラメータに対しても、このような変数が設定されます。明示的に初期化しない場合、次のような問題が発生します:
<?php
// Define $authorized = true only if user is authenticated
if (authenticated_user()) {
$authorized = true;
}
?>
ここで、authenticated_user関数が$authorized変数の値を判断すると仮定します。register_globals設定が有効になっている場合、任意のユーザーがリクエストを送信して$authorized変数の値を任意の値に設定でき、この検証を回避できてしまいます。
これらの送信データはすべて、PHPの定義済み組み込みグローバル配列($_POST、$_GET、$_FILES、$_SERVER、$_REQUESTなど)を介して取得する必要があります。このうち$_REQUESTは、$_GET/$_POST/$_COOKIEという3つの配列を結合した変数であり、デフォルトの順序は$_COOKIE、$_POST、$_GETです。
- 推奨されるセキュリティ設定オプション
error_reportingをOffに設定:エラー情報をユーザーに公開しない。開発時はONに設定してもよい。
safe_modeをOffに設定
register_globalsをOffに設定
以下の関数を無効にする:system、exec、passthru、shell_exec、proc_open、popen
open_basedirを /tmp に設定する。これによりセッション情報に保存権限を与え、同時に独立したウェブサイトのルートディレクトリを設定する。
expose_phpをOffに設定
allow_url_fopenをOffに設定
allow_url_includeをOffに設定
SQLインジェクション攻撃
データベースを操作するSQL文については、特にセキュリティに注意する必要があります。ユーザーが特定の文を入力すると、元のSQL文の機能が変わってしまう可能性があるためです。次のような例があります:
$sql = "select * from pinfo where product = '$product'";
このとき、ユーザーが入力した$productパラメータが次の場合:
39'; DROP pinfo; SELECT 'FOO
最終的なSQL文は次のようになります:
select product from pinfo where product = '39'; DROP pinfo; SELECT 'FOO'
これにより3つのSQL文になり、pinfoテーブルが削除される可能性があり、深刻な結果を招きます。
この問題は、PHPの組み込み関数を使用して簡単に解決できます:
$sql = 'Select * from pinfo where product = '"'
mysql_real_escape_string($product) . '"';
SQLインジェクション攻撃を防ぐには、次の2つのことを行う必要があります:
入力パラメータに対して常に型検証を行う
シングルクォート、ダブルクォート、バッククォートなどの特殊文字には、常にmysql_real_escape_string関数を使用してエスケープ処理を行う
ただし、開発経験に基づくと、PHPのMagic Quotesを有効にしないでください。この機能はPHP6で廃止されました。必要なときに自分でエスケープ処理を行ってください。
基本的なXSS攻撃の防止
XSS攻撃は他の攻撃とは異なり、クライアント側で行われます。最も基本的なXSSツールは、ユーザーが送信しようとしているフォームページにJavaScriptスクリプトを配置し、ユーザーが送信したデータとCookieを盗み取ることです。
XSSツールはSQLインジェクションよりも防御が難しく、各大手企業のウェブサイトもXSS攻撃を受けたことがあります。この攻撃はPHP言語とは関係ありませんが、PHPを使用してユーザーデータをフィルタリングし、ユーザーデータを保護する目的を達成できます。ここでは主にユーザーデータのフィルタリングを使用し、通常はHTMLタグ、特にaタグをフィルタリングします。以下は一般的なフィルタリング方法です:
function transform_HTML($string, $length = null) {
// Helps prevent XSS attacks
// Remove dead space.
$string = trim($string);
// Prevent potential Unicode codec problems.
$string = utf8_decode($string);
// HTMLize HTML-specific characters.
$string = htmlentities($string, ENT_NOQUOTES);
$string = str_replace("#", "#", $string);
$string = str_replace("%", "%", $string);
$length = intval($length);
if ($length > 0) {
$string = substr($string, 0, $length);
}
return $string;
}
この関数はHTMLの特殊文字をHTMLエンティティに変換します。ブラウザはこのテキストをレンダリングするとき、プレーンテキストとして表示します。例えば、<strong>bold</strong>は次のように表示されます:
<STRONG>BoldText</STRONG>
上記の関数の中核はhtmlentities関数です。この関数はHTMLの特殊タグをHTMLエンティティ文字に変換し、これによりほとんどのXSS攻撃をフィルタリングできます。
しかし、経験豊富なXSS攻撃者には、より巧妙な攻撃方法があります。悪意のあるコードを通常のASCIIテキストではなく、16進数やUTF-8エンコーディングで記述する方法です。例えば、次のように行うことができます:
<a href="http://host/a.php?variable=%22%3e %3c%53%43%52%49%50%54%3e%44%6f%73%6f%6d%65%74%68%69%6e%67%6d%61%6c%69%63%69%6f%75%73%3c%2f%53%43%52%49%50%54%3e">
この場合、ブラウザがレンダリングする結果は実際には次のようになります:
<a href="http://host/a.php?variable="> <SCRIPT>Dosomethingmalicious</SCRIPT>
これで攻撃の目的が達成されます。このような事態を防ぐために、transform_HTML関数の基盤として、#と%を対応するエンティティ記号に変換し、さらに$lengthパラメータを追加して送信データの最大長を制限する必要があります。
SafeHTMLを使用したXSS攻撃の防止
上記のXSS攻撃に対する防御は非常にシンプルですが、ユーザーのすべてのマークアップを含むわけではなく、フィルタリング関数を迂回してJavaScriptコードを送信する方法は数百種類あり、この状況を完全に防ぐ方法もありません。
現在、攻撃を突破されないことを保証できる単一のスクリプトはありませんが、比較的防御レベルが高いものはあります。セキュリティ防御には2つの方法があります:ホワイトリストとブラックリストです。そのうちホワイトリストの方がより簡単で効果的です。
ホワイトリストの解決策の1つがSafeHTMLです。これは有効なHTMLを認識できるほど賢く、危険なタグを除去できます。これにはHTMLSaxパッケージに基づく解析が必要です。
SafeHTMLをインストールして使用する方法:
1、以下のサイトにアクセスhttp://pixel-apes.com/safehtml/?page=safehtml 最新のSafeHTMLをダウンロード
2、ファイルをサーバーのclassesディレクトリに置きます。このディレクトリにはすべてのSafeHTMLとHTMLSaxライブラリが含まれます。
3、自分のスクリプトにSafeHTMLクラスファイルを含めます
4、SafeHTMLオブジェクトを作成します
5、parseメソッドを使用してフィルタリングします
<?php
/* If you're storing the HTMLSax3.php in the /classes directory, along
with the safehtml.php script, define XML_HTMLSAX3 as a null string. */
define(XML_HTMLSAX3, '');
// Include the class file.
require_once('classes/safehtml.php');
// Define some sample bad code.
$data = "This data would raise an alert <script>alert('XSS Attack')</script>";
// Create a safehtml object.
$safehtml = new safehtml();
// Parse and sanitize the data.
$safe_data = $safehtml->parse($data);
// Display result.
echo 'The sanitized data is <br />' . $safe_data;
?>
SafeHTMLはXSS攻撃を完全に防ぐことはできませんが、比較的複雑なスクリプトで検証する方法にすぎません。
一方向HASH暗号化によるデータ保護
一方向ハッシュ暗号化は、各ユーザーのパスワードが一意であり、解読できないことを保証します。最終ユーザーだけがパスワードを知っており、システムも元のパスワードを知りません。この利点は、システムが攻撃を受けた後でも、攻撃者が元のパスワードデータを知ることができないことです。
暗号化とハッシュは異なる2つのプロセスです。暗号化とは異なり、ハッシュは復号化できず、一方向です。また、異なる2つの文字列が同じハッシュ値を取得する可能性があり、ハッシュ値の一意性を保証することはできません。
MD5関数で処理されたハッシュ値は基本的に解読できませんが、可能性は常にあり、インターネット上にはMD5のハッシュ辞書も存在します。
mcryptを使用したデータ暗号化
MD5ハッシュ関数は読み取り可能な形式でデータを表示できますが、ユーザーのクレジットカード情報を保存する場合は、暗号化処理を行ってから保存し、後で復号化する必要があります。
最良の方法はmcryptモジュールを使用することです。このモジュールには30以上の暗号化方式が含まれており、暗号化した人だけがデータを復号化できることを保証します。
<?php
$data = "Stuff you want encrypted";
$key = "Secret passphrase used to encrypt your data";
$cipher = "MCRYPT_SERPENT_256";
$mode = "MCRYPT_MODE_CBC";
function encrypt($data, $key, $cipher, $mode) {
// Encrypt data
return (string)
base64_encode
(
mcrypt_encrypt
(
$cipher,
substr(md5($key),0,mcrypt_get_key_size($cipher, $mode)),
$data,
$mode,
substr(md5($key),0,mcrypt_get_block_size($cipher, $mode))
)
);
}
function decrypt($data, $key, $cipher, $mode) {
// Decrypt data
return (string)
mcrypt_decrypt
(
$cipher,
substr(md5($key),0,mcrypt_get_key_size($cipher, $mode)),
base64_decode($data),
$mode,
substr(md5($key),0,mcrypt_get_block_size($cipher, $mode))
);
}
?>
mcrypt関数には次の情報が必要です:
1、暗号化するデータ
2、データの暗号化と復号化に使用するキー
3、ユーザーが選択したデータ暗号化の特定アルゴリズム(cipher:例:MCRYPT_TWOFISH192,MCRYPT_SERPENT_256, MCRYPT_RC2, MCRYPT_DES, and MCRYPT_LOKI97)
4、暗号化に使用するモード
5、暗号化のシード。暗号化プロセスを開始するために使用するデータで、暗号化アルゴリズムを初期化するための追加のバイナリデータです。
6、暗号化キーとシードの長さ。mcrypt_get_key_size関数とmcrypt_get_block_size関数を使用して取得できます。
データとキーの両方が盗まれた場合、攻撃者はciphersを走査して解読する方法を見つけることができてしまうため、暗号化キーをMD5で一度ハッシュ化して安全性を保証する必要があります。また、mcrypt関数が返す暗号化データはバイナリデータであるため、そのままデータベースのフィールドに保存すると他のエラーを引き起こす可能性があります。そこでbase64encodeを使用してこれらのデータを16進数に変換し、保存しやすくしました。
参考文献:http://www.codeproject.com/Articles/363897/PHP-Security
記事の出典:http://blog.csdn.net/u010487568/article/details/29828757