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中防止泄露。