CakePHP セキュリティ対策について考える(SQLインジェクション、XSS、CSRF)

SQLインジェクション

概要

SQLを使って不正にデータベースを操作する攻撃

攻撃例

ユーザー名とパスワードを入力してログインする処理があるとする。
username と password の組み合わせが、データベースのものと一致すれば認証するという仕組みだとする。

SELECT * FROM users WHERE username='$username' AND password='$password'

ここで、($username: admin, $password: ' OR 'a'='a)と入力すると、SQLは以下のようになる。

SELECT * FROM users WHERE username='admin' AND password='' OR 'a'='a'

これが実行されると、パスワードは常に真なので、パスワードが間違っていても認証されてしまう。

対策

特殊文字を適切にエスケープするなどの対策が必須となる。

CakePHPの場合、find や read などの組込関数を使っていれば自動処理されるので対策は不要。
よって、なるべくCakePHPの組込関数を使うようにしましょう。

XSS(クロスサイトスクリプティング)

概要

悪意のあるコードを訪問者のブラウザに送信する攻撃

攻撃例

フォームから、以下のようなスクリプトを送信すると、ユーザのクッキー情報が抜かれる。

<script>alert(document.cookie);</script>

対策

サニタイズ(無効化)する。

もっとも一般的なのは、htmlspecialchars でタグを無効化する方法。
だが、この方法では、スクリプトだけではなく、すべてのタグが無効化されてしまう。

CakePHPでは、「Sanitize」というライブラリが用意されている。
問題となりうるタグだけを取り除き、それ以外のタグは有効にできたりする。

まず、Sanitizeライブラリを読み込む。

App::import('Sanitize');

Sanitize::html($str, $options);

htmlspecialchars() とほぼ同じ。
CakePHP1.3では、htmlentities()、1.2では独自パターンでリプレースされる。

[オプション]

キー 初期値 説明
remove false trueの場合は該当文字を削除。falseの場合はエスケープされた文字に変換
cahrset false 文字コード
quotes ENT_QUOTES クオートタイプ

Sanitize::stripWhitespace($str);

空白文字、改行、タブ文字を除去。

Sanitize::stripImages($str);

imgタグを除去し、alt属性の文字列だけにする。

Sanitize::stripScripts($str)

linkタグのCSS、imgタグ、scriptタグ、style属性を除去。

Sanitize::stripAll($str);

stripWhitespace(), stripImages(),stripScripts() を同時に実行

Sanitize::stripTags($str, $tagName, ・・・);

HTMLタグを除去。第2引数以降にタグ名を指定。

Sanitize::clean($data, $options);

複数のサニタイズを選択して実行。
配列を渡すことも可能。

[オプション]

キー 初期値 説明
odd_spaces true 通常の半角スペースのほか、Oxcaの特殊スペースも除去
encode true html()を実行
remove_html false html()実行時、htmlを除去
dollar true $マークを除去
carriage true 「¥r」を除去
unicode true 数値文字参照がエスケープされている場合に復元
escape true escape()を実行
connection defalt escape()実行時、使用するデータソースコネクションを指定

対策例

> app_controller

function __sanitize() {
	App::import('Sanitize');
	$this->data = Sanitize::clean($this->data, array('remove_html' => true));
}

> コントローラ

function add() {
	if (!empty($this->data)) {
		$this->__sanitize();
		$this->Form->create();
		if ($this->Form->save($this->data)) {
		} else {
		}
	}
}

これで、「スクリプト<script>alert(document.cookie);</script>」 を送信しても、「スクリプトalert(document.cookie);」 にサニタイズされる。

※補足(2011/11/24)

上記の例では入力時にサニタイズしていますが、出力時にサニタイズした方がより安全だとのご意見もいただきました。

その場合は、ビューに送る直前でサニタイズします。

$this->set("result", Sanitize::stripAll($result);

また、ビュー側でエスケープするのも有効です。

echo h($result['Form']['name']);

h() は、htmlspecialchars と同等。

CSRF(クロスサイトリクエストフォージェリ)

概要

管理者権限をもつユーザーにURLをクリックさせて不正な操作を強いる攻撃。

攻撃例

MVCモデルでは、「http://xxx/コントローラ/アクション/パラメータ」となる。
プログラムの作り方によっては、「http://xxx/コントローラ/delete/1」というURLを1クリックすることで、id=1のデータが削除されてしまう。

Auth や ACL でアクセス制限をかけたとしても、管理者権限でログインしているユーザーが、「<a href= "http://xxx/users/delete/1">いますぐクリック!</a>」というリンクをクリックしてしまったら無意味。

対策

ワンタイムトークンを発行して送信元の整合性をチェックする。

CakePHPでは、セキュリティコンポーネントを使う。

まず、セキュリティコンポーネントを読み込む。

var $components = array('Security');

これだけでトークンが発行される。

コントローラの beforeFilter メソッドで、トークンをチェックするアクションを指定。
必要に応じてブラックホールメソッドも設定する。

> コントローラ

function beforeFilter() {
	$this->Security->requireAuth();
	$this->Security->blackHoleCallback = 'error';
}
function error() {
	// エラー時の処理
}

ただし、この requireAuth は、POST経由でしか受け付けない。

GET経由でワンタイムトークンを使う

「http://xxx/コントローラ/delete/1」という作り方をしている場合、トークンのチェックは自分で行う。

[対策前]

> ビュー

<?php echo $html->link('削除', array('action' => 'delete', $users['User']['id']), null, '削除しますか?'); ?>

> コントローラ

function delete($id=null) {
	// 削除処理
}

[対策後]

> ビュー

<!php echo $html->link('削除', array('action' => 'delete', $users['User']['id'], $this->params['_Token']['key']), null, '削除しますか?') ?>

> コントローラ

function delete($id=null, $token=null) {
	if ($token != $this->params['_Token']['key']) {
	// エラー処理
	}
	// 削除処理
}

参考

・SQLインジェクションで攻撃された例→ 「価格.comショック」不正アクセスの詳細
・XSSで攻撃された例→ YouTubeにXSS攻撃、不正ポップアップなどの被害広がる
・CSRFで攻撃された例→ 大量の「はまちちゃん」を生み出したCSRFの脆弱性とは?

次々と裏を突く攻撃手法が登場しており、セキュリティ対策は「これで完璧」とはいかないのが現状です。
Webサイト運営者、Webシステム開発者は、常にチェックを怠らないことが最善の対策となります。