4.3 通过POST请求发起CSRF攻击
如果银行针对POST请求执行转账操作,你将需要使用一种不同的方法来生成CSRF攻击。攻击者不能使用<img>标签,因为<img>标签不能触发POST请求。相应地,攻击者的策略将取决于POST请求的内容。
最简单的情形是POST请求采用具有application/x-www-form-urlencoded或text/plain取值的内容类型(content-type)。内容类型是浏览器在发送HTTP请求时包含的头部信息。该头部信息告知接收者HTTP请求体是如何编码的。下面是一个具有text/plain内容类型的HTTP请求的例子:
内容类型❶被标记出来,并且它的类型与请求中的其他字符编码一起列出。内容类型非常重要,因为浏览器依据不同的类型采用不同的处理方式(后面马上就会介绍到)。
在这种情况下,恶意网站生成一个隐藏的HTML表单并悄悄地发向有漏洞的网站,同时不被目标对象感知到,这是有可能的。表单可以提交一个POST或者GET请求到一个URL,甚至可以提交一些参数值。下面是一段恶意代码示例,网站上的链接会引导Bob访问这段代码:
这里,我们用一个表单发起一个到Bob网银的HTTP POST请求❷(由<form>标记中的action属性指定)。因为攻击者不想让Bob看到这个表单,所以每个<input>元素❸的type都设置为hidden,这将使得鲍勃在页面上看不到这些元素。作为最后一步,攻击者在<script>标签中编写了一些JavaScript脚本,从而使得当页面被加载时能够自动提交表单❹。JavaScript脚本通过调用HTML文档的getElementByID()方法并以我们在第二行❷设置的表单("csrf-form")的ID作为参数来实现这一功能。就像我们之前讨论的GET请求一样,一旦表单提交,浏览器就会发起一个HTTP POST请求并发送Bob的cookie到银行网站,而这将导致银行转账的发生。因为POST请求会触发网站返回一个HTTP响应消息给浏览器,所以攻击者通过在iFrame中使用display:none属性来隐藏响应消息❶。最后,Bob没有看到响应消息,也不知道发生了什么。
在某些情况下,网站可能要求POST请求以内容类型application/json进行提交。有时,application/json类型的请求会有一个CSRF令牌(CSRF token)。该令牌是一个随着HTTP请求一起提交,合法网站用它来验证请求发起于最初始发起方而不是其他恶意网站。有时POST请求的HTTP体中包含令牌,有时POST请求头中包含类似X-CSRF-TOKEN这样的自定义头信息。当浏览器发送一个application/json POST请求给网站时,它一般要先发送一个OPTIONS HTTP请求。网站针对OPTIONS请求会返回一个响应消息,以说明它支持哪些类型的HTTP请求以及接受哪些信任源。这通常叫作预检OPTIONS调用。浏览器读取响应消息并生成相应的HTTP请求,在我们的网银例子中就是用来转账的POST请求。
如果正确实现,预检OPTIONS调用能够抵御某些CSRF漏洞:恶意网站没有被服务器列入可信网站列表,并且浏览器仅允许特定的网站(被称为白名单网站列表)读取HTTP OPTIONS响应消息。因此,由于恶意网站不能读取OPTIONS响应消息,浏览器也就不会发送恶意POST请求。
定义网站之间什么时候和如何互相读取响应消息的规则集合叫作跨源资源共享(CORS)。CORS限制了为该域提供文件服务以及被测试网站允许的域之外的外部域的资源访问,也包括对JSON响应资源的访问。换句话说,当开发者使用CORS保护网站时,只有当被测试的网站允许时,你才能提交application/json请求以调用被测试的应用、读取相应信息或者发起其他调用。有些情况下,你可以通过修改content-type头信息为application/x-www-form-urlencoded、multipart/form-data或text/plain来绕开这些保护措施。当发起POST请求时,针对该三种类型的content-type,浏览器并不会发起预检OPTIONS调用,因此,CSRF请求攻击可能会发生效用。如果没有发生作用,看一下服务器的HTTP响应头Access-Control-Allow-Origin,确认一下服务器端——它大概率设置了并不信任任意源。当请求从任意源发起,而响应消息头都跟着变化时,网站可能存在更大的问题,因为它允许任何源读取服务器的响应消息。这样将允许黑客在发起CSRF漏洞攻击之外,还有可能读取服务器HTTP响应消息中的任何敏感数据。