未経験からのITエンジニア学習ブログ

主にIT系のブログを書きます。

PHP 学び 問い合わせフォームの作成 part2

セキュリティ対策(XSS,クリックジャッキング,CSRF)

XSS

クロスサイトスクリプティングXSS)とは、Webサイトの脆弱性を利用し、記述言語であるHTMLに悪質なスクリプトを埋め込む攻撃です。クロスサイトスクリプティングの英語表記「Cross Site Scripting」の略称として「XSS」と表記する場合もあります。

アンケートサイトやサイト内検索、ブログ、掲示板などユーザーからの入力内容をもとにWebページを生成するサイトや、FacebookTwitterのようなWebアプリケーションはクロスサイトスクリプティングの対象になりやすいです。サイトに設置されたフォームにユーザーが情報を入力・送信する際に、埋められた悪質なHTMLスクリプトが実行され、入力された情報に加えCookie情報なども攻撃者に送られます

クロスサイトスクリプティング(XSS)とは?わかりやすく解説 | クラウド型WAF 攻撃遮断くん より引用

 

対策

htmlspecialcharsを使いサニタイズ(消毒)を行う

//XSS(Cross-Site Scripting)対策
function h($str)
{
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

 

<?php if($pageFlag === 1) : ?>
<form method="POST" action="input.php">
氏名
<?php echo h($_POST['your_name']) ;?>
<br>
メールアドレス
<?php echo h($_POST['email']) ;?>
<br>

 

htmlspecialcharsを設定することで、<>などの文字が変換されるので、JavaScriptなどのコードを無効化することができる。

PHP: htmlspecialchars - Manual より引用

 

 

・クリックジャッキング

クリックジャッキングとは、Webブラウザを悪用して、ユーザーに不利益をもたらすセキュリティ上の攻撃手法の一つです。

具体的な特徴としては、ボタンやリンクなどを透明で見えない状態にして、通常のWebページの上にかぶせてしまうというもの。最近はこういった攻撃の一つ一つが巧妙化し、私たちコンピュータの利用者や企業、官公庁などにとって、脅威となっています。

Webブラウザはインターネットの閲覧に必ず使うものであり、利用者は世界中の老若男女となり非常に多くなります。そのため被害が発生した場合の影響が非常に大きいものとなるため、悪意を持つ者にとっては格好の標的となっています。

最も簡単なクリックジャッキングの手法として、下記のようにiframeで表示させたページを透過させるような形で、悪意あるサイトを見えないようにして、クリックさせる手口があります。
クリックジャッキング

このように透明にしたサイトを重ねて表示させて、知らないうちにクリックさせるような手口になります。

具体的にクリックジャッキングに引っかかってしまうと、下記のようなことが起こってしまいます。

このようなことを起こさせ、マルウェアの感染によって、情報を搾取したりパソコンなどの端末を乗っ取るなどの攻撃を行います。

そのほかにも、例えばウィンドウ無限表示型と呼ばれるブラウザでWebページを開くと、無限にウィンドウが開き続けるものや、CD-ROMアタックのようなCD-ROMドライブの開閉を無限に行わせてハードウェアの損傷を招くもの、JavaScriptループ型のようにJavaScriptを無限ループとして実行させることでCPUやメモリリソースを食いつぶしてパソコンの動作を非常に遅くするものなどがあります。

 

クリックジャッキングとは?その攻撃の概要と対策方法を解説|サイバーセキュリティ.com から引用

 

 

対策

header(X-FRAME-OPTIONS: DENY)を使いサニタイズ(消毒)を行う

 

PHPファイルに直接記述する方法。
header関数でheader(X-FRAME-OPTIONS: DENY)を指定してHTTPヘッダーに送る。そうすると重ねて表示することができなくなる。

 

// クリックジャッキング対策
header('X-FRAME-OPTIONS:DENY');

 

このようにGoogleChromeなら検証→Network→Headresに表示されていたらOK。

 

 

CSRF(シーサーフ)

CSRFCross-Site Request Forgeriesクロスサイトリクエストフォージェリーズ)は、Webシステムを悪用したサイバー攻撃の一種です。

CSRFの手口は、ユーザーが悪意のあるURLにアクセスしてしまった場合に、意図しないリクエストを特定のWebサービスに送られてしまうというものです。Webサイトのリンクやメールに記載されたリンクをクリックして、URLのアドレスにアクセスすることで特定のWebサービスへのリクエストが送られてしまいます。

 

