各位观众老爷,大家好!今天咱们来聊聊JS的CORS,也就是跨域资源共享,以及它那让人头疼的配置错误和可能存在的Access-Control-Allow-Origin漏洞。这玩意儿,说起来简单,但一不小心就容易掉坑里,今天咱就一块儿把这些坑填平了。
一、啥是CORS?为啥要有它?
想象一下,你有个网站www.example.com
,想去www.api.com
要点数据。浏览器一看,哎哟,这俩域名不一样啊,这是跨域请求,默认情况下,为了安全起见,浏览器会阻止这种请求。 这就是浏览器的同源策略在作祟。
那为啥要有同源策略呢? 简单来说,就是为了防止恶意网站偷偷摸摸地访问你的数据。 假设没有同源策略,恶意网站可以悄悄地在你的浏览器里发起请求,冒充你访问银行网站,然后盗取你的银行信息,想想都可怕。
但是,实际开发中,跨域请求又是不可避免的。 比如,前后端分离的项目,前端域名和后端API域名通常是不一样的。 这时候,就需要CORS来帮忙了。
CORS本质上是一种机制,允许服务器告诉浏览器,哪些来源(域名、协议、端口)可以访问我的资源。 就像是服务器给浏览器开了一张“通行证”,允许特定的域名来拿数据。
二、Access-Control-Allow-Origin:CORS的核心
CORS的核心就在于服务器返回的Access-Control-Allow-Origin
响应头。 这个头告诉浏览器,哪些域名是被允许跨域访问的。
它有三种常见的取值方式:
- *`` (星号):** 这表示允许所有域名进行跨域访问。 简单粗暴,但也很危险,除非你的API是公开的,否则不建议使用。
- 具体的域名: 例如,
Access-Control-Allow-Origin: www.example.com
,表示只允许www.example.com
这个域名进行跨域访问。 null
: 这个值有点特殊,通常用于file://
协议或者data:
协议下的请求。 在这些情况下,浏览器的"源"被认为是null。
三、CORS预检请求(Preflight Request)
如果你的跨域请求比较复杂,比如使用了PUT
、DELETE
等非标准的HTTP方法,或者设置了自定义的请求头,浏览器会先发起一个OPTIONS
请求,这就是预检请求。
这个OPTIONS
请求会询问服务器,是否允许这种跨域请求。 服务器需要在OPTIONS
请求的响应中返回以下头:
Access-Control-Allow-Origin
: 允许的域名。Access-Control-Allow-Methods
: 允许的HTTP方法,例如GET, POST, PUT, DELETE
。Access-Control-Allow-Headers
: 允许的自定义请求头,例如Content-Type, Authorization
。Access-Control-Max-Age
: 预检请求的缓存时间,单位是秒。
如果服务器没有正确地响应这些头,浏览器就会阻止实际的跨域请求。
四、CORS配置错误:常见的坑
CORS配置错误是导致跨域问题的罪魁祸首。 下面列举一些常见的错误配置:
-
*`Access-Control-Allow-Origin: ` 滥用:**
虽然简单,但非常危险。 这相当于把你的API完全暴露给所有人,任何网站都可以访问你的数据。
风险: 任何恶意网站都可以窃取用户数据、冒充用户操作等。
-
忘记处理预检请求:
如果你的跨域请求需要预检,但服务器没有正确地响应
OPTIONS
请求,浏览器就会报错。错误示例 (Node.js/Express):
app.options('/api/data', (req, res) => { res.header('Access-Control-Allow-Origin', '*'); // 错误:应该根据请求的Origin动态设置 res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); res.send(); });
正确示例 (Node.js/Express):
app.options('/api/data', (req, res) => { const origin = req.headers.origin; if (allowedOrigins.includes(origin)) { // allowedOrigins是一个允许的域名列表 res.header('Access-Control-Allow-Origin', origin); } else { res.header('Access-Control-Allow-Origin', ''); // 不允许的域名,返回空字符串 } res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); res.send(); });
-
Access-Control-Allow-Credentials
的使用不当:如果你的API需要携带cookie等凭证,你需要设置
Access-Control-Allow-Credentials: true
。 同时,客户端也需要设置withCredentials = true
。错误示例 (服务端):
res.header('Access-Control-Allow-Origin', '*'); // 错误:使用*时不能设置Access-Control-Allow-Credentials: true res.header('Access-Control-Allow-Credentials', 'true');
正确示例 (服务端):
const origin = req.headers.origin; if (allowedOrigins.includes(origin)) { res.header('Access-Control-Allow-Origin', origin); // 必须是具体的域名 res.header('Access-Control-Allow-Credentials', 'true'); }
正确示例 (客户端/JavaScript):
const xhr = new XMLHttpRequest(); xhr.withCredentials = true; // 必须设置 xhr.open('GET', 'https://api.example.com/data'); xhr.send();
注意: 如果服务端设置了
Access-Control-Allow-Credentials: true
,但客户端没有设置withCredentials = true
,浏览器不会发送cookie。 反之,如果客户端设置了withCredentials = true
,但服务端没有设置Access-Control-Allow-Credentials: true
,请求会被浏览器阻止。 -
Origin验证不严格:
有些服务器会根据
Origin
请求头来动态设置Access-Control-Allow-Origin
。 如果验证不严格,可能会被绕过。错误示例 (PHP):
<?php $origin = $_SERVER['HTTP_ORIGIN']; if (strpos($origin, '.example.com') !== false) { header('Access-Control-Allow-Origin: ' . $origin); } ?>
风险: 上面的代码只检查了
Origin
是否包含.example.com
,攻击者可以注册一个evil.example.com.attacker.com
这样的域名来绕过验证。正确示例 (PHP):
<?php $allowedOrigins = array( 'www.example.com', 'api.example.com' ); $origin = $_SERVER['HTTP_ORIGIN']; if (in_array($origin, $allowedOrigins)) { header('Access-Control-Allow-Origin: ' . $origin); } ?>
五、Access-Control-Allow-Origin漏洞利用:如何被黑?
如果CORS配置不当,攻击者可能会利用这些漏洞来窃取用户数据、执行恶意操作。
-
Access-Control-Allow-Origin: null
漏洞:有些情况下,服务器会错误地返回
Access-Control-Allow-Origin: null
。 这通常发生在使用了不正确的Origin
验证逻辑时。攻击场景: 攻击者可以创建一个本地的HTML文件 (使用
file://
协议) 或者一个data:
URL,然后发起跨域请求。 由于这些请求的Origin
是null
,如果服务器错误地允许null
作为合法的Origin
,攻击者就可以绕过CORS限制。代码示例 (攻击者的HTML文件):
<!DOCTYPE html> <html> <head> <title>CORS Exploit</title> </head> <body> <script> const xhr = new XMLHttpRequest(); xhr.open('GET', 'https://vulnerable.example.com/api/sensitive_data'); // 假设vulnerable.example.com允许Origin: null xhr.onload = function() { alert(xhr.responseText); // 窃取数据 }; xhr.onerror = function() { alert('Request failed'); }; xhr.send(); </script> </body> </html>
防御: 绝对不要允许
Origin: null
。 检查你的CORS配置,确保只允许明确的域名。 -
子域名接管(Subdomain Takeover)结合CORS:
如果你的服务器允许某个子域名 (例如
*.example.com
) 跨域访问,而攻击者成功地接管了一个子域名 (例如evil.example.com
),攻击者就可以利用这个子域名来发起跨域请求,窃取数据。攻击步骤:
- 攻击者找到一个可以被接管的子域名 (例如,一个指向不存在的云存储服务的CNAME记录)。
- 攻击者接管这个子域名。
- 攻击者在接管的子域名上部署恶意代码,发起跨域请求到
api.example.com
,窃取数据。
防御:
- 定期检查你的DNS记录,确保没有悬空的CNAME记录,防止子域名被接管。
- 尽可能避免使用通配符域名 (
*
) 作为Access-Control-Allow-Origin
的值。 - 如果必须使用通配符域名,要仔细评估风险,并采取额外的安全措施。
-
Access-Control-Allow-Origin
绕过技巧:有些老旧的浏览器或者不规范的实现,可能存在一些CORS绕过技巧。 例如,利用URL的片段标识符 (
#
) 或者查询参数 (?
) 来绕过Origin验证。 虽然这些漏洞比较少见,但也需要关注。
六、最佳实践:如何正确配置CORS?
- *永远不要使用`Access-Control-Allow-Origin: `,除非你的API是完全公开的。**
- 使用白名单来限制允许跨域访问的域名。
- 严格验证
Origin
请求头,防止被绕过。 - 正确处理预检请求。
- 如果需要携带cookie等凭证,确保同时设置
Access-Control-Allow-Credentials: true
(服务端) 和withCredentials = true
(客户端)。 - 定期审查你的CORS配置,确保没有安全漏洞。
- 使用CORS中间件或者库,可以简化CORS配置,并减少出错的可能性。 (例如,Node.js的
cors
模块) - 在开发和测试环境中,可以适当放宽CORS限制,方便调试。 但在生产环境中,一定要严格配置CORS。
七、一些实用的代码示例
1. Node.js (Express) + cors 模块:
const express = require('express');
const cors = require('cors');
const app = express();
const allowedOrigins = ['http://www.example.com', 'http://localhost:3000'];
const corsOptions = {
origin: function (origin, callback) {
if (allowedOrigins.indexOf(origin) !== -1 || !origin) { // !origin 用于允许本地开发 (file://)
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true // 允许携带cookie
}
app.use(cors(corsOptions));
app.get('/api/data', (req, res) => {
res.json({ message: 'Hello from API!' });
});
app.listen(4000, () => {
console.log('Server listening on port 4000');
});
2. Python (Flask):
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app, resources={r"/api/*": {"origins": ["http://www.example.com", "http://localhost:3000"]}})
@app.route("/api/data")
def hello_world():
return {"message": "Hello from API!"}
if __name__ == '__main__':
app.run(debug=True)
3. Apache (.htaccess):
<IfModule mod_headers.c>
SetEnvIf Origin "http://www.example.com" AccessControlAllowOrigin=$0
Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Access-Control-Allow-Credentials "true"
<FilesMatch ".(ttf|ttc|otf|eot|woff|woff2|font.css|css|js)$">
<If "-f %{REQUEST_FILENAME}">
Header set Access-Control-Allow-Origin "*"
</If>
</FilesMatch>
# Handle preflight OPTIONS requests
<If "%{REQUEST_METHOD} == 'OPTIONS'">
Header set Access-Control-Max-Age "3600"
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Access-Control-Allow-Credentials "true"
Header set Access-Control-Allow-Origin "%{HTTP_ORIGIN}e"
Header set Vary Origin
Header set Vary Access-Control-Request-Method
Header set Vary Access-Control-Request-Headers
<Files *>
Require all granted
</Files>
</If>
</IfModule>
八、总结
CORS是一个重要的安全机制,但配置错误很容易导致安全漏洞。 理解CORS的工作原理,避免常见的错误配置,并定期审查你的CORS设置,才能确保你的API安全可靠。 记住,安全无小事,小心驶得万年船!
好了,今天的讲座就到这里。 各位观众老爷,咱们下回再见!