プログラミング

PHP のセキュリティーに関する考察

2006年9月29日

 一般的なこととして、PHP製のソフトウエアに関するセキュリティーに関して、考えてみたい。さまざまなセキュリティーホールのうち、SQLインジェクションとクロスサイトスクリプティングを取り上げ、主にサーバーの設定を中心に考察してみる。

この記事は、随時編集する可能性があります。また、疑問などありましたら、ご指摘いただければ恐縮です。

データベースとしてMySQLの使用を前提にしています。


1. はじめに

 PHP のセキュリティーを考える上でまずはじめに見ておきたいのが、register_globalsとmagic_quotes_gpc(及びmagic_quotes_sybase)である。ここでは、これらの設定の推奨値を

register_globals=off
magic_quotes_gpc=on
magic_quotes_sybase=off

としたい。これらの値の設定値は、次の検証用PHPスクリプトで確認することができる。

検証用スクリプト(check.php)
<?php
    include('./config.php');
    if (!($member->isLoggedIn() && $member->isAdmin())) exit;
?><html><head><meta http-equiv="Content-Type" content="text/html; charset=EUC-JP" />
</head><body><?php
    if (ini_get('register_globals')) $rg='on'; else $rg='off';
    if (ini_get('magic_quotes_gpc')) $mq='on'; else $mq='off';
    if (ini_get('magic_quotes_sybase')) $mqs='on'; else $mqs='off';
    
    echo "register_globals=$rg<br />\n";
    echo "magic_quotes_gpc=$mq<br />\n";
    echo "magic_quotes_sybase=$mqs<br />\n";
    
    echo '$param='.htmlspecialchars($param)."<br />\n";
    echo '$_GET[\'param\']='.htmlspecialchars($_GET['param'])."<br />\n";

