CSRF

参考文章·
在这一章节中,我们将会讲解什么叫做CSRF,CSRF常见的漏洞场景,以及CSRF的防御措施

什么是CSRF

跨站请求伪造,又被称为CSRF,是一个web漏洞,该漏洞可能会导致攻击者诱导用户执行该用户非本意要去执行的操作。该漏洞可导致攻击者在一定程度上规避同源策略,该策略主要是设计用来阻止不同的网站互相引用对方的资源

CSRF攻击带来的影响是什么

在一个成功的CSRF攻击中,攻击者导致受害者执行自己本来不想执行的操作。比如说,这种攻击可能会导致用户的账户绑定的邮箱被篡改、或者账户密码被篡改、或者执行转账操作。取决于操作的特点,严重的可能导致攻击者直接获取到用户账户的完全控制权,如果被控制的用户是该web应用的管理员,那么攻击者可能会直接拥有该应用的数据以及功能的绝对控制权。

CSRF的原理

要想CSRF攻击成功,下面这三个关键一定要满足。

  • 必须要有一个相关的操作,也就是说必须要有一个web应用的动作是值得攻击者去诱导的。这个动作可能是一个拥有一定的权限之后才能执行的操作(比如说更改其他用户的权限),或者跟指定用户数据相关的操作,比如更改用户的密码
  • 基于cookie的会话处理,执行这个操作需要发送一个或者更多的HTTP请求,而且用用程序仅仅依靠会话cookie来判断发出请求的用户身份。除此之外,再无其他的用于识别用户身份的机制。
  • 没有不可预测的请求参数,执行该操作的请求中必须不能包含任何攻击者不能决定或者猜测出来的值。比如,当我们想篡改用户的密码时,如果该应用的机制要求我们必须知道原来的密码,那我们就无法实施攻击了。

举个例子,有一个应用,他的其中一项功能是可以通过发送请求来更改用户的账户邮箱,当用户执行该操作时,会发出下面这样的HTTP请求包

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE

[email protected]

这个场景正好符合了CSRF攻击的三个条件:

  • 攻击者对这种操作感兴趣,通过这种操作,攻击者可以通过出发密码重置来获取到受害者用户账户的控制权
  • 这个web应用使用了一个会话cookie来识别发出该请求的用户身份。除此之外,没有其他的措施来对用户身份进行识别。
  • 攻击者可以很容易地决定执行该操作所需要的参数。

在这种情况下,攻击者可以很容易地构造出下面的表单:

<html>
  <body>
    <form action="https://vulnerable-website.com/email/change" method="POST">
      <input type="hidden" name="email" value="[email protected]" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

如果受害者访问了攻击者构造的这个web页面,那么受害者的邮箱账户将会被重置为攻击者设置的邮箱,下面是受害者访问完该页面后将会发生的事情。

  • 攻击者页面将会向该网站发送一个HTTP请求
  • 如果此时用户在该网站中处于登录状态,那么受害者的浏览器会自动将受害者在该网站中的会话cookie包含进HTTP请求中
  • 这个有漏洞的网站会依然按照正规流程去处理刚才提交的请求,把该请求当作是受害者提交的请求来进行处理,并将其账户邮箱更改为攻击者指定的邮箱

注意,虽然CSRF大部分都是和基于cookie会话的应用相关,但是其他的一些不使用cookie作为用户身份认证机制的web应用,比如将用户凭证作为识别用户身份的方法,比如HTTP basic认证和基于证书的认证

如何构造CSRF攻击

手动构造CSRF攻击通常来讲是很麻烦的,尤其是我们想要构造的请求中包含大量的参数时,或者是请求中还包含其他比较奇怪的东西时。构造CSRF攻击最简单的的方式就是使用CSRF生成器,这个工具是包含在BurpSuite专业版中的。使用方式如下:

  • 使用burp抓取请求包,然后在burp中选取出你想要测试或者利用的一段请求
  • 右键选择engagemen tools/Generate CSRF POC
    • 在这里插入图片描述
  • burp将会自动根据请求包生成相应的表单
  • 你可以在burp的poc生成器中进行各种微调,来适应不同情景的要求
  • 将生成的html代码复制到网页中,使用已经登陆到包含漏洞的网站中的浏览器来访问我们生成的页面,来测试是否能够成功达到我们的期望

如何布置我们的CSRF攻击

