Webアプリケーションのフォームは、悪意のある攻撃者の入口です。ここからどんどん入ってくるのです。怖ーい。
本記事では、PHPでフォームのセキュリティ対策を行うために、代表的な攻撃手法であるXSS、メールヘッダ・インジェクション、SQLインジェクション、CSRF、クリックジャッキングへの対策について説明します。攻撃手法とその対策方法を正しく理解し、適切に対処することで、フォームからの入力に関するセキュリティリスクを最小限に抑えることができます。
XSS(クロスサイトスクリプティング)
XSSとは
XSS(クロスサイトスクリプティング)とは、攻撃者がWebページ上に不正なスクリプトを埋め込むことで、そのページを閲覧するユーザのブラウザで不正なスクリプトが実行される脆弱性またはその脆弱性を利用した攻撃手法のことです。
ユーザがフォームに入力した値をWebページ上に表示する場合に、この脆弱性が発生する可能性があります。
例えば、次のようなPHPスクリプトがあったとします。フォームに入力した文字列をそのまま表示するスクリプトです。
<form action="" method="post">
<input type="text" name="name">
<input type="submit" value="送信">
</form>
<?php
echo $_POST['name'];
?>
フォームに次のように入力して送信すると、
こうなります。
きゃー。ページが乗っ取られてしまいました。
この例では、単にアラートを表示するだけのスクリプトを実行しましたが、名前、住所、クレジットカード情報を盗むような悪質なスクリプトが埋め込まれる可能性もあります。きゃー、怖い。
XSS攻撃対策
XSS攻撃を防ぐには、次のような方法があります。
1. 入力値のバリデーション
入力値の妥当性を検証し、不正な値を弾くことで、XSS攻撃を防止できます。
例えば以下のようなコードを書けば、入力値に特別な文字(&
"
'
<
>
)が使われるのを防ぐことができます。
if (preg_match('/[&"\'<>]/', $_POST['name'])) {
die('使用できない文字が含まれています');
}
echo $_POST['name'];
2. サニタイジング
サニタイジングとは文字列に含まれる有害な文字を無害化することです。
PHPのhtmlspecialchars()
関数を使うと、HTMLにおいて特別な意味を持つ文字(&
"
'
<
>
)をHTMLエンティティに変換することができます。
echo htmlspecialchars($_POST['name']);
3. CSPの設定
CSP(Content Security Policy)を利用することで、Webサイトのコンテンツに含めることが許可されるリソースを指定することができます。
CSPはPHPのheader()
関数を使って、HTTPヘッダで設定します。
header("Content-Security-Policy: script-src 'self';");
上記の設定では、自身のWebサイトから読み込まれたスクリプトの実行のみを許可し、外部のスクリプトやインラインスクリプト、イベントハンドラーの実行を禁止します。
ただし、jQueryなど、外部から読み込むスクリプトを実行したいこともあるでしょう。その場合は、読み込みを許可するホストを指定することができます。
header("Content-Security-Policy: script-src 'self' cdn.example.com;");
無料で利用できるプログラミング学習サービスをお探しならば Code Lesson はいかがでしょうか。プロのエンジニアが監修した学習ロードマップで効率的に学習、AIに質問、最後にクイズで理解度をチェックできます。
メールヘッダ・インジェクション
メールヘッダ・インジェクションとは
メールヘッダ・インジェクションとは、メールのヘッダに不正なデータを埋め込むことで、メールの内容を書き換えられたり、不正なアクセスを行われたりする脆弱性またはその脆弱性を利用した攻撃手法のことです。
ユーザがフォームに入力した値をメールで送信する場合に、この脆弱性が発生する可能性があります。
例えば、次のようなお問い合わせフォームがあったとします。
件名に「メールの件名」と入力した場合に送信されるメールのヘッダは次のようになります。
Subject: メールの件名
From: <xxx@xxx.xxx>
To: <yyy@yyy.yyy>
件名に「メールの件名\r\nBcc: attacker@example.com」と入力した場合のメールヘッダは次のようになります。
Subject: メールの件名
Bcc: <attacker@example.com>
From: <xxx@xxx.xxx>
To: <yyy@yyy.yyy>
Bcc(ブラインド・カーボン・コピー)のフィールドが追加され、攻撃者にもメールが送信されるようになってしまいました。
メールヘッダの各フィールドは改行文字(\r\n
)で区切られるという仕様のため、フォームの入力値に改行文字があると、意図しないメールヘッダが挿入されてしまいます。なお、メールヘッダと本文は空行(2回の改行)で区切られる仕様なので、本文の改ざんも可能です。きゃー、怖い。
メールヘッダ・インジェクション攻撃対策
メールヘッダ・インジェクション攻撃を防ぐには、次のような方法があります。
1. 入力値のバリデーション
入力値の妥当性を検証し、不正な値を弾くことで、メールヘッダ・インジェクション攻撃を防止できます。
例えば以下のようなコードを書けば、入力値に不正な文字が使われるのを防ぐことができます。
$to = $_POST['email'];
$subject = $_POST['subject'];
$message = $_POST['message'];
$additional_headers = 'From: webmaster@example.com';
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
exit('有効なメールアドレスを入力してください');
}
if (preg_match('/[\r\n]/', $subject)) {
exit('件名に使用できない文字が含まれています');
}
mb_send_mail($to, $subject, $message, $headers);
2. 追加ヘッダを配列で指定する
PHPのmail()
およびmb_send_mail()
関数は次のような書式です。
mb_send_mail($to, $subject, $message, $additional_headers);
4つの引数のうち、$to
(宛先)と$subject
(件名)に改行文字が含まれている場合は、自動的にエスケープ処理されます。PHPのmail()
/mb_send_mail()
関数にはメールヘッダ・インジェクション対策がデフォルトで入っているというわけです。
$message
(本文)は改行文字を受け入れます。これは当然だし、ヘッダに影響を与えないので問題ありませんね。
問題なのは、$additional_headers
(追加ヘッダ)です。追加ヘッダは宛先と件名以外のヘッダを追加したいときに指定します。最低限From:
は必要なので必ず指定することになります。
この追加ヘッダはPHPの古いバージョンでは文字列で指定することになっていました。複数のヘッダを設定する場合は改行文字で区切ります。
$additional_headers =
'From: webmaster@example.com' . "\r\n" .
'Cc: masawo@example.com' . "\r\n" .
'X-Mailer: DokiDoki PHP Mailer';
$additional_headers
は改行文字を受け入れるため、脆弱性を生じるリスクがありました。
しかしそれはもう過去の話です。PHP 7.2以降では、$additional_headers
は配列で指定することができるようになりました。
$additional_headers = array(
'From' => 'webmaster@example.com',
'Cc' => 'masawo@example.com',
'X-Mailer' => 'DokiDoki PHP Mailer'
);
追加ヘッダを配列で指定することにより、メールヘッダ・インジェクション攻撃を受けるリスクを減らすことができます。
SQLインジェクション
SQLインジェクションとは
SQLインジェクションとは、不正なSQL文を実行することで、データベースに対して悪意のある操作が行われる脆弱性またはその脆弱性を利用した攻撃手法のことです。
ユーザがフォームに入力した値を使ってSQL文を発行する場合に、この脆弱性が発生する可能性があります。
例えば、次のようなPHPスクリプトがあったとします。フォームに入力された名前($_POST['name']
)を使って、データベースを検索するコードです。
$sql = "SELECT * FROM table_name WHERE name = '{$_POST['name']}';";
入力された名前がhanako
ならば、SQL文はSELECT * FROM table_name WHERE name = 'hanako';
となります。これが本来期待される動作です。
ところが、名前に'; DELETE FROM user --
と入力されてしまったらどうなるでしょうか。SQL文はSELECT * FROM table_name WHERE name = ''; DELETE FROM user -- ';
となります。デーブル内のすべてのデータが削除されてしまいました。きゃー、怖い。
SQLインジェクション攻撃対策
SQLインジェクション攻撃を防ぐには、次のような方法があります。
1. プリペアドステートメントを使う
PDOでMySQLに接続しているならば、プリペアドステートメントを使いましょう。
//プリペアドステートメントを使わない例
//インスタンスの生成
$dbh = new PDO($dsn, $dbuser, $dbpass);
//SQLの実行
$sth = $dbh->query("SELECT * FROM table_name WHERE name = '$name' AND age = $age;");
//結果の代入
$result = $sth->fetchAll();
//プリペアドステートメントを使った例
//インスタンスの生成
$dbh = new PDO($dsn, $dbuser, $dbpass);
//SQL文の構造を準備
$sth = $dbh->prepare('SELECT * FROM table_name WHERE name = :name AND age = :age;');
//SQL文に値を結びつける
$sth->bindValue(':name', $name);
$sth->bindValue(':age', $age, PDO::PARAM_INT);
//SQLの実行
$sth->execute();
//結果の代入
$result = $sth->fetchAll();
プリペアドステートメントを使うと、まず最初にSQL文の構造を確定し、その後に値を結びつけて、実行します。この手順を踏むことによって、攻撃者が送信した不正な値によりSQL文の構造が改変されることを防ぐことができます。
ただし、mysqliを使っている場合には、この方法は使えません。
2. 入力値をエスケープする
mysqliでMySQLに接続しているならば、mysqli_real_escape_string()
関数を使いましょう。
//手続き型
$mysqli = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname);
$name = mysqli_real_escape_string($mysqli, $_POST['name']);
$age = mysqli_real_escape_string($mysqli, $_POST['age']);
$result = mysqli_query($mysqli, "SELECT * FROM table_name WHERE name = '$name' AND age = $age;");
//オブジェクト指向型
$mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
$name = $mysqli->real_escape_string($_POST['name']);
$age = $mysqli->real_escape_string($_POST['age']);
$result = $mysqli->query("SELECT * FROM table_name WHERE name = '$name' AND age = $age;")
mysqli_real_escape_string()
関数は、NULL
, \n
, \r
, \
, '
, "
, および Control-Z
をエスケープします。
CSRF(クロスサイトリクエストフォージェリ)
CSRFとは
CSRF(クリスサイトリクエストフォージェリ)とは、攻撃者が用意した外部のWebサイトからリクエストが送信され、不正な処理を実行される脆弱性またはその脆弱性を利用した攻撃手法のことです。
プログラムに送信されるデータは自身のWebサイトに設置したフォームに入力される値だけと思い込んでいると、この脆弱性が発生する可能性があります。
例えば、ログインしているユーザがパスワードを変更できるシステムがあるとします。ログイン時に認証が済んでいるので、パスワード変更時には改めて認証はしない仕様です。
ユーザがログインした状態のまま、そのWebサイトを離れて攻撃者の作成した罠サイトにアクセスしたらどうなるでしょう。その罠サイトにアクセスするとパスワードを変更するためのリクエストがログイン中のシステムに自動的に送信される仕組みになっています。パスワードが勝手に変更されてしまいますね。きゃー、怖い。
CSRF攻撃対策
CSRF攻撃を防ぐには、トークンを使用する方法が有効です。
ここでいうトークンとは、プログラムとやり取りをするときに使用する1回限りの使い捨ての合言葉みたいなものです。合言葉を知らない罠サイトからのリクエストを弾くことができます。具体的にやってみましょう。
フォームを表示するときの処理は次のようになります。
<?php
//セッションの開始
session_start();
//トークンの生成
$token = bin2hex(random_bytes(32));
//トークンをセッションに保存
$_SESSION['csrf_token'] = $token;
?>
<form action="" method="post">
<!-- トークンをフォームに埋め込む -->
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<!-- その他のフォーム要素 -->
<input type="submit" value="送信">
</form>
フォームからデータを受け取るPHPスクリプトは次のようになります。
//セッションの開始
session_start();
//トークンが送信されているかの確認
if (!isset($_POST['csrf_token'])) {
exit('不正なリクエストです');
}
//トークンが合っているかの検証
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
exit('不正なリクエストです');
}
以上のように、一意なトークンをフォームに埋め込むことで、CSRF攻撃を防ぐことができます。
クリックジャッキング
クリックジャッキングとは
クリックジャッキングとは、Webページ上に偽装したボタンやリンクを設置して、ユーザにクリックさせる攻撃手法です。
表示されているWebページの上に透明なページを重ねて、その透明なページ上にあるボタンなどをクリックさせることによって、悪意のあるコードを実行します。きゃー、怖い。
クリックジャッキング対策
クリックジャッキングはHTMLの<iframe>
タグを利用します。なので、<iframe>
の利用を制限するHTTPヘッダX-Frame-Options
を設定することで、クリックジャッキングを防ぐことができます。
下記のように設定すると、フレームへのページの埋め込みはすべて拒否されます。
header('X-Frame-Options: DENY');
下記のように設定すると、自身のサイトからの埋め込みのみ許可され、他のサイトからの埋め込みは拒否されます。
header('X-Frame-Options: SAMEORIGIN');
以上のように、フレームへの埋め込みを制限することで、クリックジャッキングを防ぐことができます。
でわでわ
無料で利用できるプログラミング学習サービスをお探しならば Code Lesson はいかがでしょうか。プロのエンジニアが監修した学習ロードマップで効率的に学習、AIに質問、最後にクイズで理解度をチェックできます。
コメント
コメント一覧 (2件)
tesu
他人のウェブサイトで脆弱性のテストをしてはいけません。犯罪とみなされる可能性があります。
あなたの接続元情報(*.*.ap.nuro.jp)は記録されています。気をつけてください。