プログラミング

プログラミング初心者のPHP修羅の道#18〜PHP8上級問題を徹底追求する〜

PHP8上級資格取得へ向けたアウトプット記事になります。

PHP試験運営団体より公開されている模擬試験を1つ1つみていく形で事細かくみていきますが、勉強途中ゆえ間違っている部分に関してはご了承ください。

PHP上級資格のメイン教材はPHPマニュアルと指定されていますので、このマニュアルを主に踏襲した内容となっています。

主な勉強教材は以下になります。

1.独習PHP第4版

2.はじめてのPHP

 

3.PHP公式マニュアル

 

PHP8上級試験模擬問題

試験問題19

 

推測困難なトークンに関する説明の中で、誤っているものを1つ選びなさい。
なお「¥」はバックスラッシュに読み替えること。

 

選択肢①

 

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

declare(strict_types=1);
error_reporting(-1);

var_dump( bin2hex( random_bytes(24) ) );

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

string(48) "e5c471cbf4502eb90dbfbd2480f06a2d6af69ce4b0722b31"

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

 

 

このコードは、PHPのrandom_bytes()関数を使用して、ランダムで暗号論的に安全なバイト列を生成し、それを16進表現の文字列に変換しています。

random_bytes()関数は、暗号論的に安全なランダムなバイト列を生成することができるため、セキュリティに重要な機能に使用することができます。しかし、トークンの生成には十分な長さを確保する必要があり、選択肢のコードではセキュリティ的に十分な強度を持つ24バイトのランダムなバイト列が生成されています。

結果の文字列は、長さが48の16進数表現であり、ランダムなトークンとして使用できます。

var_dump( bin2hex( random_bytes(24) ) );
// string(48) "e2598fedda0618ef22a767609e418a646612b305ec528d10"

var_dump( random_bytes(24) );
// string(24) "Z�����ɥr�^'�[S���"

よって、この選択肢は合っています。

 

選択肢②

 

CSRF等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において、openssl_random_pseudo_bytes()は「疑似ランダムなバイト文字列を生成する」事が出来る。
古いシステムでない限り暗号学的に強いアルゴリズムを使って疑似乱数が生成される事が多いが、古いシステムなどでは「暗号学的に強くない」文字列である可能性もあり、それは第二引数によって知る事が出来る。
そのため、以下のコード

declare(strict_types=1);
error_reporting(-1);

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

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

string(32) "OFUw6O7WR785Kg5YZbGZOKDwJzXRn60J"
bool(true)

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

 

openssl_random_pseudo_bytes()は、暗号学的に強いアルゴリズムを使用して、疑似ランダムなバイト文字列を生成するための関数です。この関数は、CSRFトークンなどの秘密情報を生成する場合に使用されます。

この関数の第一引数には、生成するバイト列の長さを2147483647以下の正の整数を指定します。

第二引数には、アルゴリズムが暗号学的に強いものであるかどうかを表すboolが格納され、強い場合は true、そうでない場合は false となります。

「強い」とは、 GPGやパスワードなどに使っても安全であるという意味です。

GPGとは

GPG (GNU Privacy Guard) は、デジタル署名や暗号化などのセキュリティ関連の機能を提供するオープンソースのソフトウェアです。

GPGを使用すると、機密情報やプライバシーを保護するために、メッセージを暗号化したり、ファイルに電子署名を付けたりすることができます。また、他のユーザーに公開鍵を提供することで、相手によって暗号化されたメッセージを受信したり、デジタル署名を検証することもできます。

よって、この選択肢は合っています。

 

選択肢③

 

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

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() を使いましょう。

そのため、以下のコード

declare(strict_types=1);
error_reporting(-1);

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

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

string(33) "17821489405e874a50df8b89.86544478"

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

 

uniqid()関数は、一意のIDを生成するために使用されます。

これが有用なのは、たとえば複数ホストで同時にIDを生成するような場合ですが、この関数を使用したとしても同じマイクロ秒で同じIDが生成されてしまう可能性があります。

また、第二引数にtrueを設定することでより一意になる可能性を高めることができますが、やはりuniqid()関数によって生成される値の一意性は完全には保証されません。

mt_rand()関数は、指定された範囲内の疑似乱数を生成するために使用されますが、こちらも暗号学的に安全ではないため、秘密情報として使用することはできません。

これらの関数を組み合わせたとしても暗号学的に安全であるとは言えないため、やはりrandom_int()かrandom_bytes()あるいはopenssl_random_pseudo_bytes()を使用した方が良く、この選択肢は誤りと言えるでしょう。

 

選択肢④

 

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

mt_rand( ) : int
mt_rand( int $min,int $max ) : int