特定のWebサービスへのリクエストは、Webサービスによって内容は変わるものの、Webサービスの設定変更や値の入力、操作の実行などに繋がります。また、WebサービスSNS掲示板の場合には、悪意のあるURLに設定した内容を投稿してしまうことになります。

 

ユーザーの意図しない情報・リクエストが送信されてしまうためリクエスト強要とも呼ばれます。ユーザー側は何が起きたのか気が付くことはなく、後から被害にあったことに気が付くのもその特徴です。

 

CSRFによる攻撃では、いくつかのパターンが確認されています。

 

一つはサービスにログインした状態のユーザーを狙った攻撃です。ログイン中の状態で、悪意あるURLにアクセスしてしまうと、サービスに対して任意の操作のリクエストを行うことができる場合があります。

例えばオンラインバンキングのサービスにログインした状態で、送金の操作を行うCSRFが行われ、それがサービス側で受理されてしまうことを想定するとその恐ろしさが分かります。

サービスにおける設定の変更(例えばパスワードの変更)のリクエストを行わせ、そこから攻撃を拡げるパターンも起き得るでしょう。

 

また、別の形のCSRFを利用した攻撃として、ユーザーの意図しない情報発信を行わせるものもあります。SNS掲示板などに対するリクエストをCSRFにより行われることで、ユーザーの意図しない投稿が行われてしまいます。投稿の内容が問題のあるものであれば、ユーザーの被害に直結してしまいます。上記のログイン状態とも組み合わせて利用されることが想定されます。

 

CSRFの恐ろしいところは、リクエストを強要させられてしまうことです。サイバー犯罪者は、ユーザーのログインしている状態やサービスを利用しているかどうかの有無を区別せず、広い範囲で罠を作って置きます。複数の種類のオンラインサービスに対しての罠を作っておくことで、被害に遭うユーザーを拡げる手口がとられています。

 

また、罠が設置してあるWebページへの誘導では、不安を煽るような文言や言葉巧みな文章が利用されます。ユーザーを騙し、心理的不安にさせることで、リクエスト強要に誘導する傾向が見られます。

CSRF(クロスサイトリクエストフォージェリーズ)とは?被害と対策も | セキュリティ対策 | CyberSecurityTIMES から引用

 

 

対策

本来の正しいページからの情報なのかを見分けてから処理をする。

$_SESSIONを使った合言葉(トークン)のやり取りをする

合言葉の設定はrandom_bytesを使う(PHP: random_bytes - Manual 参照)

random_bytes — 暗号学的にセキュアな、ランダムなバイト列を生成する

 

関数の設定

<?php

session_start(); //←この設定でsessionを使うことができる

 

入力画面の設定

<?php if($pageFlag === 0) : ?>
<?php
if(!isset($_SESSION[('csrfToken')])){
//issetは中身があるか判定する
$csrfToken = bin2hex(random_bytes(32));
//bin2hexを入れることで16進数に変換できうまく表記ができるようになる。
$_SESSION['csrfToken'] = $csrfToken;
}

$token = $_SESSION['csrfToken'];
?>

<form method="POST" action="input.php">
氏名
<input type="text" name="your_name"
value="<?php if(!empty($_POST['your_name']))
{echo h($_POST['your_name']) ;} ?>">
<br>
メールアドレス
<input type="email" name="email"
value="<?php if(!empty($_POST['email']))
{echo h($_POST['email']) ;} ?>">
<br>
<input type="submit" name="btn_confirm" value="確認する">
<input type="hidden" name="csrf" value="<?php echo $token; ?>">

 

確認画面の設定

<?php if($pageFlag === 1) : ?>
<?php if($_POST['csrf'] === $_SESSION['csrfToken']) :
//↑合言葉(トークンが合っているかの判定) ?>
<form method="POST" action="input.php">
氏名
<?php echo h($_POST['your_name']) ;?>
<br>
メールアドレス
<?php echo h($_POST['email']) ;?>
<br>
<input type="submit" name="back" value="戻る">
<input type="submit" name="btn_submit" value="送信する">
<input type="hidden" name="your_name"
value="<?php echo h($_POST['your_name']) ;?>">
<input type="hidden" name="email"
value="<?php echo h($_POST['email']) ;?>">
<input type="hidden" name="csrf"
value="<?php echo h($_POST['csrf']) ;?>">
</form>
<?php endif; ?>
<?php endif; ?>

 