?></body></html>
(注意:Nucleus を使用している場合のスクリプトです。Nucleus を使用していない(あるいは、何のことか分からない)場合は、<html>より前のPHPスクリプト(<?php ... ?>)を削除してください。Nucleus を使用している場合、このスクリプトをconfig.phpと同じディレクトリに置いて、Nucleus にログインしてから使用してください。)

 ブラウザでこのスクリプトにアクセス(http://your.site.com/your/path/check.php 等)してみた場合、次のように表示されればOKである。

register_globals=off
magic_quotes_gpc=on
magic_quotes_sybase=off
$param=
$_GET['param']=

 もし上記のようにならなかった場合、.htaccessを次のように記述(もしくは、記述を追加)すれば、変更されることが期待される。

php_flag register_globals off
php_flag magic_quotes_gpc on
php_flag magic_quotes_sybase off
(変更できない場合は、『php.iniを書き換える』・『Apacheの設定を変えてphp_flagディレクティブを使用可能にする』・『サーバ管理者と相談する』などの方法で変更することが可能です。)

2.register_globalsについて

 PHPマニュアルでは、register_globalsについて次のように記載されている。

グローバル変数の登録機能の使用法

PHPの変更点で最も議論の対象となったのは、おそらく、PHP 4.2.0において PHPのディレクティブ register_globalsが デフォルトでONからOFFに変更された時でしょう。 当時、このディレクティブに依存することが一般的であり、多くの人は、 このパラメータの存在すら知らず、PHPの動作そのものであるというよう に考えていました。このページは、このディレクティブにより安全でな いコードを書く可能性があるということをこのページで説明しますが、 このディレクティブそのものが安全でないわけではなく、これを誤って使 用することが安全でないということを念頭においていて下さい。


また、php.iniには、次のように記述されている。

; You should do your best to write your scripts so that they do not require
; register_globals to be on;  Using form variables as globals can easily lead
; to possible security problems, if the code is not very well thought of.
(あなたがスクリプトを書くときは、ベストを尽くしてregister_globals を onにする必要がないようにするべきである。あまりうまく考えられていないコードを利用してフォームの送信をグローバル変数で処理すれば、セキュリティーに関する問題が簡単に発生しうる。)


 単刀直入にいえば、よく分からない場合は register_globals の値は、off にしておかなければならず、on にした場合に予期しないセキュリティーホールが発生する可能性が排除できないということである。別の言い方をすれば、on にしても絶対大丈夫だと確信が持てる場合にのみ、on にするべきであるということだ。
 従って、自分で作成したのではないスクリプトを使用する場合、

1.スクリプトをくまなく調べて、register_globals=on でも安全であるかどうかを検証する。
2.register_globals=off とする。

といった2種類の対応が可能であるが、もしそのスクリプトがregister_globals=off にも対応しているのであれば迷わずoffにするべきであろう。
 今日出回っているほとんどのPHPソフトウェアが、register_globals=offに対応している。また、自分でスクリプトを作成する場合でも、register_globals=off でそのスクリプトが動くようにすることが強く推奨されている(php.iniを参照)。従って、register_globals=onとするのは、よほど特殊な環境(かなり以前に書かれたPHPソフトウエアを利用するなど)のみに限りたい(その場合、php.iniではregister_globals=offに設定し、必要なソフトウエアでのみregister_globals=onとなるように.htaccessを設置することが望ましい)。

3.magic_quotes_gpcについて
 
 PHPマニュアルでは、magic_quotes_gpcについて次のような記述がある。

なぜマジッククオートを使用するのか

PHPに実装されたマジッククオートは、初心者により書かれたコードを危険から 守る手助けとなります。 マジッククオートをonにした場合でも SQLインジェクション は可能ですが、そのリスクは減少します。


また、次のようにも記述されている。

なぜマジッククオートを使用しないのか

1.これがonであることを仮定すると、移植性に影響します。

2.エスケープされたデータが全てデータベースに挿入されるわけではないので、 このように全てのデータをエスケープすることは性能を低下させます。

3.しばしば、 エスケープするべきではないデータまでエスケープされてしまう問題に 悩まされることになります。


 つまり、私を含めたプロでない人間にとっては、初心者としてマジッククオートの使用が推奨されるということになる。使用しない理由について、1と3はプログラミングに関することであり、ソフトウエアを使用する場合だけのときは関係がない。2については、自分が使用するサイトによほどのアクセスが集中する場合でない限り、無視してよかろう。むしろ、少々性能が低下しても、セキュリティー的により安全なほうがよいと思う。

4.magic_quotes_sybase について

 このオプションは、データベースとしてSybaseタイプのエスケープが必要なもの(SQLiteなど)を利用している場合にオンにするべきものである。MySQLをデータベースとして使用している場合は、オフにしておかなければならない。magic_quotes_sybase=onの場合、たとえmagic_quotes_gpc=onであっても正しくエスケープされないことによるセキュリティーホールが発生する場合がある。

5.register_globals=offでセキュリティーホールが塞がれることがある。

 今までのところで、PHPの設定として

register_globals=off
magic_quotes_gpc=on
magic_quotes_sybase=off

が推奨されることを示した。以降、この設定がなされていると仮定して話を進めることにする。さて、先の検証プログラムに再度アクセスしてみる。今度は、URLとして
http://your.site.com/your/path/check.php?param=test
を指定してみよう。次のように表示されるはずである。

register_globals=off
magic_quotes_gpc=on
magic_quotes_sybase=off
$param=
$_GET['param']=test

4行目の『$param=』に注目したい。空文字になっている。もしregister_globals=onになっていると、次のように表示される。

register_globals=on
magic_quotes_gpc=on
magic_quotes_sybase=off
$param=test
$_GET['param']=test

『$param=test』となっている。このことが、セキュリティーホールを生み出す原因となりうる。

 たとえば、次のようなPHPスクリプトを考えてみる。

スクリプトShowInfo.phpの内容
<?php
####################省略####################
if (_authorized()) $username=_quotedUserName();
if ($username) {
    $query="SELECT * FROM user_data WHERE name='$username'";
    $res=mysql_query($query);
####################省略####################    
}
####################省略####################
?>

これは、ユーザー認証を行い、認証されたユーザーが接続しているときにのみ個人情報を表示するということを意図したスクリプトである。かなり行儀の悪いものであるが、もしregister_globals=offとなっていれば、このスクリプトは意図したように働く。つまり、_authorized()関数により認証された場合にのみ $username 変数がNULLでなくなる(_quotedUserName()関数の戻り値が代入される)ため、個人情報が表示されることになる。ところが、register_globals=onの環境では、大変なことになる。たとえば、『ShowInfo.php?username=admin』にアクセスすると、$username変数に『admin』が代入された状態でこのスクリプトが実行されるために、管理者(ユーザーネームがadminと仮定)の個人情報が表示されてしまうのである。さらにregister_globals=on, magic_quotes_gpc=offの環境下ではもっとひどいことが起こりうる。$username変数がエスケープされないままSQLクエリーに渡されることによりSQL インジェクションが可能になり、全データの閲覧などができてしまうのである(SQL インジェクションの詳細については、この記事では割愛します)。
 ここで注目したい点は、register_globals=offの条件下では、これはセキュリティーホールにならないこと。もっと複雑なスクリプトでも、register_glolbals=onの条件でのみセキュリティーホールが発生することは、しばしばある。『register_globals=onの条件で脆弱性が…』というようなセキュリティーに関する報告をよく見かけるのは、そのためである。

6.magic_quotes_gpc=onでセキュリティーホールが塞がれることがある。

 さて、3たびcheck.phpにアクセスしてみる。今度は、URLとして
http://your.site.com/your/path/check.php?param="test"
を指定してみよう。次のように表示されるはずだ。

register_globals=off
magic_quotes_gpc=on
magic_quotes_sybase=off
$param=
$_GET['param']=\"test"\
(ノート:バックスラッシュ(╲)と円記号(¥)は、等価です。)

5行目の『$_GET['param']=\"test"\』に注目していただきたい。もしこれを、magic_quotes_gpc=offの条件下で表示させると、次のようになるだろう。

register_globals=off
magic_quotes_gpc=off
magic_quotes_sybase=off
$param=
$_GET['param']="test"

『$_GET['param']="test"』と表示された。このように、magic_quotes_gpc=onの条件下では、特殊な記号にバックスラッシュ(もしくは円記号)が付加されている(エスケープされている)事が分かる。このことがセキュリティー問題と関係してくる。

 以下の例は、SQL インジェクション対策がなされていないスクリプトの典型例である。

<?php
####################省略####################
$username=$_GET['username'];
if (!$username) exit('ERROR');
$query="SELECT * FROM user_data WHERE name='$username'";
$res=mysql_query($query);
####################省略####################    
?>


magic_quotes_gpc=offの環境下では、エスケープされていない文字列がそのままSQLクエリーに導入されているので、簡単にSQLインジェクションがおきてしまう。対策としては、たとえば『$username=addslashes($username);』という一行を入れればよい。他方、magic_quotes_gpc=onとしておけば、同様の処理(addslashes)が自動的になされるので、SQL インジェクションは起こらない。(注意:文字コードがShift-JISの際の問題は複雑なので、ここでは考慮していません。

 クロスサイトスクリプティングに関しても、同様である。たとえば次のようなコードがあるとする。

faq.phpの内容
<?php
####################省略####################
$uri=$_SERVER['REQUEST_URI'];
echo "<a href=\"$uri#FAQ\">FAQ</a>";
####################省略####################
?>
<a name="FAQ"></a>
####################省略####################

ここで、magic_quotes_gpc=offの条件で、『?"onclick="alert('XSS');"title="』にアクセスし、『FAQ』リンクを押すと、

XSS

のように予期しないダイアログが表示される。つまり、意図しないjavascriptが実行されてしまうわけである。同じことを、magic_quotes_gpc=onの条件で行っても、意図したリンク先(faq.php#FAQ)に飛ばないという不具合があっても、ダイアログが表示されることはない。

 これらの例のように、一部のクロスサイトスクリプティング脆弱性は、magic_quotes_gpc=onと設定することで防げることが分かる。

7.結論

 ずばり、

register_globals=off
magic_quotes_gpc=on
magic_quotes_sybase=off

としておけば、SQLインジェクションやクロスサイトスクリプティングのすべてではないが、かなりを防げるということである。昨今のPHPソフトウエアは、この設定条件で働くものがほとんどなので、特別の事情がない限りこの設定にしておくべきだと思う。自分の使用しているソフトにセキュリティーホールが報告されても、まずそれがregister_globals=offやmagic_quotes_gpc=onの条件下でも起こりうる脆弱性なのかどうかをチェックし、必要であればアップグレードするなどの適切な処置を行うようにしたい。

コメント

藤咲 (2006年10月25日 18:08:54)

magic_quotes_gpcはONかOFFかというのは難しい問題と思いますが、私としては基本OFFではないかと思います。
そのあたりは以下の話からの流れが参考になるかと思います。
http://lists.sourceforge.jp/mailman/archives/maple-user/2006-April/000259.html

基本をONとして考えてしまうと、作成側で「ON」であることを前提としたスクリプトが作成されてしまい、それは「OFFであることを前提としてしまって動かないスクリプト」よりも重大な問題を抱えることになります。

ONであっても問題がないと考えるケースは
・セキュリティホールがあるスクリプトを運用するのに塞がれるまでの一時的対処としてONとする
・自分で作成したスクリプト以外は動かさず、かつそのスクリプトは公開しない
の2点ではないかと思います。
それ以外の公開するスクリプトはできるだけOFF環境でセキュリティホールをなくすべく書くべきだと思います。

それでは万が一のセキュリティホールがあった場合に防ぐことはできないというのは確かにありますが、私のような初心者PHPスクリプト作者が公開するスクリプトを作成する場合どのケースが一番簡単かと考えるなら、以下のようになると思います。
1.ONを前提として作成する
 →OFFの場合に重大なセキュリティホールがあるため、結局OFFの場合の処理を作成せざるを得ない。
2.OFFを前提として作成する
 →漏れによるセキュリティホールができる場合がある。ONの場合に動かない。
つまり、環境を選べない以上絶対にOFFでの処理を作成せざるを得ないなら、そもそもOFF環境だけをサポートして、そちらに注力した方がいいんじゃないかなぁと思ったりするんですが、それはちょっと乱暴ですかね…。
なんか言いたいことがまとまってなくてすみません。

Katsumi (2006年10月25日 23:21:33)

 藤咲さん、コメントどうもありがとうございます。おっしゃるようにmagic_quotes_gpcの設定の問題は難しいのですが、ここでの議論としてはユーザ側の設定として考察しました。ソフトを使う側としては、もしエラーが出ないのならば ON がいいかなと。エラーが出てしまうのならば、OFFにすることも考えなければなりませんが。
 逆に、ソフトを開発する側の考え方は別です。私見としては、先ず第一にmagic_quotes_gpcがOFFでもセキュリティーホールが入らなくする事が第一条件。第二に、ONでもOFFでもエラー無く動作することが求められると思います。Nucleus のプラグインの場合だと、requestVar などを利用してプログラムするのがよいでしょうね。
 ソフト開発者としての考察は、後日記事にする予定です。

(2009年1月7日 12:34:03)

常にOffにしておきたい人向けのコードです。
http://pentan.info/php/magic_quotes_on.html

コメント送信