警告
この関数が生成する値は、暗号学的に安全ではありません。そのため、これを暗号として使ってはいけません。暗号学的に安全な値が必要な場合は、random_int() か random_bytes() あるいは openssl_random_pseudo_bytes() を使いましょう。
とあるため、これを使用しても推測(予測)困難なトークンを作成する事は出来ない。
そのため、以下のコード

declare(strict_types=1);
error_reporting(-1);

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

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

int(2147483647)
int(248780506)

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

 

mt_rand()関数はPHPの乱数生成関数の一つで、指定した範囲の整数の乱数を生成します。しかし、暗号学的に安全ではないため、推測(予測)困難なトークンを作成するためには適切ではありません。

オプションの引数min、maxを付けずに コールした場合、mt_rand()は0から mt_getrandmax()の間の擬似乱数値を返します。

mt_getrandmax()乱数値の最大値を表示する関数です。

minからmaxまでの幅はmt_getrandmax()の範囲内におさめる必要があります。 つまり、(max – min) <= mt_getrandmax()でなければいけません。

よって、この選択肢は合っています。

 

試験問題20

セッション に関する説明の中で、誤っているものを1つ選びなさい。
なお「¥」はバックスラッシュに読み替えること。

 

選択肢①

 

PHPは「複数回のアクセスを通じて特定のデータを保持する手段」としてのセッションサポート機能を持っている。
セッションサポート機能により、スーパーグローバル配列SESSION

declare(strict_types=1);
error_reporting(−1);

session_start();
$_SESSION['key'] = 'value';

をブラウザ経由で実行した後に以下のコード

declare(strict_types=1);
error_reporting(-1);

session_start();
var_dump($_SESSION['key']);

をブラウザ経由で実行すると

string(5) "value"

となる。

 

最初のコードでは、session_start()関数によりセッションを開始し、$_SESSION配列にキー”key”に対して値”value”を設定しています。これにより、セッションにデータが保存されます。

次に、2つ目のコードでは、session_start()関数によりセッションを再開し、$_SESSION配列からキー”key”に対する値を取得しています。

これは、最初のコードでセッションに保存されたデータが、2つ目のコードで正常に取得されていることを示しています。セッション機能により、リクエスト間でデータを保持することができるため、ブラウザを経由して複数回のリクエストを送信しても、セッションに保存されたデータが維持されることがわかります。

引用元:はじめてのPHP

以下に示すコードは「セッションを使ったページアクセス数のカウント」の例です。

if(isset($_SESSION['count'])){
    $_SESSION['count'] = $_SESSION['count'] + 1;
}else{
    $_SESSION['count'] = 1;
}

セッション作成時には$_SESSION配列は空のため、最初の訪問であれば1を設定し、そうでない場合は訪問回数にプラス1していく処理となっています。

画像のように1回目のアクセス時にはPHPSESSIDはサーバに送られず、session_start()関数が実行されることで初めてセッションを開始し、新しいPHPSESSIDがクライアント側に送られます。

この時WEBサーバ側では$_SESSIONの情報がセッションIDと関連づけられて保存されているため、次回クライアント側でページにアクセスすると、WEBクライアントから送られてきたPHPSESSIDを識別して対応するセッション情報を提供します。

よって、この選択肢は合っています。

 

選択肢②

 

PHPのセッションサポート機能において、セッションデータはデフォルトではファイルに保存される。
また、保存先のファイルは session_save_path() 関数によって取得または設定する事ができる。

session_save_path( string|null $path = null ) : string|false

session_save_path() は、 現在のセッションデータ保存パスを返します。
そのため、以下のコード

declare(strict_types=1);
error_reporting(-1);

ob_start();
var_dump( session_save_path() );
session_save_path('/tmp');
var_dump( session_save_path() );

を実行すると

string(0) ""
string(4) "/tmp"

となる。

 

選択肢のコードは、PHPのセッションサポート機能におけるセッションデータの保存先を設定する例です。

まず、session_save_path()関数を使用して現在のセッションデータの保存パスを取得しています。初期値では保存先のパスは空の文字列(“”)が返されるため、最初のvar_dump()の結果はstring(0)””となります。

次に、session_save_path(‘/tmp’)というコードが実行されています。これにより、セッションデータの保存先を/tmpというディレクトリに設定されます。そして、session_save_path()関数を再度実行して保存先のパスを取得しています。今度は/tmpという文字列が返されるので、2番目のvar_sump()の結果はstring(4)”/tmp”となります。

つまり、このコードはセッションデータの保存先を初めに空の文字列から/tmpに変更し、それをvar_sump()関数で確認するものです。session_save_path()関数を使用することで、セッションデータの保存先を任意のディレクトリに設定することができます。

