AJAX请求的CSRF防护
什么是跨站请求伪造(CSRF)
首先快速回顾一下什么是跨站请求伪造:
- 用户登录银行网站:https://example.com
- 银行网站有"转账"功能:https://example.com/manage_money/transfer.do
- “转账"功能接受以下POST参数:toAccount和amount
- 用户登录状态下收到伪装成朋友的邮件
- 用户点击邮件中的链接访问猫视频:https://attacker-site.co.uk/cats.htm
- cats.htm在显示猫视频的同时,会通过客户端AJAX请求向https://example.com/manage_money/transfer.do发送POST请求,参数为toAccount=1234和amount=100,从受害者账户向攻击者账户转账100英镑
同源策略的误解
存在一个常见的误解:由于同源策略,网站无法向其他域发起跨域请求。这是因为浏览器控制台会显示:
然而事实并非如此。请求已经发出,浏览器消息只是告诉你当前源(https://attacker-site.co.uk)无法读取跨域请求返回的任何数据。但与XSS攻击不同,CSRF不需要读取返回数据。请求已经发出,由于用户已登录银行并拥有会话cookie,该会话cookie已被传递给银行网站授权交易。
AJAX防护机制
如果目标应用没有CSRF防护措施,上述攻击对AJAX请求和传统表单POST都有效。可以使用传统推荐的同步器令牌模式来防护。这涉及创建一个随机、不可预测的令牌(除了cookie中的会话令牌外),并将其作为会话变量存储在服务器端。
但还有另一种方法:使用自定义头部,如X-Requested-With。
基本流程:
- 在每个改变应用服务器端状态的AJAX请求中设置自定义头部,如X-Requested-With: XmlHttpRequest
- 在每个服务器端方法处理程序中,确保调用CSRF检查函数
- CSRF函数检查HTTP请求中是否存在X-Requested-With: XmlHttpRequest头部
- 如果存在则允许,否则返回HTTP 403响应并在服务器端记录
许多JavaScript框架(如JQuery)会自动在任何AJAX请求中发送此头部。此头部无法跨域发送:
- 现代浏览器尝试这样做会触发CORS预检请求
- 旧版浏览器(如IE 8和9)可以发送跨域请求,但完全不支持自定义头部
- 非常旧的浏览器根本无法发送跨域AJAX请求
什么是预检请求
CORS(跨域资源共享)是一种削弱安全性的机制,旨在允许相互信任的站点打破同源策略并读取彼此的响应。
简而言之,CORS不会阻止任何以前可能发生的事情。例如,使用<form method="post">的跨域POST一直是被允许的,因此CORS允许任何AJAX请求产生以前可能的HTTP请求,而无需预检请求。
但是,带有自定义头部的请求会导致浏览器使用OPTIONS动词自动向端点发送请求。如果服务器端应用识别OPTIONS请求(即支持CORS),它将回复一个头部,显示允许调用域发送哪些头部。
渗透测试注意事项
上述方法只有在服务器端应用验证收到自定义头部X-Requested-With时才有效。作为渗透测试人员,应验证所有可能发现的CSRF漏洞是否实际可利用。
开发者建议
如果您选择的服务器端语言不支持服务器端变量,或者您不想为每个用户会话存储额外令牌的开销,这可能是一个好的快捷方式。但是,请确保为每个改变应用状态的处理器验证HTTP请求头部的存在。
请记住,这只适用于AJAX请求。如果您的应用在禁用JavaScript时必须回退到完整的HTML请求,则此方法不适用。自定义头部无法通过<form>标签发送。
结论
这是一种有用且易于实现的CSRF防护措施。虽然攻击者可以轻松添加自定义头部(例如使用Burp Suite),但他们只能对自己的请求这样做,而不能像客户端攻击所需的那样对受害者的请求这样做。
如果应用程序的风险承受能力较低,请使用令牌防护而不是或同时使用:深度防御。