Anders 的回答解决了这个问题,但只提供了在会话中存储令牌副本的解决方案。也可以存储令牌客户端,只要它受到适当的保护以免受重放攻击 - 但这与会话劫持问题有很大的重叠......
if ($_REQUEST['token']) {
if (validate($_REQUEST['token'], $_COOKIE['ctoken'])) {
// do the protected thing....
} else {
// handle exception
}
}
$token=sha1(openssl_random_pseudo_bytes(40)) . ":" . time();
$client_stored_token=encrypt($token, create_key);
set_cookie('ctoken',$client_stored_token);
function validate($token, $client_stored_token)
{
$client_token=decrypt($client_stored_token, create_key());
@list($rand_bytes, $timestamp)=explode(':', $client_token);
if ($rand_bytes==$token && time()-$timestamp<MAX_THRESHOLD) {
return true;
}
return false;
}
function create_key()
{
return sha1($_SERVER['HTTP_USER_AGENT']
.(int)(ip2long($_SERVER['REMOTE_ADDR'])/65536)
.SOME_STATIC_SALT);
}
(也可以通过 window.name 传播令牌,但这需要更多考虑)。
安德斯指出:
浏览器不允许修改引用标头,因此攻击者无法伪造。
不幸的是,在各种浏览器中,referer 标头存在很多问题——如果你依赖它,你就会陷入困境。
引用者通过 HTTP 标头以明文形式传递并因此可能被欺骗的事实
即使是最复杂的 CSRF 和会话保护机制也很容易被 HTTP 连接上的 MITM 击败。这就是我们使用 HTTPS 的原因。