チェ・ゲバムラの日記

脱犬の道を目指す男のブログ

【CakePHP2.x】セキュリティ~CakePHP2実践入門「13章」~

代表的な攻撃

SQLインジェクション

DBに対して外部から任意の操作を許してしまう問題。

select * from users where email='a@example.com' and
pass='pass01' limit 1;
に対して

フォームから
email : a@example.com
pass : pass01' OR 1=1
(最後に半角スペースあり)

結果
select * from users where email='a@example.com' and
pass='pass01' OR 1=1 limit 1;
常に真になってしまう!!!

対策
1.モデルのメソッドを使う
2.DataSourceクラスのfetchAllメソッドを使う

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

ユーザ入力値などのシステムから動的に出力する値をビューで表示する場合、
HTMLタグやスクリプト記号などを適切にエスケープしていないために
WEBページ改ざん、意図しないスクリプト実行を許してしまう問題。
悪用されると「クロスサイトリクエストフォージェリ」対策を向こうにしたり、
セッションハイジャック」を引き起こす可能性がある。

ケース1
変数の値をビューで出力してXSS発生
< script>alert( "aaa");< /script >が実行されてしまう

ケース1の対策
h()関数の利用


ケース2
フォームに変数の値を出力する

PHP< input type="text" name="name" value=" 変数 ">
ユーザ入力値
">< script>alert( ”aaa");< /script >
< script>alert( "aaa");< /script >が実行されてしまう

ケース2の対策
Formヘルパーを使う

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

ユーザが気づかぬうちにWEBシステムへのリクエストを送信して処理を実行させられる問題。
悪用すると気づかぬうちに記事投稿や商品購入、パスワード変更といった処理を実行させられる可能性がある。

発生例
正規サイト
hoge.com/csrf を用意。POSTされるとPOST OK!と表示するだけのプログラム。

攻撃者のサイト
atack.com/csrf.html を用意。アクセスされると自動でhoge.com/csrfにPOSTするプログラム。

アクセスしただけで処理が実行されてしまった!


対策
Securityコンポーネントをコントローラで指定するのみ。
これにより正規ユーザの意図したリクエストと判別できるようにトークンの識別処理を行う。

注意点1(フォーム作成の場合)
あくまでトークンによって判断しているため、トークン送信がないと正規であっても不正とみなしてしまう。
Formヘルパーのcreateメソッドもしくは
postLinkメソッドのhiddenフィールドとして出力される。
基本的にはFormヘルパーを使うこと。

注意点2(JSでフォームを動的変更の場合)
Securityコンポーネントにより、JSを動的に追加したりする場合は改ざんチェックに引っかかってしまう場合がある。
この場合はフォーム改ざんチェックを無効化し、CSRF対策のみ実行すること。
無効化の方法はSecurityコンポーネントの$validatePostプロパティにfalseを設定する。

ex.
public function beforeFilter(){
$this->Security->validatePost = false;
}

更にこのままだと細工したフォームによる意図しないデータ更新を許す可能性があるため、
登録するパラメータだけを抽出、新たに連想配列を作成して値を入れ直す必要がある。


注意点3(Ajaxで画面遷移せずPOSTリクエスト送信の場合)
Securityコンポーネントが利用するトークンはリクエストごとに値が変化するため、
同じトークンを繰り返し送信するとエラーになる。
Ajaxだとこれになる場合がある。
Securityコンポーネント>$csrfUseOnceプロパティにfalseを設定すると繰り返し利用可能となる。

ex.
public function beforeFilter(){
$this->Security->csrfUseOnce = false;
}

セッションハイジャック

攻撃者が第三者のセッションIDを入手、
ログインセッションなどを乗っ取り、なりすましが可能になる問題。

対応方法
クロスサイトスクリプティングなどのでセッションIDが漏洩しないようにすることが大切。
更に漏洩した場合、被害発生確率を下げるためにセッションIDを変更する。


対策1(セッションID自動変更)
autoRegenerateをtrueにすると10回リクエストごとにセッションIDを自動更新する。

/app/Config/bootstrap.php
Config::write('Session',array(
    'defaults' => true,
));

更に、リクエスト回数を指定する場合は下記。

<?php
App::uses('CakeSession','Model/Datasource');
CakeSession::$requestCountdown = 1;


対策2(セッションIDを任意のタイミングで変更)
SessionコンポーネントもしくはCakeSessionクラスのrenewメソッドを実行します。
ログイン処理でユーザ情報をセッションに格納した場合はセッションIDを変更しておく。

<?php
//Sessionコンポーネントの場合
$this->Session->renew();

//CakeSessionクラスの場合
App::uses('CakeSession','Model/Datasource');
CakeSession::$requestCountdown = 1;

renewメソッド実行前にセッション操作を行ってない場合はセッションは変更されない。
この場合は実行前にCakeSessionクラスのstartメソッドを実行する。

<?php
App::uses('CakeSession','Model/Datasource');
CakeSession::start();
$this->Session->renew();

CakePHP特有の問題を防ぐ

意図しないコントローラメソッドの実行

CakePHPデフォルトのルーティングではアクセスされたURLから実行するコントローラ、アクションメソッドを自動実行するが、
内部活用の為のメソッドを外部から実行される可能性がある。

対策1(メソッドのアクセス制御子をprotectedかprivateにする)
対策2(メソッド名をアンダースコアから始める)

細工をしたフォームによる意図しないデータ更新

FormヘルパーからPOST送信した値をそのままモデルのsaveメソッドへ渡すと、
不正なパラメータが送信された場合に想定外のカラムが更新される可能性がある。
ex
nameを新規登録するだけのサービスなのに、

email:などと入力して更新する予定ではない箇所まで更新されてしまう等。

対策1(Securityコンポーネントを使う)
利用するとデフォルトでフォームの改ざんチェックが有効となり、
登録処理はエラーとなって処理される。


対策2(登録するパラメータだけ抽出する)※著者推奨
そのまま、想定外のパラメータがきても無視する。
save時はそのまま保存するのみ。

<?php
$data = array(
    'name' => Set::extract($this->request->data, 'User.name'),
);
$this->User->save($data);


※不十分な対策(モデルのsaveメソッドで更新パラメータを指定する)
対策2に似て非なるもの。
saveメソッドの第三引数で更新パラメータの指定は可能だが、
実行されるSQL文に主キーの指定(where id = 1)が入るため、任意のレコードのnameを変更できてしまう。

認証をかけているつもりでも処理が動作してしまう

Security,Authコンポーネントを利用しても、処理順番によっては認証が掛からない場合がある。
どちらも認証処理はstartupメソッドで実行されるが、
startup前の処理は認証が掛かっていても無条件に実行される。

アプリケーション側で注意するべき箇所は下記。
注意箇所1(コントローラのbeforeFilterメソッド)
これはstartupメソッドより先に実行される。
ここに認証後に実行するべき処理を記述しないようにする。

注意箇所2($componetsプロパティで指定しているコンポーネント
記述したコンポーネントは、記述した順序で実行される。

public $components = array('Sample' , 'Auth', 'Security');
よりも
public $components = array('Auth', 'Security', 'Sample');
の方が良いかもしれない。