はじめに
今回はフォームセキュリティを意識した実装についてまとめていきます!
XSS(クロスサイトスクリプティング)
まず、クロスサイトスクリプティングってなんだろうか?というところからまとめてみます。
ネット調べると以下のようなことみたいです。
クロスサイトスクリプティング(XSS)とは、攻撃対象のウェブサイトに、脆弱性がある掲示板のようなウェブアプリケーションが掲載されている場合に、悪意のある第三者がそこへ罠を仕掛け、サイト訪問者の個人情報を盗むなどの被害をもたらす攻撃です。また該当するような脆弱性をさしてクロスサイトスクリプティングと呼ぶこともあります。
(引用URL:https://www.kagoya.jp/howto/network/xss/)
自分の言葉に変えてまとめると、「フォームや投稿画面に悪意あるプログラム文を入れるとブラウザはそれを投稿の文ではなく、一つのプログラムと認識して外部から操作されてしまう!」といった感じでしょうか。なのでデマニュースとか流せてしまうというわけですね。
CSRF(クロスサイトリクエストフォージェリ)
CSRFについてもこちらの記事が分かりやすかったので引用させていただきます!
クロスサイトリクエストフォージェリは、ウェブアプリケーションの脆弱性を悪用するサイバー攻撃の一種です。文字通り「サイト横断的に(Cross Site)リクエストを偽装(Request Forgeries)する」攻撃です。CSRFと略されシーサーフと呼ばれることが多くなっています。
具体的に説明します。攻撃者は罠となるサイトを用意し、ユーザーが罠にかかるのを待つかリンクやメールなどで利用者を罠サイトへ誘導します。誘い込まれたユーザーが罠サイトにアクセスし、その際にユーザーがターゲットとなるウェブサイトへログイン状態になっていると、そのウェブサイトに偽のリクエストが送信・実行されてしまうという攻撃です。
引用URL:https://siteguard.jp-secure.com/blog/what-is-csrf
実装
今回、以上の攻撃を意識した実装を行いました。
まずは見てみます。
<?php
session_start();
header('X-FRAME-OPTIONS:DENY');
function h($str){
return htmlspecialchars($str,ENT_QUOTES,'UTF-8');
}
$pageFlag=0;
if(!empty($_POST['btn_confirm'])){
$pageFlag=1;
}
if(!empty($_POST['btn_submit'])){
$pageFlag=2;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<?php if($pageFlag===0):?>
<?php
if(!isset($_SESSION['csrfToken'])){
$csrfToken=bin2hex(random_bytes(32));
$_SESSION['csrfToken']=$csrfToken;
}
$token=$_SESSION['csrfToken'];
?>
<form method="POST" action="index.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="hidden" name=csrf value="<?php echo $token;?>">
<input type="submit" name="btn_confirm" value="確認する">
</form>
<?php endif; ?>
<?php if($pageFlag===1) :?>
<?php if($_POST['csrf']===$_SESSION['csrfToken']) :?>
<form method="POST" action="index.php">
名前
<?php echo h($_POST['your_name']);?>
<br>
メールアドレス
<?php echo h($_POST['email']);?>
<br>
<input type="submit" name="btn_submit" value="送信する">
<input type="submit" name="back" 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;?>
</body>
</html>
実はXXSに関しては簡単に対策できます。
XXSでは、入力に対しての文字をプログラムとして読み取らないようにするために、htmlspecialchars()を使用します。htmlspecialchars は、例えばタグとして利用される < 、 > など、HTMLにおいて特殊な意味を持つ文字だけを対象に、HTMLエンティティに変換する関数です。
ただし、今回この関数名が長すぎるのでhという関数に再定義しなおしています。
function h($str){
return htmlspecialchars($str,ENT_QUOTES,'UTF-8');
}
これを使えば、表示の際に例えばと入力した際に、アラートは出ずに表示できます。
CSRFについては、その仕組みからSESSIONに値を発行して、各ページでそのSESSIONの値が正しいかどうかで判断すれば対応できます。
ちなみにSESSIONを使用するには以下の一文が必要です。
session_start();
あとは各ページ(今回は、$pageFlag=0,1,2の3ページ)でSESSIONの値が正しいどうかを確認するだけです
おわりに
こうしたセキュリティのことを少し知らないだけで大変なことになりうるので、もっと勉強しないといけないといけないなと思いました。エンジニアの責任の重さを感じました。