どもです。
PHPでセッション認証を行っているサイトがあります。
しかしこのサイトにはCSRF攻撃を行う隙が存在していました。
Ajaxを使用した際にはセッション認証を行っていなかったのです。
このサイトのAjax実行時にセッション認証を行い、CSRF攻撃を防ぐことにしました。
Ajaxにトークンを送る
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="csrf-token" content="<?= $csrf_token ?>">
<title>***</title>
<script src="ajax.js"></script>
</head>
Ajaxで値を送るには、当然ですがHTML上にトークンを記載しなければなりません。
PHPのセッション認証は基本<form>内に<input type="hidden" name="csrf-token">を設置しますが、Ajaxの場合はメタタグで書くのが一般的だそう。
この際に注意するのは、JSの読込より前にメタタグを置くこと。
でないと、次の処理でトークンを回収しようと思ったらundefined!なんてことに。
冷静に考えれば当たり前ですが、これちょっとハマりかけました自分。
送信する際は既存のAjax処理全てに載せなければならないので、ajaxSetupを使います。
$.ajaxSetup({
headers:{
'csrf-token': $('meta[name="csrf_token"]').attr('content')
}
});
headersを設定すると、HTTPヘッダ情報として値を送信できます。[詳細]
PHPでトークンを受け取る
さて、受信側です。
$_SERVER['HTTP_CSRF_TOKEN']
送信したパラメータは、このような指定で取り出せます。
HTTPの「CSRF_TOKEN」という意味です。専用の命名規則ではありませんので、JS側を変更すればお好みの['HTTP_○○']が取れます。
echo (checkToken($_SERVER['HTTP_CSRF_TOKEN']))? 'ok' : 'ng';
function checkToken($chk_token){
if($chk_token === $csrf_token){
return true;
}else{
return false;
}
}
取り方が分かれば、既存の認証処理にかければ認証部分はクリア。
新しいセッショントークンを受け取る
セッション認証をクリアしたら、セッショントークンを更新しなければなりません。
それ自体は既存処理を通って終わりですが、生成した新規トークンをAjaxに返さなければ。
しかし、
echo $new_csrf_token;
では、Ajaxの処理結果に干渉してしまいます。
こいつはあくまでAjax処理を実行するための認証で、認証をするためにAjaxを実行しているわけではないのですから。
出力テキストではなく、Ajaxの戻り値にデータを載せることは可能なのか…?
手段、ありました。
header("new-csrf-token: ".$new_csrf_token);
受信側は、こう。
$(document).ajaxSuccess(function(e, xhr, options) {
let new_csrf_token = xhr.getResponseHeader('new-csrf-token');
});
あとは、HTML側のトークン&ajax.headersを更新してやれば、無限に認証ができます。
$(document).ajaxSuccess(function(e, xhr, options) {
let new_csrf_token = xhr.getResponseHeader('new-csrf-token');
//PHPの認証用(既存のトークン用input)
$('input[name="csrf-token"]').val(new_csrf_token);
//メタタグ
$('meta[name="csrf-token"]').attr('content', new_csrf_token);
//ajax.headersの再設定
$.ajaxSetup({
headers:{
'csrf-token': $('meta[name="csrf-token"]').attr('content')
}
});
});