CSRF漏洞利用的布置方式和反射型XSS是很相近的,通常情况下,攻击者会在他们控制的web页面中插入恶意的HTML代码,然后再诱导用户去访问该页面。这个操作可以通过向受害者发送带有该页面链接的邮件或者通过其他消息软件向受害者发送链接。或者攻击者将链接插入到了一个网站中,比如说评论,那么只要有人访问了该评论中的链接,CSRF攻击也就被触发了。

不仅如此,一些利用GET进行CSRF攻击的可以直接将攻击隐藏到一个URL中,在这种情况下,攻击者将不需要使用任何的外部网站,而只需要该用户发送一个链接即可,比如下面这个POC

<img src="https://vulnerable-website.com/email/[email protected]">

预防CSRF攻击

防御CSRF攻击最好的方法就是在这些包含敏感操作的请求中使用CSRF token,这个token应该满足以下三个条件

  • 不可预测
  • 和用户会话是绑定的
  • 在这些敏感操作被执行时都会进行token的验证

CSRF token

什么是CSRF token

CSRF token是一个独一无二的,秘密的,不可预测的一个值,这个值是由服务端生成的,然后这个token会被传送给客户端,当客户端向服务端发起请求时,服务端会验证客户端是否包含有效的csrf token,如果没有的话,则直接丢弃请求包

CSRF token防御CSRF攻击的原理其实是很简单的,因为这样一来,攻击者就无法自行构造出一个包含有效csrf token的HTTP请求,因为他们无法得知CSRF token的值

CSRF token应该如何生成

csrf token的生成方式应该满足绝对的不可预测这一特点,这一点和session token是一致的。

你应该使用密码学强度的伪随机数生成器(PRNG),使用时间戳作为种子。

如果你认为PRNG的机密强度还不够,那么你可以将PRNG生成的随机数再和你自己随即出来的一些指定用户的熵串联起来,然后将整个字符串使用强哈希算法哈希一下,将这个hash出来的字符串作为最终的csrf token,这样一来会给攻击者制造出更多的障碍,这样可以避免攻击者通过访问该网站获取CSRF样本,进而分析出CSRF token的生成方式(感觉这个根本不可能,怎么可能分析的出来,仅时间戳这一点就很难一直,更别说还有PRNG算法了)。

CSRF token是如何传输的

CSRF token是高度机密的,在他们的生命周期中,因该被当作很敏感的数据对待。通常使用的方式是使用HTML表单的一个隐藏域将CSRF token发送给服务端,如下:

<input type="hidden" name="csrf-token" value="CIwNZNlR4XbisJF39I8yWnWX9wX4WFoz" />

如何验证CSRF token

当CSRF token生成之后就应该和cookie一起存放在server端,当一个请求发送过来时,服务端应该验证该请求中的token是否与自己保存的一致,如果请求包中直接都没有token,那么服务端应该直接丢弃该请求。

一定不能将CSRF token存放在cookie字段中,因为浏览器会自动将cookie提交到服务端。

由于CSRF token是由服务端生成的,所以在我们在访问web应用时,服务端发回的html页面中的表单就已经设置了一个隐藏域并将其值设置为了刚生成的csrf token,这样一来,就算你访问了攻击者精心构造的web页面,由于csrf token不符合,攻击者依然无法实施CSRF攻击

SameSite Cookie

SameSite属性主要用于在发起跨站请求时控制如何提交cookie,通过设置这个属性,应用程序可以阻止浏览器自动提交cookie,因为浏览器不对cookie进行区分,即使不是这个网站的cookie也会提交上去。

SameSite属性是在服务端响应报文中的SetCookie字段中进行设置的,有两个候选值,一个是Lax,另一个是Strict

SetCookie: SessionId=sYMnfCUrAlmqVVZn9dqevxyFpKZt30NN; SameSite=Strict;
SetCookie: SessionId=sYMnfCUrAlmqVVZn9dqevxyFpKZt30NN; SameSite=Lax;

如果SameSite属性被设置为了Strict,那么浏览器将不会提交不同源的Cookie,也就是说在B网站中向A网站发起请求时,浏览器是不会自动将A网站的Cookie附加到发给B网站的报文中的,这样做虽然提高了安全性,但是却使得用户的使用体验变差了,如果用户在其他的网站中被导向了自己之前已经登陆过的网站,那么该网站就会显示该用户是处于未登陆状态的,需要用户重新登陆。

