PHP中的CSRF(Cross-site request forgery)攻击示例和预防

Posted

技术标签:

【中文标题】PHP中的CSRF(Cross-site request forgery)攻击示例和预防【英文标题】:CSRF (Cross-site request forgery) attack example and prevention in PHP 【发布时间】:2011-02-01 08:41:10 【问题描述】:

我有一个网站,人们可以像这样进行投票:

http://mysite.com/vote/25

这将对第 25 项进行投票。我只想将其提供给注册用户,并且仅当他们想要这样做时。现在我知道有人在网站上忙的时候,有人给他们这样的链接:

http://mysite.com/vote/30

那么投票将是他在该项目上的位置,而他不想这样做。

我看过explanation on the OWASP website,但我不是很明白

这是 CSRF 的一个例子,我该如何防止这种情况。我能想到的最好的事情是在链接中添加一些东西,比如哈希。但是,在所有链接的末尾放置一些东西会很烦人。有没有其他方法可以做到这一点。

另一件事可能有人可以给我一些其他的例子,因为这个网站对我来说似乎相当神游。

【问题讨论】:

【参考方案1】:

首先,不应使用 GET 请求来更改服务器上的状态,因此对于您的投票服务,我建议使用 POST/PUT。这只是一个指导方针,但却是一个聪明的指导方针。

所以对于您的问题,CSRF 是一个客户端问题,因此您使用哪种服务器语言(在您的情况下为 php)并不重要。标准修复是相同的,如下所示:在 URI/POST 数据中具有随机值,在 Cookie 标头中具有相同的值。如果这些匹配,您可以确定没有 CSRF。在 *** 上有很多关于如何做到这一点的信息,例如。 this one. 祝你好运!

【讨论】:

【参考方案2】:

CSRF 攻击中有 3 名玩家

    受害者网站(在您的示例中为您的投票网站)[知道他的登录用户 cookie] 您客户的浏览器(当他登录时)[知道他的 cookie] 攻击者网站 [不知道登录用户的 cookie]

CSRF 攻击取决于两个事实

    浏览器会在每次请求时自动发送 cookie 我们依靠 cookie 来识别我们的登录用户(例如:setcookie("sessionID", "0123456789ABCDEF", time()+3600);

如果攻击者可以离开或其他人使登录用户请求此

// http://victim.website/vote/30

例如,通过将链接放在攻击者网站上或通过电子邮件发送,登录的客户端浏览器将连同此请求一起发送识别 cookie(sessionID),这将使受害者网站认为他已登录用户真的很想投票!

但是,如果受害者的网站更聪明,并使用额外的 GET 或 POST 参数(不是 cookie)验证登录用户的请求,攻击者现在就会遇到问题,因为浏览器不会自动发送 GET 和 POST 参数,他必须猜。

// http://victim.website/vote/30?csrfSecret=0123456789ABCDEF

攻击者不知道csrfSecret参数,这是受害者网站和他的客户端之间的秘密(就像会话令牌一样),因此攻击者无法构建他想要伪造请求的URL .

同样,如果通过 POST 请求进行投票,攻击者将无法在其网站(或第三方网站)上制作表单,因为他不知道受害者网站与其用户之间的秘密。

<form method="post" action="http://victim.website/vote" >
    <input type="hidden" name="vote" value="30">
    <input type="hidden" name="csrfSecret" value="????? I don't know it :(">
</form>

【讨论】:

【参考方案3】:

OWASP 有一个用于 PHP 的 CSRFGuard 和一个用于 PHP 的 ESAPI,这是我很久以前为 XMB -> UltimaBB -> GaiaBB 编写的。

http://code.google.com/p/gaiabb-olpc/source/search?q=function+get_new_token&origq=function+get_new_token&btnG=Search+Trunk

似乎其他一些人已经清理了该代码并允许使用更强大的令牌:

https://www.owasp.org/index.php/PHP_CSRF_Guard

谢谢, 安德鲁

【讨论】:

【参考方案4】:

这可能成为 CSRF 的一个例子,如果:

获取该链接(例如通过&lt;img&gt; 标签):伪造 来自另一个站点:跨站点

例如,如果我可以在 *** 的 html 源代码中注入这个 &lt;img&gt; 标签 (我可以,因为 *** 允许在他的帖子中使用 &lt;img&gt; 标签)

<img src="http://mysite.com/vote/30" />

你只会投票给那个项目 ;-)

通常使用的解决方案是在 URL 中放置一个生命周期有限的令牌,并在获取 URL 时检查此令牌是否仍然有效。

基本思路是:

生成页面时: 生成唯一令牌 将其存储在用户的会话中 并将其放在页面的链接中——看起来像这样:http://mysite.com/vote/30?token=AZERTYUHQNWGST 调用投票页面时: 检查令牌是否存在于 URL 中 检查它是否存在于用户的会话中 如果不是 => 不登记投票

有这样的想法:

令牌的生命周期不长,而且难以猜测 这意味着你的攻击者: 只有几分钟的窗口,在此期间他的注射将有效 一定要善于猜测^^ 必须为每个用户生成不同的页面。

另外请注意,用户离开您的网站后会话保持活跃的时间越短,当他访问不良网站时会话仍然有效的风险就越小。

但在这里,您必须在安全性和用户友好性之间做出选择...

另一个想法(这不是完全安全,但有助于防止不知道如何强制 POST 请求的人),将仅在人们投票时接受 POST 请求:

浏览器正在为注入的标签发送 GET 请求 由于此 URL 正在修改一些数据,无论如何,它不应该与 GET 一起使用,而只能与 POST 一起使用

但请注意,这并不完全安全:(可能?)可以通过一些 javascript 来强制/伪造 POST 请求。

【讨论】:

你说得对,伪造 GET 就像伪造 POST 请求一样容易。虽然我不同意必须有一个过期的令牌。如果攻击者能够获取您的会话数据,那么您遇到的麻烦比一些额外的投票更大。但是您建议的修复仍然有效,因为密钥只是在 Cookie 和请求数据中具有令牌/随机值(保存在 cookie 中或绑定到用户会话密钥)。 感谢两位的建议。我将更改所有链接以获取此令牌。我不得不同意这是一种更省钱的方法。但是我不打算实现令牌到期,我同意 MygGaN 的这一点。 你们中的任何人都知道当我使用 AJAX 进行投票时该怎么做。我是否应该在用户在页面上时重复使用相同的密钥,并且只在他刷新时生成一个新令牌。或者我是否必须在投票完成时为所有链接提供一个新令牌。 不确定该问题是否有明确的答案,但您必须认为“过于频繁”刷新令牌会导致麻烦;特别是,如果用户在他的浏览器中打开了您网站上的多个选项卡,并且令牌从其中一个发生更改,该怎么办? 修改服务器状态的操作应该始终需要 POST 请求,否则网络爬虫、预爬虫等。可能会触发状态变化

以上是关于PHP中的CSRF(Cross-site request forgery)攻击示例和预防的主要内容,如果未能解决你的问题,请参考以下文章

Cross-site request forgery

CSRF(Cross-site request forgery)

跨站请求伪造CSRF(Cross-site request forgery)

浅谈CSRF(Cross-site request forgery)跨站请求伪造(写的非常好)

如何防止 PHP、csrf、xsrf 中的表单重放/中间人攻击

Cross-Site Scripting