CSRF与CORS:正确保护浏览器请求
你构建了一个带有简洁API的Web应用,但随后在日志中发现了奇怪的请求——或者更糟,安全扫描器标记你的网站存在CSRF和CORS配置错误。这两个缩写词经常被混为一谈,但它们解决的是不同的问题。误解它们可能会让你的用户面临风险,或破坏合法的跨域请求。
在本文中,我们将阐明CSRF和CORS究竟是什么,它们如何与浏览器安全交互,并为你提供具体步骤,在不破坏功能的情况下保护你的应用。
什么是CSRF,为什么你应该关心?
跨站请求伪造(CSRF)是一种攻击,它诱骗用户的浏览器向用户已认证的网站发送请求。想象一下,你登录了银行网站 bank.com。然后你访问了一个恶意网站,其中包含一个图片标签,如 <img src="https://bank.com/transfer?to=attacker&amount=1000">。你的浏览器会自动在请求中包含你的银行Cookie,如果银行没有CSRF防护,转账就会成功。
关键点:CSRF利用了网站对用户浏览器的信任。攻击者不需要窃取你的会话;他们只需要你的浏览器代表你发出请求。
CSRF攻击如何工作
要使CSRF攻击成功,必须满足三个条件:
- 受害者必须已认证(例如,拥有有效的会话Cookie)。
- 攻击者必须知道请求的结构(端点、参数)。
- 请求不能包含攻击者无法猜测的任何不可预测参数。
常见的目标是改变状态的操作:更改电子邮件、转账、发布内容或修改权限。
什么是CORS,为什么它存在?
跨域资源共享(CORS)是一种浏览器机制,允许或拒绝网页向与提供页面的域不同的域发出请求。它是同源策略(SOP)的扩展,SOP限制了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
如果没有CORS,恶意网站可以在你登录时使用JavaScript读取银行API的数据。CORS为服务器提供了一种明确允许某些跨域请求的方式。
同源策略(SOP)
如果协议、主机和端口完全相同,则两个URL具有相同的源。例如,https://example.com/app 和 https://example.com/api 共享相同的源,但 http://example.com(协议不同)和 https://api.example.com(主机不同)则不是。
SOP阻止来自一个源的脚本读取来自另一个源的响应。CORS通过添加HTTP头部来放宽此限制,这些头部告诉浏览器是否允许请求。
CSRF与CORS:关键区别
理解CSRF和CORS不是对立的至关重要;它们解决不同的安全问题。CSRF是关于防止未经授权的状态改变请求,而CORS是关于控制哪些源可以读取响应。
| 方面 | CSRF | CORS |
|---|---|---|
| 主要目标 | 防止伪造请求 | 控制跨域读取 |
| 攻击向量 | 恶意网站触发请求 | 恶意网站读取响应 |
| 防御机制 | 令牌、SameSite Cookie | HTTP头部(Access-Control-*) |
| 浏览器强制执行 | 无(服务器必须验证) | 是(浏览器阻止读取) |
如何防御CSRF
有几种经过验证的策略可以缓解CSRF。你不需要全部使用,但分层防御是明智的。
1. 使用反CSRF令牌
最稳健的防御是在每个状态改变请求中包含一个唯一、不可预测的令牌。服务器在处理之前验证令牌。令牌应绑定到用户的会话,并且不应在URL中暴露(以避免通过引用头泄露)。
// 示例:在Node.js/Express中生成和验证CSRF令牌
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/process', csrfProtection, (req, res) => {
// 令牌自动验证
res.send('OK');
});
2. 设置SameSite Cookie
现代浏览器支持Cookie的 SameSite 属性。将其设置为 Lax 或 Strict 可防止浏览器在跨站请求中发送Cookie,从而阻止许多CSRF攻击。Lax 允许在顶级导航(例如点击链接)时发送Cookie,而 Strict 阻止所有跨站请求。
Set-Cookie: sessionId=abc123; SameSite=Lax; Secure; HttpOnly
3. 验证Origin和Referer头部
检查传入请求的 Origin 或 Referer 头部。如果它们与你的预期域不匹配,则拒绝请求。这是一个简单但有效的额外层。
4. 为AJAX使用自定义头部
如果你的API通过JavaScript使用,要求一个自定义头部,如 X-Requested-With。浏览器对自定义头部强制执行CORS预检,因此来自恶意网站的简单表单POST不会包含它。
正确配置CORS
CORS配置错误也可能导致安全问题。最常见的错误是在设置 Access-Control-Allow-Origin: * 的同时允许凭据。浏览器禁止这种组合,但开发人员有时会尝试以不安全的方式绕过它。
CORS头部的最佳实践
- 涉及凭据时,指定确切的源而不是通配符。
- 将允许的方法限制为你的API仅支持的方法(例如
GET, POST)。 - 将允许的头部限制为你的应用实际使用的头部。
- 设置
Access-Control-Max-Age以缓存预检响应并减少开销。 - 避免盲目反射
Origin头部;根据白名单验证它。
# 示例:Nginx的CORS配置
location /api/ {
if ($http_origin ~* (https://(app|admin)\.example\.com)) {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
}
if ($request_method = 'OPTIONS') {
return 204;
}
}
请记住,CORS由浏览器强制执行,而不是服务器。它不是认证或授权的替代品;它是一种安全地放宽同源策略的方式。
综合应用
保护浏览器请求需要分层方法。以下是一个快速检查清单:
- 对所有状态改变操作使用反CSRF令牌。
- 将SameSite Cookie设置为
Lax或Strict。 - 在服务器上验证Origin/Referer头部。
- 精确配置CORS:白名单源、限制方法,并避免与凭据一起使用通配符。
- 定期测试你的应用,使用安全扫描器和手动检查。
通过理解CSRF和CORS的不同作用,你可以避免常见陷阱并构建更安全的Web应用。
常见问题
CORS能防止CSRF攻击吗?
不能,CORS不能防止CSRF。CSRF攻击不依赖于读取响应;它们只是触发请求。CORS控制哪些源可以读取响应,但它不阻止请求被发送。要防止CSRF,你需要令牌、SameSite Cookie或源验证。
将Access-Control-Allow-Origin设置为'*'安全吗?
仅当你的API不使用凭据(Cookie、HTTP认证)时,将其设置为'*'才是安全的。如果涉及凭据,浏览器将拒绝响应。对于已认证的API,始终指定确切的源。
如果我在Authorization头部使用JWT,还需要CSRF保护吗?
如果你将JWT存储在Cookie中,你仍然需要CSRF保护,因为Cookie会自动发送。如果你将JWT存储在内存中并通过Authorization头部发送,则CSRF不是问题,因为攻击者无法跨域设置自定义头部。但是,你必须防范XSS以确保令牌安全。
准备好测试你的API的CORS头部了吗?使用我们的 Nginx日志分析器 来检查请求模式并发现可疑的跨域尝试。