• PHP
【php8上級/準上級試験】模擬問題解説 問題19. CSRF (Cross-Site Request Forgery) random_bytes()

「第1回 PHP 8 上級 模擬試験」を解説していきます

本記事ではひたすら下記記事の模擬試験の解説をしていきたいと思います!

第1回 PHP 8 上級 模擬試験
https://study.prime-strategy.co.jp/study/ph8ex1/

解説記事一覧

模擬問題 19

推測困難なトークン に関する説明の中で、誤っているものを1つ選びなさい。
また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。

declare(strict_types=1);
error_reporting(-1);
下記はマニュアルから一部引用した内容である。

uniqid ( string $prefix = “” , bool $more_entropy = false ) : string
警告:この関数が生成する値は、暗号学的に安全ではありません。そのため、これを暗号として使ってはいけません。暗号学的に安全な値が必要な場合は、random_int() か random_bytes() あるいは openssl_random_pseudo_bytes() を使いましょう。

mt_rand ( ) : int
mt_rand ( int $min , int $max ) : int
警告:この関数が生成する値は、暗号学的に安全ではありません。そのため、これを暗号として使ってはいけません。暗号学的に安全な値が必要な場合は、random_int() か random_bytes() あるいは openssl_random_pseudo_bytes() を使いましょう。

mt_rand ( ) : int
mt_rand ( int $min , int $max ) : int
警告:この関数が生成する値は、暗号学的に安全ではありません。そのため、これを暗号として使ってはいけません。暗号学的に安全な値が必要な場合は、random_int() か random_bytes() あるいは openssl_random_pseudo_bytes() を使いましょう。

CSRF 等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において、random_bytes() は「暗号論的に安全な、疑似ランダムなバイト列を生成する」事が出来る。

そのため、以下のコード

var_dump( bin2hex( random_bytes(24) ) );

は正しく実行でき、結果は

string(48) "e5c471cbf4502eb90dbfbd2480f06a2d6af69ce4b0722b31"

となる (乱数の値は実行毎に変わる)。この値は「暗号論的にランダムなバイト列 (の 16 進表現)」なので、長さが十分であれば「推測 (予測) 困難なトークン」となり得る。

Webアプリケーションの脆弱性の一つです

ユーザーが意図しないリクエストを、強制的に送信させる攻撃

ターゲットユーザーには気づかれなず、別サイトに移動させられるのがファーストステップです

CSRF (Cross-Site Request Forgery) 攻撃シナリオ 正規ユーザー 正規サイト 悪意のある サイト CSRF対策 トークンベース • セッションごとのトークン • フォームにトークン埋め込み • リクエスト時に検証 SameSite Cookie • Strict設定 • Lax設定(推奨) • クロスサイト制限 追加の対策 • Refererチェック • 重要操作の再認証 • カスタムヘッダー

その後はクライアントサイドは正規のコンテンツに見えるが、別の状態で、利用しているサーバーへリクエストすることとなります。

CSRF攻撃の第二段階:隠されたリクエスト ユーザーの目に見える部分 可愛い猫の写真ギャラリー お得な商品情報 興味を引く記事コンテンツ 裏で実行される処理 <form action=”https://bank.example/transfer” method=”POST” style=”display:none”> <input type=”hidden” name=”amount” value=”10000″> <script> document.forms[0].submit(); </script> Cookieを含むリクエストが自動的に送信される → bank.example

正規アクセスとCSRF攻撃の情報の流れ 正規アクセス ユーザー ブラウザ 銀行サイト 1. 送金ページにアクセス 2. Cookie付きリクエスト 3. 送金フォーム + CSRFトークン 4. 送金実行(意図的な操作) CSRF攻撃 ユーザー ブラウザ 銀行サイト 1. 悪意のあるサイトにアクセス 2. 自動送信(Cookie付き) 3. リクエスト処理(CSRFトークンなし) 4. ユーザーは操作に気付かない

問題文の内容は正しい⭕です

CSRF 等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において、openssl_random_pseudo_bytes() は「疑似ランダムなバイト文字列を生成する」事が出来る。

古いシステムでない限り暗号学的に強いアルゴリズムを使って疑似乱数が生成される事が多いが、古いシステムなどでは「暗号学的に強くない」文字列である可能性もあり、それは第二引数によって知る事が出来る。

そのため、以下のコード

var_dump( base64_encode( openssl_random_pseudo_bytes(24, $flg) ) );
var_dump($flg);

は正しく実行でき、結果は

string(32) “OFUw6O7WR785Kg5YZbGZOKDwJzXRn60J”
bool(true)
となる (乱数の値は実行毎に変わる)。この値は、第二引数で与えた $flg の値が true であるため「暗号論的にランダムなバイト列 (の 16 進表現)」なので、長さが十分であれば「推測 (予測) 困難なトークン」となり得る。

random_bytes()

random_bytes()は暗号論的に安全な乱数を生成する関数として有効です。

乱数生成メソッドの比較 random_bytes() • 暗号論的に安全な乱数生成 • 予測困難な値を生成 • PHP 7.0以降で使用可能 非推奨の方法 • rand() – 予測可能な乱数 • mt_rand() – メルセンヌツイスターで予測可能 • uniqid() – タイムスタンプベースで予測可能 • time() – 完全に予測可能 推奨される使用例 $token = bin2hex(random_bytes(32)); $csrf_token = base64_encode(random_bytes(32));

この説明に誤りはありません。内容は⭕です

CSRF等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において。

そのため、以下のコード

var_dump( uniqid((string)mt_rand(), true) );

は正しく実行でき、結果は

string(33) “17821489405e874a50df8b89.86544478”
となる (乱数の値は実行毎に変わる)。uniqid() 関数と mt_rand() 関数を組み合わせているため、この値は「暗号論的にランダムな文字列」なので「推測 (予測) 困難なトークン」となり得る。

