2013/09/13

PHP開発エンジニア必読!最低限必要なセキュリティ対策

PHP開発エンジニア必読!最低限必要なセキュリティ対策

システムを開発する前は「ユーザーに便利に使ってもらいたい」「使いやすいUIを取り入れて簡単に操作できるようにしたい」などとユーザーのために開発を始めると思います。

しかし開発を始めると悪意のあるユーザーの事も考える必要があります。

悪意のあるユーザーがありとあらゆる手段でシステムを乗っ取り、改ざん、破壊しようと試行錯誤してくる事を前提とし、その対策をする必要があります。

今回はシステムを開発するにあたって知っておいて欲しい攻撃手法とセキュリティ対策を紹介します。

目次

■クロスサイトスクリプティング(XSS)

サイトをまたいでスクリプトを実行させる、という意味合いです。
XSSは攻撃者から直接アクセスがあるわけではありません。
攻撃者が用意したサイトにアクセスした一般ユーザーが悪意のある引数を持たされて、攻撃対象サイトに飛ばされてきます。
XSSの攻撃対象は、ユーザーから入力されたデータをそのまま返すサイトです(メールフォーム入力後の確認画面を表示させるなど)。

サンプルコード

・脆弱サイト (zeijyaku.php)

<form action="aa.php" method="POST">
お名前を入力してください<input type="text" name="お名前">
<input type="submit" value="送信">
</form>
<?php 
echo "名前:".$_POST['お名前'];

・攻撃者が用意したサイト (kogeki.php)

<form action=" zeijyaku.php" method="POST" name ="warui_form">
<input type="hidden" name="お名前" value='<script>alert("悪意のあるjavascript");</script>'>
</form>
<a href="" onclick="document.warui_form.submit();return false;" >小銭稼ぎたい方はこちらをクリック!</a>

・攻撃者の用意したサイトのリンクから脆弱サイトに飛んだ時に返ってくるソース

<form action="aa.php" method="POST">
<input type="text" name="お名前">
<input type="submit" value="送信">
</form>
名前:<script>alert("悪意のあるjavascript");</script>

ユーザーがこのリンクをクリックしてしまうと、脆弱サイトとユーザーのやりとりの間に、攻撃者が用意した悪意のあるスクリプトが入ってしまいます。

その悪意のあるスクリプトによりユーザーのクッキー情報や入力情報を攻撃者に送ったり、意図しない書き込みをさせられたりする可能性があります。

クロスサイトスクリプティング(XSS)攻撃イメージ

■クロスサイトスクリプティング(XSS)対策方法

今回のサンプルでは脆弱サイトが、ユーザーから送られてきたデータをそのままユーザーに返しているのが問題です。
プログラム上意味のある特殊文字文字参照形式に書き換える事によってプログラムとして実行されないようにしましょう。

特殊文字文字参照
「&」 &amp;
「"」 &quot;
「'」 &#039;(あるいは &apos;)
「<」 &lt;
「>」 &gt;

PHP関数htmlspecialchars()を使うと一括で変換してくれます。

今回の場合は
$_POST['お名前']」を
htmlspecialchars($_POST['お名前'])
とするとユーザーのPCに送られるソースは

「名前:&lt;script&gt;alert(&quot;悪意のあるjavascript&quot;); &lt;/script&gt;」となります。

ブラウザで文字参照形式の記述を特殊文字に戻して表示してくれますのでブラウザでの表示は

「名前:<script>alert("悪意のあるjavascript");</script>」

となります。

こうする事によって表示を崩さないままスクリプトを実行されないようにできます。

もう一つの対策として、今回入力してもらうデータは名前なので30文字以上ならエラーを出すなど、のチェックをしましょう。

最近はサーバーでもXXS対策をしてくれているところも多いですがセキュリティーは何重にもかけておきましょう。

■クロスサイトリクエストフォージェリ(CSRF)

CSRFシーサーフとも呼ばれます)とは言葉の通り、サイトをまたいでリクエストを偽装する攻撃方法です。
上記のクロスサイトスクリプティング(XSS)とも似ているところがあります。
攻撃者の用意したサイトにアクセスすると、imgタグのsrcリクエストなどから攻撃者の用意した第二のサイトにもアクセスした事になり、更にそのサイトから攻撃対象となる脆弱サイトへ意図しないデータを送るという仕組みです。

サンプルコード

