提示:这篇文章没有包含新内容或重大突破。文章中的主题你应该早就熟悉,因为之前已经有很多比我聪明的人写过这方面的文章。不幸的是,尽管有很多相关的资料,人们还是一直在错误的使用这个东西。
其实,跨站请求伪造已经是一个众所周知的话题了,几乎在每个安全讨论、书籍和网站中都会出现。这很不错,它们确实很重要(不过我希望人们能够从全局上考虑安全问题,而不是某个方面)。 大部分的网站和我读过的所有书籍,它们在理论的阐述上都是正确的,但是在讲到实际实现时,却全是错误的。在本文中,我们将把CSRF示例这篇文档作为一个例子。
为了有效地使用常用的透明防护策略来抵抗CSRF攻击,你需要生成一个不可预测的token,然后在表单处理页面验证session和表单中是否同时存在这个token。这里只有两个需要关注的地方,但是我看到很多示例代码都会在这里出错。
比较
其实,CSRF中关键点是验证session中的token和表单中包含的token是否一致。这很简单但是很关键,很多人就栽在这里了。他们写的代码常常像下面的例子:
if ($_POST['csrf'] == $_SESSION['csrf'])
你发现其中致命的错误了吗?如果session和表单中该字段同时为空,它们也是相等的!所以你想攻击的话,只需要简单的在表单中删除该字段就可以了,我在疏忽导致的CSRF攻击一文中详细解释了这种攻击。你不仅仅需要验证它们相等,而且还要保证它们已经被设置且非空。
在那篇文章的试验中,我特意把PHP的警告给打开,我可以关闭错误显示或取消警告输出,但我的目的是要显示到底发生了什么事情。(译注:就是在php.ini中进行配置,让php显示warning消息,这样就可以看到php会提示session和表单中没有设置csrf变量)
不可预测的Token
这部分也比较简单,就是产生攻击者无法预测的token。我没有说一定要用随机数,尽管那是一个不错的做法,关键的地方就是要让攻击者无法猜测token值。这是一个老生常谈的问题,但确实一直存在。时间不是随机的,它在一秒秒的增加。md5值也不是随机的。由于滚雪球效应,两个相似值的哈希值可能大相径庭,但是它们也不是随机值。因此,两个非随机值的计算结果也是一个非随机值。然而我经常看到以下代码:
$csrfToken = md5(time());
这是一个愚蠢的做法。这个哈希函数没有解决任何问题,这是一个很脆弱的token。现在,我在一些讨论会上提及这个问题,听众一般会礼貌性地点头赞同,然后私下里告诉我它确实没有任何用处。事实确实如此,请参看md5时间攻击一文。
即使添加一些salt也是无法解决这个问题,只是会让它变得更脆弱。如果你对此有兴趣,可以查看我的弱CSRF中的代码。
Stefan Esser在ZendCon大会上做了题为“PHP中隐藏的安全问题”的演讲,里面讨论了很多session相关的问题,你可以去查看一下。
因此,赶紧回去修改自己的CSRF页面吧。