安全な乱数生成と非安全な乱数生成の比較 非推奨の方法 uniqid() mt_rand() rand() • 予測可能 • タイムスタンプベース 推奨される方法 random_bytes() openssl_random_pseudo_bytes() • 暗号論的に安全 • 予測困難 セキュアなトークン生成の例 $token = bin2hex(random_bytes(32)); // 64文字の16進数 // または $token = base64_encode(random_bytes(32)); // base64エンコード ※ 32バイト(256ビット)は現代の暗号化に十分な長さ

よって問題文は誤り❌です

CSRF 等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において、mt_rand() は乱数を生成するが、

とあるため、これを使用しても推測(予測)困難なトークンを作成する事は出来ない。

そのため、以下のコード

var_dump( mt_getrandmax() );
var_dump( mt_rand() );

は正しく実行でき、結果は

int(2147483647)
int(248780506)
となるが (乱数の値は実行毎に変わる)、この値は「暗号論的にランダムな文字列」ではないので「推測 (予測) 困難なトークン」となり得ない。

よって選択肢の内容は正しい⭕です

問題番号正解の選択肢
1PHPの「最新以外の(古い)コード」は、公式サイトでの提供は全くしていない。そのため、古いバージョンのコードが必要な場合、別途「非公式の外部サイト」からソースコードを入手する必要がある。
2論理型 (boolean) は「真偽値」とも呼ばれ、値は true か false か null のいずれかになる。なお、true、false、null の文字は、大文字でも小文字でもよい。
3抽象クラスから継承する際、親クラスで abstract としてマークされた全てのメソッドは子クラスで定義する必要があり、可視性は同等(またはより緩い制約)で、必須引数の数は同じであれば型宣言が異なってもかまわない。
4PHP において、デストラクタは __destruct() メソッドで実装される。親クラスのデストラクタは暗黙的に呼ばれ、呼び出し順序は「子クラスのデストラクタ → 親クラスのデストラクタ」となる。
5__get() はアクセス不能(protected または private)または存在しないプロパティからデータを読み込む際に使用される。なお、__getStatic() は存在せず、オブジェクトや静的コンテキストで動作しない。
6PHP のリファレンス渡しを使用すると、関数内で引数を修正できる。(※※正解テキストでは「呼び出す側で変数に & を付ける必要がある」と記載されています。)
7名前空間は namespace キーワードで宣言する。通常はファイルの先頭に記述する必要があり、名前空間宣言前に書かれたクラスはその名前空間に含まれない。
8PHP 5 では Exception クラスが全例外の基底クラスだったが、PHP 7 以降は Exception クラスは Throwable インタフェースを基底とし、Error クラスが内部エラーの基底クラスとして導入された。
9ArrayAccess インターフェイスは、オブジェクトを配列としてアクセスするための機能を提供する。
10SplFileInfo クラスは、ファイルの情報取得や操作を行うためのクラスである。
11$_COOKIE は、HTTP クッキーから渡された値が連想配列として格納され、また設定も可能である。
12PHP 7.1.x では、文字列操作関数で負のオフセット指定が可能となり、[] や {} による文字単位のアクセスも文字列の末尾からのオフセットとして解釈される。
13可変変数は、スーパーグローバル変数にも使用できる。
14PHP 7.4.x では、波括弧を使った配列や文字列のオフセットアクセスの文法は非推奨となった。
15PHP 8 では、private メソッドの継承に関するルールが変更され、親クラスの同名メソッドの可視性に関係なく子クラスでオーバーライド可能となった。
16break は、現在実行中の for, foreach, while, do-while, switch 構造の実行を終了し、オプションでネストしたループの何段分を抜けるか指定できる。
17XSS 対策として、htmlentities() を適切に使用することで、文字列や配列の入力に対して安全な出力が可能となる。
18アップロードされたファイルの元のファイル名は、$_FILES[‘{formのnameの値}’][‘name’] に格納され、move_uploaded_file() の第二引数として利用できる。
19uniqid() と mt_rand() を組み合わせることで、暗号論的にランダムな(推測困難な)トークンを生成できる。
20PHP のセッションでは、セッション ID がクッキーに保存され、session_set_cookie_params() によりそのクッキーのパラメータを設定できる。
21PHP の変数の参照カウントは、xdebug_debug_zval() で確認でき、オブジェクトを clone した場合は内部的に参照が使われ、一時的に参照カウントが増加する。
22DirectoryIterator クラスは、ディレクトリ内のファイルやサブディレクトリの情報を取得するシンプルなインターフェイスを提供する。
23mail() 関数は、メールを送信するための関数で、第四引数で追加のヘッダー情報を指定できる。
24escapeshellarg() と escapeshellcmd() は、外部入力をシェルコマンドの引数として使用する際に必要なエスケープ処理を行い、どちらを使用しても意味合いは同じである。
25stream_wrapper_register() 関数を使用すると、新しいストリームラッパー(プロトコルハンドラ)を登録できるが、既に存在する場合は失敗する。
26strpos() 関数は、文字列内で指定した部分文字列の最初の出現位置を返し、見つからなければ false を返す。ただし、先頭位置の場合は 0 が返るため注意が必要。
27Phar のスタブには __HALT_COMPILER() が使用され、これ以降のコードはコンパイルされない。
28function_exists() 関数は、指定された関数が定義されているかどうかをチェックし、存在すれば true を返す。
29openssl_decrypt() 関数は、openssl_encrypt() で暗号化されたデータを正しく復号し、元のデータを取り戻す。
30strtotime() 関数は、英語形式の日付文字列を Unix タイムスタンプに変換し、無効な日付の場合は false を返す。