・攻撃者が用意したサイト1 (hello.php)

<img src=" kogeki.php">

・攻撃者が用意したサイト2 (kogeki.php)

<body onload="document. kogeki.submit();">
    <form name=" kogeki " method="post" action="http://zeijaku.com/ ">
        <input type="hidden" name="日記本文" value="猫大好き">
    </form>
</body>

hello.phpにアクセスしただけでhttp://zeijaku.com/のサイトにユーザーのリクエストを偽装されてデータを送信されてしまします。

クロスサイトリクエストフォージェリ(CSRF)攻撃イメージ

■クロスサイトリクエストフォージェリ(CSRF)対策方法

CSRFの対策にはトークンが有効です。
トークンとはユーザーとサイトの間で決めた合言葉のようなものです。
サイトがユーザーに日記本文を書き込むフォームのソースを送る時

<form name=" kogeki " method="post" action="http://zeijaku.com/ ">
  <input type="text" name="日記本文">
  <input type="hidden" name="トークン" value="毎回変わる秘密の暗号2828">
</form>

のようにトークンというユーザー認証暗号をフォームに組み込みます。
ユーザーからデータが送信された場合トークンが一致しなければCSRFの可能性が高くなります。
何故ならこの場合攻撃者はサイトからデータを送信する事は容易にできても、データを受け取る事は出来ないので、トークンの内容は把握しようがないからです。

■SQLインジェクション

SQLインジェクションとは、本来のSQL文にの実行させたい追加文を注入(インジェクション)してSQL文を変更させてしまう攻撃です。
ログイン確認の時に、ユーザーが入力したIDとパスワードをMySQLのデータと比較するシステムはよくあると思います。
攻撃者に注入されたSQL文のせいでIDとパスワードが間違っている場合でもマッチしてしまう事があります。
攻撃対象はユーザーからの入力情報をそのままSQL文に組み込んでしまっているシステムです。

サンプルコード

SELECT * FROM login_database userid = '$id' AND userpass = '$pass'

ユーザーの入力を受ける変数の値が

$id = 'ユーザー';
$pass = '猫大好き';

だった場合

SELECT * FROM login_database userid = 'ユーザー' AND userpass = '猫大好き'

というSQL文が実行されます。
返り値によってIDとパスワードが合っていたかどうか判断できます。

このシステムにSQLインジェクションを仕掛ける場合はこうなります。

$id = "";
$pass = "' OR 'うさぎ' = 'うさぎ'";

このような値を入力すると

SELECT * FROM login_database userid = '' AND userpass = '' OR 'うさぎ' = 'うさぎ'

このようなSQL文になります。
インジェクトされた
「OR "うさぎ" = "うさぎ"」
は常に正なのでIDとパスワードが合っていた時と同じ値が返ってきます。

対策として「'をエスケープする方法があります。
''」とシングルクオートの前にシングルクオートを付けてエスケープするとシングルクオートを文字として扱えるようになります。

しかし、セカンドオーダーSQLインジェクションという攻撃方法もあります。
あらかじめIDを登録する際に文字列にエスケープされたシングルクオート「''」を付けて「'」を文字列に紛れ込ませ、それを利用してSQLインジェクションを成功させる方法です。

更にSQLインジェクションはシングルクオートを使わない方法もあります。

SELECT * FROM login_database userid = '$id' AND userage > $age

のように文字ではなく数字で比較するSQL文の場合シングルクオートで囲みません。

$age = "33 UNION ・・・"

と値を入れると

SELECT * FROM login_database userid = '' AND userage > 33 UNION ・・・

となりUNION以降のSQL文も実行される事になります。
攻撃者の好きなSQL文を実行させてしまうなんて考えただけでも恐ろしいです。

他にはSQLで「以下コメントアウト」を意味する「--」を使う方法や、
マルチバイトの1バイト文字を使ってエスケープを回避する方法などがあります。
これからも新しい方法が出てくる可能性も十分あります。

■SQLインジェクション対策方法

システムは重くなりますがデータベースのIDとパスワードを全て変数に格納して、それをmcrypt_genericなどで暗号化したものと、ユーザーの入力した文字列を同じ方法で暗号化したものとを比較するのが一番確実です。

他には

  • ・SQL文に組み込む変数すべの特殊文字を適切にエスケープする。
  • ・数字なのか文字列なのか、何文字までなのかなど変数の値をチェックする
  • ・$str = mb_convert_encoding($str, "SJIS", "SJIS")を実行してマルチバイトの1バイト文字を消す。
  • ・サーバーにWAF(Webアプリケーションファイヤーウォール)を入れる

