サイトを安全に!PHPでcsrf対策を行う方法【初心者向け】

初心者向けにPHPでcsrf対策を行う方法について解説しています。指定したフォームなどから投稿しているかどうか確認するのがcsrf対策になります。Webアプリケーションのセキュリティを安全にするためにも覚えておきたい知識です。

TechAcademyマガジンはオンラインのプログラミングスクールTechAcademy [テックアカデミー]が運営する教育×テクノロジーのWebメディアです。初心者でもすぐ勉強できる記事が2,000以上あります。

PHPでcsrf対策を行う方法について解説します。

csrfは、Webアプリケーションの脆弱性・またそれを利用した悪意のある攻撃のことですが、サイトの知名度があればあるほど攻撃されることも多々あります。

セキュリティを維持するためにも知っておきたい知識です。

 

なお本記事は、TechAcademyのPHP/Laravel講座の内容をもとに紹介しています。

 

田島悠介

今回は、PHPに関する内容だね!

大石ゆかり

どういう内容でしょうか?

田島悠介

PHPでcsrf対策を行う方法について詳しく説明していくね!

大石ゆかり

お願いします!

 

csrfとは

CSRF(クロスサイトリクエストフォージェリ)とは、Webアプリケーションの脆弱性・またそれを利用した悪意のある攻撃を指します。

どんな攻撃か具体例を出してみますね。

仮にhoge.comというサイトにログインしている状態で、下記のリクエストを行うと指定したsend_user_idに指定したpointを送れるとします。

リクエスト先: http://hoge.com/send_point.php
メソッド: POST
パラメータ:
    send_user_id ・・・ ポイントを送付するユーザID
    point ・・・ 送付するポイント数

悪意のある攻撃者が用意したページで下記のフォームがあるとします。

もし、hoge.comにログインしている状態のユーザが下記のボタンを押してしまうと、攻撃者に自分のポイントを送付してしまいます。

もちろん、hoge.comサイトがCSRF対策を行っている場合は、下記のボタンを押しても不正なリクエストとして攻撃を防げます。

<form method="POST" action="http://hoge.com/send_point.php">
    <input type="hidden" name="send_user_id" value="攻撃者のユーザID">
    <input type="hidden" name="point" value="1000">
    <input type="submit" value="押してね♪">
</form>

 

csrf対策のための書き方

現在推奨されている対策としてはワンタイムトークンを利用する方法になります。

Laravelなどの有名なフレームワークもCSRF対策にはワンタイムトークンを採用しているため、これを抑えておくだけで問題はないと思います。

ワンタイムトークンの実装方法は、まず投稿フォームのページを表示する前にランダムな文字列(トークン)を生成し、ユーザーのセッションに保存します。

そして、下記のようにフォームに生成したトークンをhiddenで埋め込みます。

リクエスト先では、セッションに保存したトークンと送信されたトークンが一致するか確認を行い、一致しない場合は不正なリクエストとして扱います。

これで攻撃者は生成されるトークンの値を知りうることができないため、CSRF攻撃ができなくなります。

フォームを表示する前の処理

// ランダムな文字列を生成してセッションに設定
$csrf_token = ランダムな文字列;
$_SESSION['csrf_token'] = $csrf_token;

 

フォーム

<form method="POST" action="http://hoge.com/send_point.php">
    <input type="hidden" name="csrf_token" value="<?=$csrf_token?>">
    <input type="text" name="send_user_id">
    <input type="text" name="point">
    <input type="submit value="ポイントを送付する">
</form>

 

リクエスト先(send_point.php)

// 先に保存したトークンと送信されたトークンが一致するか確認します
if (isset($_POST["csrf_token"]) 
 && $_POST["csrf_token"] === $_SESSION['csrf_token']) {

 echo "正常なリクエストです";

 ・・・後続処理 ・・・

} else {
 echo "不正なリクエストです";
}

 

[PR] PHPのプログラミングで挫折しない学習方法を動画で公開中

実際に書いてみよう

先に挙げたワンタイムトークンの実装例を元にCSRF攻撃を防ぐ実装を行ってみましょう。

前提として下記のコードは「send_point_form.php」というファイルに保存されており、「http://hoge.com/send_point_form.php」に対応しているとします。

send_point_form.php

<?php
 // ログインした状態と同等にするためセッションを開始します
 session_start();

 // 暗号学的的に安全なランダムなバイナリを生成し、それを16進数に変換することでASCII文字列に変換します
  $toke_byte = openssl_random_pseudo_bytes(16);
  $csrf_token = bin2hex($toke_byte);
  // 生成したトークンをセッションに保存します
  $_SESSION['csrf_token'] = $csrf_token;
?>

 <form method="POST" action="http://hoge.com/send_point.php">
 <input type="hidden" name="csrf_token" value="<?=$csrf_token?>">
 <input type="text" name="send_user_id">
 <input type="text" name="point">
 <input type="submit" value="ポイントを送付する">
 </form>

下記のファイルはリクエスト先となり、トークンがセッションに保存されたトークンと一致するかチェックします

 

send_point.php

<?php
// POSTでcsrf_tokenの項目名でパラメーターが送信されていること且つ、
// セッションに保存された値と一致する場合は正常なリクエストとして処理を行います
if (isset($_POST["csrf_token"]) 
 && $_POST["csrf_token"] === $_SESSION['csrf_token']) {

 echo "正常なリクエストです";

 // TODO
 // 指定されたsend_user_idに自分のポイントからpointを移す処理を行います
        
 } else {
  echo "不正なリクエストです";
 }
?>

 

参考

php.net-openssl
php.net-bin2hex

この記事を監修してくれた方

青木 敦史(あおきあつし)
昼間は自社のWebサービスを運営している会社でフロントエンド/バックエンドを担当しているエンジニア。

Webエンジニアの経験は5年ほどです。TechAcademyではPHP/Laravelコースを担当しています。
開発実績: メイクレッスン支援アプリ / 電力自由化パッケージ / 携帯電話料金計算 / ライブチャットサービス

 

大石ゆかり

CSRF対策って何でしょうか?

田島悠介

簡単に考えると、指定のフォームを利用して投稿しているかどうか確認する方法なんだよ。

大石ゆかり

別のサイトに書かれているURLをクリックして投稿したりするのを防ぐためなんですね!

オンラインのプログラミングスクールTechAcademyではオンラインブートキャンプPHP/Laravelコースを提供しています。

PHPやフレームワークのLaravelを使ってWebアプリケーションの開発を学ぶことができます。

現役エンジニアがパーソナルメンターとして受講生に1人ずつつき、マンツーマンのメンタリングで学習をサポートし、最短4週間で習得することが可能です。

また、現役エンジニアから学べる無料のプログラミング体験会も実施しているので、ぜひ参加してみてください。