API安全测试:接口鉴权、参数篡改、重放攻击
API安全测试,说白了就是检查你的App后端接口到底有多抗揍。我做了这么多年移动安全,见过太多App前端做得花里胡哨,结果API一抓包,裸奔得跟没穿衣服似的。今天咱们就聊聊接口鉴权、参数篡改和重放攻击这三个核心点。
1. 接口鉴权:你的API到底认不认识人?
接口鉴权是API安全的第一道门。如果这道门没锁好,攻击者可以直接调用你的后端接口,想干嘛就干嘛。
常见的鉴权方式有这些:
- Token鉴权:用户登录后,服务端返回一个token,后续请求都带上它。我个人习惯用JWT(JSON Web Token),因为它自带签名,不容易被篡改。
- API Key鉴权:每个客户端分配一个固定的key。嗯,这个其实不太安全,因为key一旦泄露,别人就能冒充你。
- OAuth 2.0:适合第三方授权场景,比如用微信登录你的App。
- 签名鉴权:把请求参数按规则排序,加上密钥算出一个签名。服务端用同样的方式验证。
重点来了:很多App只在前端做了登录校验,后端接口却没有任何鉴权。我曾经遇到过一款金融类App,它的用户信息接口居然可以直接用任意userId查询,连token都不需要。这相当于把用户数据库的大门敞开了。
测试方法:
- 抓包获取一个正常请求,去掉token或签名,看服务端是否返回数据。
- 修改token中的用户ID,看能否访问其他用户的数据。
- 尝试用过期token请求接口,看服务端是否做了校验。
// 一个简单的JWT鉴权示例(服务端验证)
const jwt = require('jsonwebtoken');
function verifyToken(req, res, next) {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).json({ error: '未提供token' });
}
try {
const decoded = jwt.verify(token, '你的密钥');
req.userId = decoded.userId;
next();
} catch (err) {
return res.status(403).json({ error: 'token无效或已过期' });
}
}
2. 参数篡改:改个数字就能白嫖?
参数篡改是我在项目中遇到最多的问题。攻击者通过修改请求中的参数值,来达到越权操作或绕过业务逻辑的目的。
常见的篡改场景:
| 参数类型 | 篡改示例 | 风险等级 |
|---|---|---|
| 用户ID | 将userId=1001改为userId=1002 | 高 |
| 金额 | 将price=100改为price=1 | 高 |
| 数量 | 将count=1改为count=-1 | 中 |
| 状态 | 将status=0改为status=1 | 中 |
| 角色 | 将role=user改为role=admin | 高 |
你想想看,如果电商App的结算接口没有对金额做服务端校验,攻击者把100块的商品改成1分钱,那平台就亏大了。我记得有一次测试一个外卖App,它的优惠券接口居然允许用户自己传优惠金额,我直接传了个负数,结果账户余额反而增加了。
避坑指南:我曾经见过一个团队,前端做了各种参数校验,后端却完全信任前端传过来的数据。结果上线第一天就被薅羊毛薅到服务器崩溃。记住:永远不要信任客户端传过来的任何数据,所有校验必须在服务端再做一遍。
测试方法:
- 用抓包工具(比如Burp Suite或Charles)拦截请求,修改参数值再发送。
- 尝试修改数据类型,比如把数字改成字符串,看服务端会不会报错。
- 尝试添加额外的参数,看服务端是否处理了未定义的参数。
3. 重放攻击:同一个请求发两次会怎样?
重放攻击,说白了就是攻击者把抓到的请求包原封不动地再发一次。如果服务端没有做防重放机制,那后果可能很严重。
举个例子:
你转账100块给朋友,抓包工具截获了这个请求。如果服务端没有防重放,攻击者把这个请求重放100次,你的账户就会被扣掉10000块。我遇到过最夸张的一个案例,某支付App的转账接口,攻击者重放了3次,就转走了30万。
防重放的常见方案:
- 时间戳+随机数:每个请求带上当前时间戳和一个随机数,服务端检查时间戳是否在有效期内,以及随机数是否被使用过。
- 一次性Token:每次请求前先获取一个一次性token,用完即废。
- 签名机制:把请求参数加上时间戳一起签名,服务端验证签名有效性和时间戳。
// 防重放签名示例(客户端生成签名)
function generateSignature(params, secretKey) {
const timestamp = Date.now();
const nonce = Math.random().toString(36).substring(2);
// 参数排序后拼接
const sortedKeys = Object.keys(params).sort();
let signStr = '';
for (let key of sortedKeys) {
signStr += `${key}=${params[key]}&`;
}
signStr += `timestamp=${timestamp}&nonce=${nonce}`;
// 计算签名
const signature = CryptoJS.HmacSHA256(signStr, secretKey).toString();
return {
timestamp,
nonce,
signature
};
}
个人经验:我建议在服务端维护一个最近5分钟内的nonce集合,每次请求检查nonce是否已存在。如果存在,直接拒绝。这样可以有效防止重放攻击。不过要注意定期清理过期的nonce,不然内存会爆。
知识体系总览
下面这张图把API安全测试的核心逻辑串起来了,你可以对照着看:
实战测试流程
说了这么多理论,咱们来点实际的。我一般按这个流程测API安全:
- 信息收集:抓取所有API请求,了解接口文档和参数结构。
- 鉴权测试:去掉token、修改token、使用过期token,看服务端反应。
- 参数篡改测试:修改关键参数的值、类型、数量,看服务端是否校验。
- 重放测试:把同一个请求发两次,看服务端是否做了防重放。
- 越权测试:用A用户的token访问B用户的资源。
- 批量测试:用脚本快速发送大量请求,看服务端是否有频率限制。
最后说一句:API安全测试不是一次性的工作。每次版本更新、接口变更,都要重新测一遍。我见过太多团队,上线前测得好好的,改了个小功能就漏了。安全测试,贵在坚持。