なども有効です。

■クリックジャッキング

クリックジャッキングとは言葉の通りクリックするボタンを乗っ取る攻撃方法です。
その手順は

  1. 攻撃者のページはIframeなどで他のページを表示させます。
  2. そのページに重ねて悪意のあるボタンをそのページと同じ配置で設置します。
  3. そして設置した悪意のあるボタンをcssで透明にします。
  4. ユーザーは上に重なっている透明のボタンを気付かずにクリックしてしまいます。

そのボタンでJavaScriptを実行させられたり、元のページの他のボタンの上に、そのページの「データ消去ボタン」を重ねて表示させられる恐れもあります。

もし直接被害に合わなかったとしてもクリックジャッキングの背景に自分のサイトが使われたとしたら、確実に信用が失われます

■クリックジャッキング対策方法

クリックジャッキング対策方法の対策として他のページのIframe内にサイトを表示させない、というのが有効です。

サーバーの.htaccessに
Header set X-FRAME-OPTIONS "DENY"
と記述すると他のサイトのIframe内に表示されなくなります。

もしくはPHPファイルに直接
header('X-FRAME-OPTIONS:DENY')
と記述しても同じです。

■その他にも覚えておいて欲しい攻撃方法

PHPのバージョンが進むにつれてセキュリティの問題もどんどん解決してきています。
以下の攻撃の被害は最近では少なってきましたが、どういう攻撃の方法があるのかをご紹介します。

HTTP レスポンス分割攻撃

これもヘッダで読み込む変数に悪意のあるデータを入力する攻撃方法です。

header( 'Location: http://zeijaku.com/index.php?userid=' . $id . '/' );

$id="a%0d%0a Location:%20http://kogeki.com/"

という値が渡された時、意図しない先にジャンプしてしまいます。 対策として二つあります。

  • ・「CR,LF(\r\n)」が含まれていないかチェックする。
  • ・PHP 4.4.2 と PHP 5.1.2において一度に複数のヘッダを送信できないようになりましたのでPHPのバージョンを最新のものにする

NULL バイト攻撃

PHPの関数の中にはNULLバイト文字(\x00 , \0)を正しく扱えないバイナリセーフでない関数がいくつかあります。

$filename="kogeki.php¥0zeijaku";
If(preg_match( '/zeijaku$/', "filename" )){
	readfile($_GET['filename']);
}

この場合1行目の拡張子がzeijakuかどうかのチェックを通ってしまい、
3行目のreadfileではNULLバイト文字以降を読まないのでkogeki.phpとして実行されてしまいます。

対策方法は

$filename =str_replace( "\0", "",$filename );

str_replaceを実行してNULLバイト文字を消すのが有効です。

Email ヘッダ・インジェクション

メール送信関数のヘッダにユーザーから入力されたデータをそのまま使った場合、意図しないヘッダ情報を追加される恐れがあります。

$header = 'Form: ' . $_POST['form'] ;
mail( $to, $subject, $message, $header );

のコードに

$_POST['form'] = 'zeijyaku@mail.com\nbcc:kogeki@mail.com\n';

というデータが入っていた場合bbcに指定したアドレスにもメールを送信されてしまいます。
対策方法は正規表現をつかってメールアドレスのデータだけなのかどうかをチェックする事です。

JavaScriptの正規表現をまとめた記事がありますので参考にしてください
イベント処理とフォームを使ってユーザーの入力を最適化する方法 JavaScript初級編

■まとめ

攻撃方法はどんどん進化していく中、システムを作る側の責任として常にありとあらゆる攻撃を想定してその対策をしていかなければなりません。
ユーザーから入力された情報を処理する場合には細心の注意が必要です。

一般的に知られているセキュリティは汎用性を重視していますので最低でもやっておき、更に自分で開発しているシステムに合ったセキュリティも手を抜く事なく追加しましょう。

例えば、名前を受け取るフォームからのデータは30文字以上は受け取らないようにしたり、年齢を受け取るフォームからは数字以外は受け取らないようにするなどです。

一見地味に見えますがどんな小さなセキュリティでも役に立たないセキュリティはありません。

普段から考えられるだけすべてのセキュリティを導入する癖を付けておきましょう。