よって、この選択肢は合っています。

補足として「ob_start()」についても触れておきます。

PHPのob_start()関数は、出力バッファリングを開始するための関数です。

出力バッファリングとは、PHPのスクリプトが出力する内容を一時的にメモリ上にバッファリングしておくことを指します。これにより、スクリプトが出力する内容を一時的に保留し、最終的にまとめて出力することができます。

ob_start()関数は、スクリプトの先頭に記述されることが一般的であり、スクリプト全体で出力バッファリングを有効にするために使用されます。関数を呼び出すことで、PHPは出力をバッファリングし始めます。

ob_start(); // 出力バッファリングを開始

echo "Hello, "; // これが出力されずにバッファリングされる

// 出力バッファリングを終了し、バッファリングされた内容を出力
ob_end_flush();

ob_start()関数によって出力バッファリングが開始されていますので、echo “Hello, “;という出力は直接ブラウザに送信されず、バッファリングされます。そして、ob_end_flash()関数によって出力バッファリングが終了し、バッファリングされた内容が一括してブラウザに送信されます。

ob_start()関数は、出力バッファリングを制御するための様々なオプションを指定することもできます。例えば、ob_start()関数の引数に圧縮オプションを指定することで、出力を圧縮して送信することもできます。また、ob_start()関数は組み合わせて使用することで、出力を一時的にメモリ上にバッファリングすることなくファイルに書き込むこともできます。出力バッファリングは、PHPにおける出力の制御を強化するための便利な機能の一つです。

 

選択肢③

 

PHPのセッションサポート機能において、セッションIDというセッションIDと呼ばれるユニークなIDが割り当てられ、それは基本的にユーザー側にクッキーとして保存される。
そのためクッキーを使うので、セッションIDを保存するクッキーに対するパラメータを設定する事ができる関数が存在する。

session_set_cookie_params( int $lifetime_or_options, string|null $path = null, string|null $domain=null, bool|null $secure = null , bool|null $httponly=null):bool
session_set_cookie_params(array $lifetime_or_options ) : bool

そのため、以下のコード

declare(strict_types=1);
error_reporting(-1);

ob_start();
var_dump( session_get_cookie_params() );
session_set_cookie_params(['lifetime' => 86400, 'samesite' => 'Strict', 'secure' => true, 'httponly' =>true]);
var_dump( session_get_cookie_params() );

を実行すると

array(6) {
  ["lifetime"]=>
  int(0)
  ["path"]=>
  string(1) "/"
  ["domain"]=>
  string(0) ""
  ["secure"]=>
  bool(false)
  ["httponly"]=>
  bool(false)
  ["samesite"]=>
  string(0) ""
}

Warning: session_set_cookie_params(): Unrecognized key 'samesite' found in the options array in ...
array(6) {
  ["lifetime"]=>
  int(86400)
  ["path"]=>
  string(1) "/"
  ["domain"]=>
  string(0) ""
  ["secure"]=>
  bool(true)
  ["httponly"]=>
  bool(true)
  ["samesite"]=>
  string(0) ""
}

となる。

 

session_set_cookie_params()関数は、セッションIDを保存するクッキーに対するパラメータを設定するための関数です。

session_set_cookie_params(
    int $lifetime_or_options,    // セッションIDの有効期限
    string|null $path = null,    // クッキーのパス
    string|null $domain = null,  // ドメイン
    bool|null $secure = null,    // セキュア属性
    bool|null $httponly = null,  // httponly属性
    string|null $samesite = null // samesite属性
) : bool

コードの中で使用されているsession_set_cookie_params()関数の引数には、連想配列が渡されています。

[
'lifetime' => 86400,
'samesite' => 'Strict',
'secure'   => true,
'httponly' => true
]

これは、セッションIDの有効期限を86400秒(1日)、samesite属性を’Strict’セキュア属性をtruehttponly属性をtrueに設定することを意味しています。

これらは結果的に、セッションIDの有効期限が86400秒(1日)に設定され、パスが’/’ドメインが空セキュア属性がtruehttponly属性がtruesamesite属性がデフォルトの値であることを示しています。

また、sametime要素が省略されると、SameSiteクッキー属性は設定されません。そのため正確には以下のように出力されます。

array(6) {
  ["lifetime"]=>
  int(0)
  ["path"]=>
  string(1) "/"
  ["domain"]=>
  string(0) ""
  ["secure"]=>
  bool(false)
  ["httponly"]=>
  bool(false)
  ["samesite"]=>
  string(0) ""
}
array(6) {
  ["lifetime"]=>
  int(86400)
  ["path"]=>
  string(1) "/"
  ["domain"]=>
  string(0) ""
  ["secure"]=>
  bool(true)
  ["httponly"]=>
  bool(true)
  ["samesite"]=>
  string(6) "Strict"
}

