はじめに
Amazonや楽天などのECサイトや、FacebookやTwitterなどのSNSサービスといった日頃使用しているWebサービスは、多くの場合個人情報やクレジットカードなどの情報を取り扱っています。
一度登録しておくと情報を引き継げるため大変便利ですが、これらの情報を抜き取ったり、悪用したりする人々が少なからず存在します。
こういった攻撃の狙い目のことを「脆弱性」といい、「脆弱性」を狙った攻撃を防ぐための対策を、一般に「Webセキュリティ対策」と呼んでいます。
今回はこれらの「脆弱性」を狙った代表的な3つの攻撃方法と、PHP、CakePHP、Laravelでのそれぞれの対策を紹介します。
「脆弱性」を狙った代表的な攻撃方法3つ
①XSS(クロスサイトスクリプティング)
②CSRF(クロスサイトリクエストフォージェリ)
③SQLインジェクション
これらに対するそれぞれの対策を以下に記述します。
①XSS(クロスサイトスクリプティング)
XSSはサイト自体ではなくユーザーに対して被害がある攻撃手法です。
脆弱性のあるWebサイトに対して、悪意のある不正なスクリプトを挿入することで、サイトに訪れたユーザーに実行させるといった流れとなっています。
例:コメントを投稿するフォーム
<?php
<h1>投稿フォーム</h1>
<form action="comment_complete.php" method="post">
<input type="text" name="comment"><br/>
<input type="submit" value="送信">
</form>
入力フォームに以下のようなjsコードを書かれ、XSS対策をしないまま出力するとコードが実行されてしまいます。
これによって、そのページにアクセスした人全員が強制的にalertメッセージが表示されてしまいます。
<script> window.alert('このメッセージを表示したユーザー全員にアラートメッセージを表示');</script>
XSSの対策:サニタイズ(無効化)する
サニタイズ:ユーザからフォームの値を取得した際、htmlで特別な意味を持つ以下の記号を別の記号に置き換えること
記号 | サニタイズ |
---|---|
< | < |
> | > |
& | & |
" | " |
' | ' |
PHPの場合:htmlspecialchars()を用いる
<?php
$comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
echo $comment;
CakePHPの場合:h()メソッドを使用する
htmlspecialchars()と同義となっています。
<?php
echo h($comment);
また、入力画面でFormHelperを使用することで、自動でサニタイズしてくれます。
<?php
echo $this->Form->control('comment', ['label' => 'コメント']);
Laravel(blade)の場合: {{ }}で囲う
これによって、自動的にhtmlspecialchars関数を通してくれます
<?php
{{ $comment }}
詳しくはこちらに記述しています。
②CSRF
CSRF(クロスサイトリクエストフォージェリ)もXSSと同様に、ユーザーに対して被害がある攻撃手法です。
悪意のあるWebサイトにスクリプトや自動転送(HTTPリダイレクト)を仕込むことによって、ユーザーに別のWebサイト上で何らかの操作を意図せずに行わせます。
例:パスワード変更画面
<?php
<h1>パスワード変更画面</h1>
<form action="http://hoge.com/password_change_complete.php" method="post">
<input type="hidden" name="password" value="password">
<input type="text" name="password">
<input type="submit" value="変更する">
</form>
このようなCSRF対策のされていないパスワードの変更画面でパスワードを変更すると、一定時間、cookie情報は保持されます。
cookieが保持されている状態で、以下のようなタグが入っている悪意のある別のWebページにアクセスすると、攻撃者用ページからiframe内で会員情報変更処理ページに自動でPOST送信されます。
以下をユーザーがアクセスすると、パスワードが勝手に「cracked」に書き換わってしまいます。
<iframe src="hack-iframe.html" width="0" height="0">
<body onload="document.forms[0].submit()">
<form action="http://hoge.com/password_change_complete.php" method="POST">
<input type="hidden" name="password" value="cracked">
</from>
</iframe>
CSRFの対策:データを送信するページにトークンを埋め込む
リクエスト送信フォーム内にhidden値でワンタイムのtokenを埋め込み、同一セッション内での一致確認を行います。
PHPの場合:inputのhiddenにトークンを埋め込み、受信側でトークンのチェックをする
// 送信側.php
<?php
session_start();
if (empty($_SESSION['token'])) { // トークンが空の場合、生成する
$token = bin2hex(openssl_random_pseudo_bytes(24));
$_SESSION['token'] = $token;
} else { // トークンがあれば使用する
$token = $_SESSION['token'];
}
?>
<h1>パスワード変更画面</h1>
<form action="http://hoge.com/password_change.php" method="post">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
<input type="text" name="password">
<input type="submit" value="変更する">
</form>
// 受信側.php
<?php
session_start();
$token = filter_input(INPUT_POST, 'token');
if (empty($_SESSION['token']) || $token !== $_SESSION['token']) {
die('CSRF攻撃が発生');
}
CakePHPの場合:コントローラーにコンポーネントを追加する
CakePHPには共通のコントローラごとに共通の処理を支援する、コンポーネントという機能があります。
この中でトークンを埋め込み、CSRF対策をしてくれるコンポーネントが提供されています。
<?php
class AppController extends Controller {
{
public $components = array('Security');
}
}
Laravel(Blade)の場合:viewファイルのformタグ内に「@csrf」と記述する
<?php
<form action="{{ url('/password_change') }}" method="POST">
@csrf
...
</form>
③SQLインジェクション
入力フォームなどで意図しないSQLクエリを無理やり実行させることで、データベースを操作する攻撃のことです。
例:会員ログイン画面
<h1>会員ログイン画面</h1>
<form action="http://hoge.com/login_complete.php" method="post">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token, ENT_COMPAT, 'UTF-8'); ?>">
<input type="text" name="id">
<input type="text" name="password">
<input type="submit" value="ログインする">
</form>
<?php
//SQLクエリの生成
$sql = "SELECT * FROM users WHERE id = '".$_POST['id']."' AND password = '".$_POST['password']."'";
//クエリの実行
$data = $pdo -> query($sql) -> fetch(PDO::FETCH_ASSOC);
if ($data) {
echo 'ログイン完了';
}
上記のフォームの場合、
- id:「` OR 1=1;--」
- password:未入力
で送信ボタンを押下すると、ログインが行えてしまいます。
※「` OR 1=1;--」:1=1が正しければSQLクエリを実行し、--でpasswordをコメントアウトしているため実行されてしまっています。
SQLインジェクションの対策:SQL文を組み立てる際に、SQL文の変更をさせないようにする
PHPの場合:prepare()を使用する
<?php
//SQLクエリの生成
$sql = "SELECT * FROM users WHERE id = :id AND password = :password";
//クエリの実行準備
$sth = $pdo -> prepare($sql);
//検索クエリの設定と実行
$sth -> bindValue(':id', $_POST['id']);
$sth -> bindValue(':password', $_POST['password']);
$sth -> execute();
//結果を配列で取得
$data = $sth -> fetch(PDO::FETCH_ASSOC);
if($data){
echo 'ログイン完了';
}
CakePHPの場合:クエリービルダーでDB操作を行う
公式ドキュメントより引用
クエリービルダーは裏側でPDOプリペアードステートメント(prepared statement)を使うことで、 SQLインジェクション攻撃から守っています。
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\TableRegistry;
class SampleController extends AppController
{
public function login()
{
// テーブルクラスのインスタンスを取得
$users = TableRegistry::get('Users');
//クエリの実行
$users = $users->find();
}
}
Laravelの場合:クエリービルダーもしくは、EloquentORMでDB操作を行う
<?php
namespace App\Http\Controllers;
// クエリービルダー
use Illuminate\Support\Facades\DB;
// Eloquent ORM
use App\Models\User;
class SampleController extends AppController
{
public function login()
{
// クエリービルダー
$users = DB::table('users')->get();
// Eloquent ORM
$users = User::all();
}
}
まとめ
フレームワークでは組み込み関数などのもともと組み込まれているする機能を使用することで、セキュリティ対策を安全かつ楽に行うことができます。
対策\フレームワーク | PHP | CakePHP | Laravel |
---|---|---|---|
XSS | htmlspecialchars() | h() | {{ }} |
CSRF | トークン埋め込み処理書く | コンポーネントの追加 | @csrf |
SQLインジェクション | prepare() | クエリービルダー | クエリービルダー or Eloquent ORM |
対策\言語orフレームワーク | PHP | Laravel |
---|---|---|
XSS | htmlspecialchars() | {{ }} |
CSRF | トークン埋め込み処理書く | @csrf |
参考
- 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 徳丸 浩 (著)
- XSS
- CSRF
- SQLインジェクション