zhaoyu@home:~$

spring-security-跨站请求伪造

csrf是什么

我们通过一个银行转账的例子来模拟csrf。如有一个a.com网站在登录后成功后,通过cookie保存了登录状态,并且没有进行较强的安全措施, 并有一个转账请求页面:

<form method="post"
    action="/transfer">
<input type="text"
    name="amount"/>
<input type="text"
    name="routingNumber"/>
<input type="text"
    name="account"/>
<input type="submit"
    value="Transfer"/>
</form>

那么在没有退出登录前,点击了b.com网站,b.com网站偷偷伪造了这样一个请求,

<form method="post"
    action="https://a.com/transfer">
<input type="hidden"
    name="amount"
    value="100.00"/>
<input type="hidden"
    name="routingNumber"
    value="evilsRoutingNumber"/>
<input type="hidden"
    name="account"
    value="evilsAccountNumber"/>
<input type="submit"
    value="Win Money!"/>
</form>

那么在点击Win Money提交后,b.com网站利用a.com的登录状态伪造了一个a.com的请求,如果成功,可能导致用户的钱转到了诈骗者的账户中。

这就是一次简单地csrf攻击,所以普通的cookie是无法完全阻止csrf攻击的。甚至用户都不用点击提交按钮,恶意网站使用脚本也可以自动 完成请求。

如何预防csrf

csrf的根本原因是网站没有判断哪些是用户正常发起的请求,哪些是恶意网站的请求。所以我们能够区分上述两种请求,就可以防止攻击。 方案如下:

  • 同步器token
  • 在session的cookie中指定相同站点属性。

安全的请求方法必须幂等

上面的两种方案都要求,安全的请求方法幂等。即HTTP的GET,HEAD,OPTIONS,TRACE不能改变应用的状态。

同步器Token

同步器token模式是解决csrf最全面且十分有优势的方案。该方案让每次HTTP请求增加一个随机生成的token。当请求发送到服务器时,服务器 必须验证这个token,如果不匹配,那么拒绝请求。

该方案的关键是将token作为http请求的一部分,如作为一个参数或者放在header中,而不是放在浏览器的cookie中。因为浏览器会自动将 cookie包含在HTTP请求中。

但是,通常我们只需要在更新应用状态的请求中添加cookie,这需要保证安全的http方法必须是幂等的。这样一方面可以让外部网站链接到 我们的网站,从而提高可用性;另一方面,我们不需要在GET方法中包含随机token,从而导致token泄露。

如下:我们使用了一个_csrf的参数作为csrf token。由于浏览器同源策略的保证,外部网站是不可能读取到这个token的。

<form method="post"
    action="/transfer">
<input type="hidden"
    name="_csrf"
    value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
    name="amount"/>
<input type="text"
    name="routingNumber"/>
<input type="hidden"
    name="account"/>
<input type="submit"
    value="Transfer"/>
</form>

相同站点属性

另一种临时解决CSRF攻击的方法是在cookie中设置同站属性。服务器指定一个同站属性,让浏览器在外部网站使用的时候,不发送cookie 给服务器。在响应的header中包含SameSite,如下:

Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax

SameSite的值有如下两种: Strict-严格模式,其他外部网站都不会包含cookie。 Lax-松散模式,只有相同网站或者来自顶层导航的幂等方法可以发送cookie。

session过期

session过期有很多解决方法,每种方法都有折衷方案。

  • 减少超时的最佳方法是在表单请求时,使用javascript请求csrf token,表单接着更新token并提交。
  • 使用一些js,让用户知道回话即将过去,用户可以点击按钮,刷新session。

文件上传

MultiPart用于文件上传,会将请求分为多个part,每个part都包含header部分,其中包含了一个Content-Disposition的header选项。 表示了上传的内容,如果是文件,则保护一个filename的属性。
每个part部分的Content-Type是可选的,如果没有,默认为text/plain。如果没有识别,会被设置为application/octet-stream。 如果多个文件被填充成单个表单项,那么它们的请求格式则会是 multipart/mixed

所以MultiPart有多个header部分,所以token不能放在某个header。一种是将token放在body中,另一种是放在请求参数中。

放在body中,需要先读取body,然后再执行验证,这回允许任何人在你的服务器上上传临时文件,然而只有验证通过的用户才会 将临时文件提交。推荐使用这种方式,因为在大多数服务器中,临时文件的上传影响很小。

放在请求参数中,验证会先执行,避免了临时文件的上传,但是缺点是放在表单中的请求参数可能会泄露,通常,敏感信息需要放在body或者 header中防止泄露。