よって、この選択肢は誤りです。

 

選択肢④

 

PHPのセッションサポート機能において、セッションデータはデフォルトではファイルに保存される。
しかし「ファイル以外(DB等)」に保存をする事も出来る。そのためにsession_set_save_handler() という関数がある。

session_set_save_handler(
    callable $open ,
    callable $close ,
    callable $read ,
    callable $write ,
    callable $destroy ,
    callable $gc ,
    callable $create_sid = ? ,
    callable $validate_sid = ? ,
    callable $update_timestamp = ?
) : bool

session_set_save_handler(
    object $sessionhandler ,
    bool $register_shutdown = true
) : bool

最近は後者の方法で実装される事が多いが、その場合、SessionHandlerInterface、 SessionIdInterface(オプション) または SessionUpdateTimestampHandlerInterfaceを実装したクラスを継承したクラスのオブジェクトを引数として指定する必要がある
そのため、以下のコード

declare(strict_types=1);
error_reporting(-1);

class Hoge {
}

ob_start();
session_set_save_handler(new Hoge);

を実行すると

Fatal error: Uncaught TypeError: session_set_save_handler(): Argument #1 ($open) must be of type SessionHandlerInterface, Hoge given in ...

となる。

 

session_set_save_handler()関数は、セッションデータの保存と読み込みを制御するためのコールバック関数を引数として受け取ります。

エラーが発生している原因は、session_set_save_handler()関数の第1引数には、SessionHandlerInterfaceを実装したクラスのオブジェクトを渡す必要があるためです。HogeクラスはSessionHandlerInterfaceを実装していないため、エラーが発生しています。

以下は公式レファレンスで紹介されているSessionHandlerInterfaceの例です。

class MySessionHandler implements SessionHandlerInterface
{
    private $savePath;

    public function open($savePath, $sessionName): bool
    {
        $this->savePath = $savePath;
        if (!is_dir($this->savePath)) {
            mkdir($this->savePath, 0777);
        }

        return true;
    }

    public function close(): bool
    {
        return true;
    }

    #[\ReturnTypeWillChange]
    public function read($id)
    {
        return (string)@file_get_contents("$this->savePath/sess_$id");
    }

    public function write($id, $data): bool
    {
        return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
    }

    public function destroy($id): bool
    {
        $file = "$this->savePath/sess_$id";
        if (file_exists($file)) {
            unlink($file);
        }

        return true;
    }

    #[\ReturnTypeWillChange]
    public function gc($maxlifetime)
    {
        foreach (glob("$this->savePath/sess_*") as $file) {
            if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
                unlink($file);
            }
        }

        return true;
    }
}

$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();

// $_SESSION への値の設定や格納されている値の取得を進めます

このコードは、PHPのセッションデータをカスタムの保存先に変更するために、SessionHandlerInterfaceを実装したMySessionHandlerというクラスを定義し、それをsession_set_save_handler()関数に渡してセッションハンドラを設定しています。

MySessionHandlerクラスは、以下のような機能を持っています。

open($savePath, $sessionName): bool

セッションを開始する際に呼び出されるメソッドで、セッションデータの保存先を設定します。保存先のディレクトリが存在しない場合は作成します。戻り値は true を返します。

close(): bool

セッションを終了する際に呼び出されるメソッドで、特別な処理は行いません。戻り値は true を返します。

read($id)

セッションデータの読み込みを行うメソッドで、指定されたセッションIDに対応するデータを読み込みます。戻り値はセッションデータを表す文字列を返します。

write($id, $data): bool
セッションデータの書き込みを行うメソッドで、指定されたセッションIDに対応するデータを書き込みます。戻り値は書き込みが成功したかどうかを表す boolean 値を返します。

destroy($id): bool
セッションデータの削除を行うメソッドで、指定されたセッションIDに対応するデータを削除します。戻り値は true を返します。

gc($maxlifetime)
セッションデータのガベージコレクションを行うメソッドで、古いセッションデータを削除します。戻り値は特別な値を返す必要はありません。

MySessionHandlerクラスのインスタンスを作成し、それを session_set_save_handler()関数に渡すことで、セッションデータの保存と読み込みを MySessionHandlerクラスのメソッドにカスタマイズすることができます。

そして、session_start()関数を呼び出すことで、セッションを開始し、$_SESSIONに値を設定したり、格納されている値を取得したりすることができます。

よって、この選択肢は合っています。

ABOUT ME
ヒロ
社会人4年目/25歳/食品商社で2年間営業した後、IT業界にシステムエンジニアとして転職/Java,PHP言語を扱う開発エンジニア