如果SameSite属性被设置成了Lax,那么浏览器会将不同源的Cookie附加到请求报文中,但是需要满足下面两个条件:

  • 请求使用的必须是GET方法,如果请求方式是POST,则不会将Cookie附加到请求中
  • 由用户点击发起的请求会被附加上Cookie,如果是由脚本提交的,则不会附加上Cookie(个人感觉这是很关键的一点)

所以即便我们将SameSite设置为了Lax,依然可以提供一定程度的CSRF防御,因为大部分的CSRF攻击都要通过POST请求来完成。但是依然有两个地方值得我们提高警惕:

  • 一些网站的确会通过GET方法来执行一些敏感操作(实际上这样做是不规范的)
  • 很多web应用和框架对HTTP请求方法的容忍度都是比较高的,也就是说他们当初设计的是通过POST请求方法来完成特定的操作,但是实际情况是,使用GET请求方法也能完成该操作

不建议仅仅依靠SameSite属性来防御CSRF,更推荐的方式是结合SameSite和CSRF token两种方式来防御CSRF攻击

常见的CSRF漏洞

大部分的CSRF漏洞是由于CSRF token验证部分的失误引起的

下面是我们之前讨论的HTTP请求报文加上CSRF token之后的样子

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm

csrf=WfF1szMUHhiokx9AHFply5L2xAOfjRkE&[email protected]

CSRF token在POST方法中进行验证,但是在GET方法中却不进行验证

GET /email/[email protected] HTTP/1.1
Host: vulnerable-website.com
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm

如果没有CSRF token就不验证,直接放行,正常的处理应该是直接丢弃HTTP请求

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 25
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm

[email protected]

CSRF token没有和用户的session绑定

有些web应用直接弄了个CSRF token池,如果用户提交的请求中的csrf token能和token池中的任意一个token对应上,就能认证通过,这样一来,攻击者可以直接使用自己的账户登陆上去,然后获取到一个CSRF token,然后再利用该CSRF token去构造CSRF请求,也能达到CSRF攻击的目的。

CSRF token没有绑定到指定用户的session上

这种情况通常发生在web应用使用了两个框架时,一个用来追踪用户,一个用来防御CSRF

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF; csrfKey=rZHCnSzEp8dbI6atzagGoSYyqJqTz5dv

csrf=RhV7yQDO0xcq9gLEah2WVbmuFqyOq7tY&[email protected]

这个网站设计的是把csrfKey写到了cookie字段中

也就是说,这个CSRF token和cookie并没有正确的绑定在一起,如果这个应用包含任何能够让攻击者在受害者机器上设置cookie的功能的话,那么攻击者就可以事先先使用自己的账户登陆到该网站,获取一个cookie以及与其对应的csrf token,然后使用这个能够设置cookie的功能将自己的cookie设置到受害者的电脑上,然后再使用自己的csrf去构造CSRF攻击请求。

基于Referer字段的CSRF防御

通过检查请求是否由该网站发出来防御CSRF攻击,因为CSRF攻击一般都是从攻击者的网站发出的请求,这种方式的效率并不太高,而且很容易被bypass

Referer字段是HTTP报文中的一个可选字段,Referer是不小心拼错的,其实应该是referrer,双写r,但是RFC就是这么写的,也就一直这么错过来了,该字段的值表示请求来源于哪一个网站,这个是由浏览器自动加上的,有各种各样的方法可以更改这个字段的值

验证referer字段取决于发送给服务端的HTTP请求

如果提交给服务端的http请求中并没有Referer字段,那么验证就会直接忽略,攻击者可以通过构造HTML来告知浏览器不要发送referer字段,最简单的方式就是使用META标签

<meta name="referrer" content="never">

Referer字段的验证可以被bypass

有些应用验证referer字段的方式着实是有些天真,他们只看Referer字段的值中是否包含自家的域名,那么攻击者可以很简单地通过这种下面这种方式绕

http://attacker-website.com/csrf-attack?vulnerable-website.com

这样发送过去的请求中的Referer字段就是http://attacker-website.com/csrf-attack?vulnerable-website.com,如果按照上面的验证方式,则可以简单通过验证

还有一种验证是验证referer字段的值的起始字符串是否为自己的域名,其实这种也是有办法绕过的,攻击者将有漏洞的网站的域名注册为自己的子域名即可

http://vulnerable-website.com.attacker-website.com/csrf-attack

其实有了上面META标签,我们根本没必要使用绕过这种方式来进行CSRF攻击,直接不让浏览器发送Referer字段就行了

发布了290 篇原创文章 · 获赞 80 · 访问量 12万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章