トリッキーコードネット トップへ戻る   C/C++, Java, Perl, PHP, JavaScript, アルゴリズム, ショートコーディング, IOCCCコードの解説, 等々

サイト情報

トリッキーなコード

7行プログラミング

物凄いコード集

アルゴリズム

データ構造

C/C++な話題

コードサンプル

ツール/環境構築

開発ノウハウ 等

ネタ/ジョーク集

おススメ書籍/サイト

サイトTOP >> コードサンプル >> CSRF対策2 - セッションチケット

CSRF対策2(セッションチケットによる対策)

セッションチケット

CSRF対策1で述べた「CAPTCHA(画像認証)を使用するCSRF対策」を、より簡素化したものがセッションチケットによる対策です。 流れとしては、 ・サーバ側で乱数を生成し、それをセッションに格納します。 ・フォームを表示する際(サーバがブラウザへ、フォーム描画用HTMLレスポンスを返す際)に、hidden値として、生成した乱数をセットします。 ・ユーザは特に何も意識せず、フォームをsubmitします。 ・サーバ側で、セッションに保存してある乱数と、hiddenからの入力文字列を比較し、正常なリクエストか否かを判定します。 CSRF対策(セッションチケット)動作フロー 何故これがCSRF対策になるのかというと、 正規ユーザが何らかの手違いでCSRFフォームを踏んでしまい、サーバへ悪意のあるリクエストが送信されてしまった場合でも、 CSRFによるリクエストの場合は、hiddenに値が入らない為です。 (↑江戸時代にあった「割り符の照合」と同じ考え方です^^;) 現代バージョンでは「チケット - 半券の照合」とでもいうべきでしょうか??w) CAPTCHAを使用する方法に比べ、 ・実装が簡単 ・サーバの不可が軽減 ・ユーザに余計なアクションを強要しない という長所がある反面、 CAPTCHAの長所である、「連続アクセス」への対応にはなりません。 CAPTCHAとの使い分けが重要になってきますが、例えばECサイトを運営する場合等は、 ユーザアカウントの作成時(ユーザ登録時):CAPTCHAを使用した認証 買い物かごへ商品を入れる際: セッションチケット にすると良いんじゃないかな~と思います。 ところで、CSRF対策用のPHPセッションクラスを作ってみたので、もしよろしければどうぞ~~。

CSessionクラス:

class CSession {

    /**
     * このシステム用の、$_SESSION一次キーを返す
     *
     * @param  void
     * @return string
     * */
    public static function getSessKey()
    {
        return 'hogehoge'; // ←ここだけ手直しをする。
    }

    /**
     * session_start()のラッパー。
     * セキュリティ対策上、できるだけSessionIDをCookieで渡すようにする。
     *
     * @param  void
     * @return void
     * */
    public static function sessionStart()
     {
         $userAgent = @trim( $_SERVER['HTTP_USER_AGENT'] );

         if (    preg_match('/kddi/i', $userAgent)
              || preg_match('/softbank/i', $userAgent)
              || preg_match('/docomo/i', $userAgent))
         {
             // 携帯はCookieが使えないとみなす。
         }
         else {
             // PC or SmartPhoneからのアクセス ⇒ SessionIDはcookieで渡す

             ini_set('session.use_only_cookies', 1);
         }

         session_start();
    }

    /**
    * CSRF対策用に発行したセッションチケットを取得
    *
    *  @param  string  チケットを発行した画面(アクション)名
    *  @return string  発行したチケット
    * */
    public static function getTicket( $wndname )
    {
        $ret = '';

        $ret = isset($_SESSION[CSession::getSessKey()]['csrf_ticket'][$wndname])
                    ? $_SESSION[CSession::getSessKey()]['csrf_ticket'][$wndname]
                    : '';

        return $ret;
    }

    /**
     * CSRF対策用のセッションチケットを発行
     * 新しいチケットを発行したら、それ以外の画面でのチケットは全て廃棄する。
     *
     * @param   string  チケットを発行する画面(アクション)名
     * @return  string  発行したチケット
     * */
    public static function csrfTicketIssue( $wndname )
    {
        $ret = '';

        unset($_SESSION[CSession::getSessKey()]['csrf_ticket']);
        $_SESSION[CSession::getSessKey()]['csrf_ticket']           = array();

        $ret = md5(uniqid(mt_rand(), true));
        $_SESSION[CSession::getSessKey()]['csrf_ticket'][$wndname] = $ret;

        return $ret;
    }

    /**
     * CSRF対策用のセッションチケットをチェック
     * チェックが終わったら、チェックを行ったチケットは廃棄する。
     *
     * @param   string  チケットを確認する画面(アクション)名
     * @param   string  POSTされてきたチケット
     * @param   bool    falseだと、チェックを行ったチケットを廃棄しない
     * @return  bool
     * */
    public static function csrfTicketCheck( $wndname, $ticket, $ticketClear = true)
    {
        $ret          = false;
        $sessTicket   = '';
        $postTicket   = '';

        $sessTicket   = CSession::getTicket($wndname);
        $postTicket   = @trim($ticket);

        if ($sessTicket == '' || $postTicket == '' || $sessTicket != $postTicket) {
            $ret = false;
        }
        else {
            $ret = true;
        }

        if ($ticketClear) {
            unset($_SESSION[CSession::getSessKey()]['csrf_ticket'][$wndname]);
        }

        return $ret;
    }
}

CSessionクラスメソッドの補足:

getSessKey: このスクリプトが使われているシステムでの、$_SESSION一次キーを返します。 (※もしかすると、同じドメインで複数のシステムを動かすかもしれない為) sessionStart: ユーザエージェントからアクセス元を判定(携帯 or 携帯以外)し、 携帯以外からのアクセスの場合は、use_only_cookiesを1にセットしてからsession_start()を呼び出します。 (※ use_only_cookiesを1にすると、セッションハイジャックに多少強くなるらしい) 携帯でも、最近の機種はCookieを使用できる為、 ユーザーエージェントによる条件分岐を、より細かくする必要があるかも・・・。

CSessionクラスの使い方:

フォーム側のスクリプト
<?php
require_once('./inc/CSession.php');
CSession::sessionStart();
$ticket     = CSession::csrfTicketIssue('フォームの識別子');
$ticketName = 'csrfticket' . substr($ticket, 0, 4);
?>

・・・

フォーム内で、
<input type="hidden" name="<?php echo $ticketName; ?>" value="<?php echo $ticket; ?>" />
フォーム値の受け取りスクリプト
<?php
require_once('./inc/CSessionInc.php');
CSession::sessionStart();
$ticket     = CSession::getTicket('フォームの識別子');
$ticketName = 'csrfticket' . substr($ticket, 0, 4);

if (! isset($_POST[$ticketName]) ) {
    // セッションチケットがない
    die('ERROR');
}

$postTicket = trim($_POST[$ticketName]);

if (! CSession::csrfTicketCheck('フォームの識別子', $postTicket)) {
    // セッションチケットが一致していない
    die('ERROR');
}
とします。
         このエントリーをはてなブックマークに追加   


作業効率化・ライフハックのオススメ記事




コンピュータ・テクノロジーのオススメ記事





恋愛・人間関係のオススメ記事




※ 当サイトは、トップページからリンクで辿る事の出来るページに限り、リンクフリーです。
※ 当サイトの閲覧/利用によって生じた如何なる損害も、当サイト管理人は責任を負いません。
※ 当サイトの内容を転載される場合は、当サイトへのリンクをお願い致します。