送信完了画面

<?php if($pageFlag === 2) : ?>
<?php if($_POST['csrf'] === $_SESSION['csrfToken']) :?>
送信が完了しました。

<?php unset($_SESSION['csrfToken']);
//↑合言葉(トークン)の削除 ?>
<?php endif; ?>
<?php endif; ?>

 

 

問い合わせフォーム全コード

<?php

session_start(); //←この設定でsessionを使うことができる

// クリックジャッキング対策
header('X-FRAME-OPTIONS:DENY');

// your_nameが?の後に入っていない場合、
Noticeのエラー文が出てしまうので、それを消す
if(!empty($_SESSION)){
// GET(POST)の中身を見る方法
echo '<pre>';
var_dump($_SESSION); //←にキーを入力すると中身が見られる
echo '</pre>';
// $_で書いている変数をスーパーグローバル変数と呼ぶ
// phpの場合は9種類ある
// 連想配列になっている
// 今回の場合、name="your_name"がキーになっていて
// 入力された値がvalue="送信"
}

//XSS(Cross-Site Scripting)対策
function h($str)
{
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}


// 解説
// inputに </input>と書いているのですが、
// 実は </input>は不要になります。
// 当時、クセで書いていたと思うのですが、
先の講座では不要と解説させていただいております。
// チェックボックスで複数選択できるようなフォームの場合に、
// 配列として受け取る場合はnameにをつける形になります。
// []をつけないと1つの値しかとれなかったと思います。


// 入力、確認、完了画面の作成
// 3つを分ける場合→input.php, confirm.php, thanks.php
// input.php←今回は一つのファイルで全てを入れる
// CSRF 偽物のinput.php 悪意のあるページにアクセスするとことになる。
// 自分が意図しない操作を勝手に行なってしまう。

$pageFlag = 0;

if(!empty($_POST['btn_confirm'])){
$pageFlag = 1;
}
if(!empty($_POST['btn_submit'])){
$pageFlag = 2;
}

?>

<!DOCTYPE html>
<meta charser="utf-8">
<head></head>
<body>

<?php if($pageFlag === 1) : ?>
<?php if($_POST['csrf'] === $_SESSION['csrfToken']) :
//↑合言葉(トークンが合っているかの判定) ?>
<form method="POST" action="input.php">
氏名
<?php echo h($_POST['your_name']) ;?>
<br>
メールアドレス
<?php echo h($_POST['email']) ;?>
<br>
<input type="submit" name="back" value="戻る">
<input type="submit" name="btn_submit" value="送信する">
<input type="hidden" name="your_name"
value="<?php echo h($_POST['your_name']) ;?>">
<input type="hidden" name="email"
value="<?php echo h($_POST['email']) ;?>">
<input type="hidden" name="csrf"
value="<?php echo h($_POST['csrf']) ;?>">
</form>
<?php endif; ?>
<?php endif; ?>


<?php if($pageFlag === 2) : ?>
<?php if($_POST['csrf'] === $_SESSION['csrfToken']) :?>
送信が完了しました。

<?php unset($_SESSION['csrfToken']);
//↑合言葉(トークン)の削除 ?>
<?php endif; ?>
<?php endif; ?>


<?php if($pageFlag === 0) : ?>
<?php
if(!isset($_SESSION[('csrfToken')])){
//issetは中身があるか判定する
$csrfToken = bin2hex(random_bytes(32));
//引数の数字は24でもOK。
bin2hexを入れることで16進数に変換できうまく表記ができるようになる。
$_SESSION['csrfToken'] = $csrfToken;
}

$token = $_SESSION['csrfToken'];
?>

<form method="POST" action="input.php">
氏名
<input type="text" name="your_name"
value="<?php if(!empty($_POST['your_name']))
{echo h($_POST['your_name']) ;} ?>">
<br>
メールアドレス
<input type="email" name="email"
value="<?php if(!empty($_POST['email']))
{echo h($_POST['email']) ;} ?>">
<br>
<input type="submit" name="btn_confirm" value="確認する">
<input type="hidden" name="csrf" value="<?php echo $token; ?>">

</form>
<?php endif; ?>


<body>
</html>

 

ただ作って終わりじゃなくてその後のリスクのことも考えて作るのが大切ですね。