JSONP劫持漏洞的核心问题在于:JSONP接口没有对请求来源进行严格校验,攻击者可以诱导用户访问恶意页面,通过script标签加载目标网站的JSONP接口,窃取用户敏感数据。而Referer校验就是最直接、最有效的防御手段之一——通过检查HTTP请求头中的Referer字段,确认请求是否来自合法的源站域名,从而阻断跨域恶意调用。简单来说,只要你的JSONP接口返回了用户的隐私数据,就必须加上Referer白名单校验,否则等于把数据大门敞开给黑客。
JSONP(JSON with Padding)本质上是一种跨域数据获取方案,它利用script标签不受同源策略限制的特性,通过动态创建script元素来请求其他域名的数据。正常场景下,前端页面需要获取不同域名下的数据时会用到JSONP。但问题在于,script标签的src属性可以被任意页面引用,攻击者只需要在自己的恶意网站上写一段代码,就能让访问者的浏览器自动向目标JSONP接口发起请求,并通过回调函数把返回的数据发送到攻击者的服务器。这就是JSONP劫持,也叫JSONP hijacking。
JSONP劫持的攻击原理到底是什么要理解Referer校验为什么能防住这个漏洞,必须先搞清楚攻击是怎么发生的。假设你的网站有一个JSONP接口:https://api.yoursite.com/userinfo?callback=getData,这个接口会返回当前登录用户的姓名、手机号、邮箱等信息。正常情况下,你自己的前端页面调用它没问题。但攻击者建了一个恶意网站evil.com,在里面放了这么一段代码:
<script src="https://api.yoursite.com/userinfo?callback=stealData"></script>
当用户在登录了yoursite.com之后,又不小心访问了evil.com,浏览器会自动带上yoursite.com的Cookie去请求那个JSONP接口。接口返回的数据会被当作JavaScript执行,调用攻击者定义的stealData函数,数据就这样被偷走了。整个过程用户毫无感知,因为script标签加载是静默的,不会有任何弹窗或提示。
Referer校验的基本逻辑和实现方式Referer是HTTP请求头中的一个字段,记录了当前请求是从哪个页面跳转过来的。利用这个特性,服务端可以判断请求是否来自自己的域名。如果Referer不在白名单内,直接拒绝返回数据。具体实现上,后端需要在处理JSONP请求时,读取请求头中的Referer值,与预设的合法域名列表进行比对。
// Node.js Express 示例
app.get('/userinfo', function(req, res) {
var referer = req.headers.referer || req.headers.referrer;
var allowedDomains = ['https://www.yoursite.com', 'https://yoursite.com'];
if (!referer || !allowedDomains.some(function(domain) {
return referer.indexOf(domain) === 0;
})) {
res.status(403).send('Forbidden: invalid referer');
return;
}
var callback = req.query.callback;
var userData = getUserData(req);
res.send(callback + '(' + JSON.stringify(userData) + ')');
});
上面这段代码的逻辑很清晰:先拿到Referer,检查它是否以合法域名开头,如果不是就返回403禁止访问。只有通过校验的请求,才会正常返回JSONP数据。这种方式简单粗暴,但非常有效。
Referer校验的局限性和注意事项虽然Referer校验是防御JSONP劫持的重要手段,但它并不是万能的。首先,Referer头可以被用户或者浏览器设置为不发送,某些隐私模式下Referer会被清空,这会导致正常用户也无法访问接口。其次,Referer可以在某些情况下被伪造,虽然在浏览器端script标签发起的请求中伪造难度较大,但不能完全排除。第三,部分老旧的代理服务器或防火墙会篡改Referer头,造成误判。
所以在实际生产环境中,Referer校验通常不会单独使用,而是作为多层防御体系中的一环。建议配合以下措施一起使用:
第一,使用CSRF Token机制。在每次请求时生成一个随机Token,前端在调用JSONP时必须携带这个Token,服务端验证Token的合法性。这样即使攻击者能构造请求,也无法拿到正确的Token。
// 后端验证Token示例
app.get('/userinfo', function(req, res) {
var token = req.query.token;
if (!validateToken(token)) {
res.status(403).send('Forbidden: invalid token');
return;
}
// 再进行Referer校验
var referer = req.headers.referer;
// ...
});
第二,检查请求中是否包含Cookie。JSONP劫持之所以能成功,关键在于浏览器会自动携带目标域名的Cookie。如果后端要求请求必须携带特定的自定义请求头(比如X-Requested-With),而script标签无法添加自定义头,那么攻击就会失败。但这种方式需要前端使用XMLHttpRequest而非纯script标签,实际上就已经脱离了JSONP的范畴,变成了CORS方案。
第三,对返回的数据进行脱敏处理。即使被劫持,返回的数据如果不包含核心敏感信息,损失也能降到最低。比如手机号只返回前三位和后四位,邮箱只返回域名部分。
Referer校验的最佳实践和配置细节在配置Referer白名单时,一定要注意精确匹配。不能只写域名,要写完整的协议加域名。比如不要只写yoursite.com,要写https://www.yoursite.com和https://yoursite.com两个。因为Referer是完整的URL,只匹配域名部分可能会被绕过,比如攻击者用https://yoursite.com.evil.com这种子域名来欺骗。
// 更严格的白名单匹配
var allowedDomains = [
'https://www.yoursite.com/',
'https://yoursite.com/',
'https://m.yoursite.com/'
];
function isValidReferer(referer) {
return allowedDomains.some(function(domain) {
return referer === domain || referer.indexOf(domain) === 0;
});
}
另外,要处理好Referer为空的情况。正常用户直接在浏览器地址栏输入JSONP接口地址时,Referer是空的。这种情况应该直接拒绝,因为正常的JSONP调用一定是从页面上发起的,必然带有Referer。如果你的业务确实需要允许空Referer的访问,那就要用其他方式补充验证,比如IP白名单或者Token机制。
还有一个容易被忽略的点:Referer校验要放在业务逻辑之前执行,并且要在返回数据之前完成。有些开发者把校验放在数据处理之后,这样即使校验不通过,数据也已经被读取和处理了,存在信息泄露的风险。正确的做法是先校验,校验不通过直接返回错误,不执行任何后续逻辑。
JSONP接口的整体安全加固方案单独依赖Referer校验来防护JSONP劫持是不够的,真正安全的做法是从架构层面减少JSONP的使用。JSONP本身就是一个过时的跨域方案,现在主流的做法是使用CORS(跨域资源共享)配合proper的认证机制。CORS可以精确控制哪些域名、哪些方法、哪些请求头可以跨域访问,比JSONP安全得多。
如果因为历史原因必须继续使用JSONP,那么完整的安全加固方案应该包括:Referer白名单校验 + CSRF Token验证 + 敏感数据脱敏 + 限制接口返回的数据范围 + 设置合理的Cache-Control头防止数据被缓存。多层防御叠加在一起,才能把风险降到可接受的范围。
从运维角度来说,还要定期对JSONP接口进行安全审计。检查是否有不必要的JSONP接口暴露在外,是否有接口返回了过多的敏感字段,白名单配置是否有遗漏或过于宽松的情况。很多安全事件不是因为技术不行,而是因为配置疏忽。一个写错的域名、一个忘记更新的白名单,都可能成为攻击的突破口。
总结:Referer校验是必要但非充分的防线JSONP劫持漏洞在今天依然存在,尤其是一些老旧系统和第三方接口中仍然大量使用JSONP。Referer校验作为最基础的防御手段,实施成本低、效果明显,是每个使用JSONP的项目都必须加上的安全措施。但它有局限性,不能作为唯一的防护。真正负责任的做法是把Referer校验纳入整体安全策略中,配合Token验证、数据脱敏、接口审计等手段,构建纵深防御体系。同时,长远来看,逐步用CORS或后端代理的方式替代JSONP,才是从根本上消除这类风险的正确方向。
