前言

今天继续 DVWA Web 漏洞靶场系列,来学习另一个 Web 安全中出现频率极高、影响范围极广的经典漏洞——Reflected Cross Site Scripting(反射型跨站脚本,简称 Reflected XSS)

如果说 SQL 注入是让攻击者影响数据库查询逻辑,命令注入是让攻击者影响服务器操作系统,那么 XSS 的目标则是——让攻击者控制其他用户浏览器中的页面行为

一旦 XSS 利用成功,攻击者注入的 JavaScript 代码会在受害者浏览器中,以目标网站的身份和权限运行。轻则弹窗、篡改页面内容,重则可能造成钓鱼、会话劫持、敏感信息读取、账户操作诱导等问题。

XSS 的核心原理同样很简单:

Web 应用将用户可控输入直接输出到 HTML 页面中,却没有进行正确的上下文编码,导致浏览器把输入内容当成 HTML 标签或 JavaScript 代码解析执行。

DVWA 的 Reflected XSS 靶场正好通过 Low、Medium、High、Impossible 四个安全等级,完整展示了“无过滤 → 黑名单过滤 → 正则过滤 → 输出编码”的防护演进过程。


1. Reflected XSS 核心理论基础

1.1 什么是反射型 XSS?

反射型跨站脚本漏洞(Reflected Cross Site Scripting),对应常见漏洞编号:

CWE-79:Improper Neutralization of Input During Web Page Generation(Web 页面生成过程中对输入未进行正确处理)

反射型 XSS 的典型特征是:

  • 攻击者构造恶意参数;
  • 参数通过 URL 查询字符串、表单提交等方式发送给服务器;
  • 服务器将该参数“反射”回响应页面;
  • 浏览器解析页面时执行攻击者注入的脚本;
  • 恶意内容通常不会被永久保存到数据库中。

例如,某页面存在一个姓名参数:

http://dvwa.local/vulnerabilities/xss_r/?name=Alice

后端将参数直接拼接进页面:

echo "Hello " . $_GET['name'];

正常访问时,页面显示:

Hello Alice

但如果用户输入中包含可被浏览器解析的标签或事件处理代码,并且后端没有进行安全编码,浏览器就可能把它当作页面的一部分执行。


1.2 反射型 XSS 的本质

反射型 XSS 的漏洞链路可以概括为:

攻击者可控输入
        ↓
URL 参数 / 表单参数 / 请求头等
        ↓
后端未正确过滤或未进行输出编码
        ↓
输入被拼接到 HTML 响应页面
        ↓
浏览器将输入当作 HTML / JavaScript 解析
        ↓
恶意脚本在目标站点上下文中执行

最关键的问题不在于“用户输入了 <script>”,而在于:

服务器没有区分“普通文本”和“浏览器可执行的 HTML 内容”。

开发者原本希望页面显示的是一段文字,但浏览器最终接收到的却是一个真正的标签、属性或脚本片段。


1.3 反射型 XSS 成立的必要条件

并不是所有能回显输入的页面都会产生 XSS。通常需要同时满足以下几个条件。

条件一:攻击者能够控制某个输入点

常见输入来源包括:

  • URL 查询参数,例如 ?name=xxx
  • GET、POST 表单参数
  • 搜索框内容
  • 错误提示中的参数
  • 跳转地址、回调参数
  • HTTP 请求头中的 Referer、User-Agent 等

条件二:输入会被输出到页面中

例如:

echo $_GET['name'];

或者:

echo "<div>搜索结果:" . $_GET['keyword'] . "</div>";

只要用户输入最终出现在 HTML 响应内容中,就需要进一步判断输出位置和编码方式。

条件三:输出时没有进行正确的上下文编码

错误示例:

echo "<pre>Hello {$name}</pre>";

安全的文本输出应当进行 HTML 编码:

echo "<pre>Hello " . htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "</pre>";

经过 htmlspecialchars() 处理后,<>"' 等特殊字符会被转换为 HTML 实体,浏览器只会把它们显示成普通文字,而不会当作标签或脚本解析。


1.4 Reflected XSS、Stored XSS 与 DOM XSS 的区别

XSS 并不只有一种类型,最常见的是以下三类:

类型 恶意内容是否持久化 触发方式 典型场景
Reflected XSS(反射型) 不持久化 用户访问恶意链接或提交恶意请求 搜索页面、报错页面、参数回显页面
Stored XSS(存储型) 持久化到数据库或文件 其他用户访问已被污染的页面 评论区、昵称、留言板、个人资料
DOM XSS 不一定经过后端 前端 JavaScript 直接操作 DOM location.hash、前端模板拼接、innerHTML

其中,反射型 XSS 常常需要攻击者诱导受害者点击一个精心构造的 URL,因此它也经常出现在钓鱼链接、伪造通知、社工攻击等场景中。


1.5 XSS 可能造成什么危害?

XSS 并不只是一个“弹窗漏洞”。

弹窗只是验证脚本是否执行成功的最直观方式,真实风险取决于页面权限、浏览器安全策略、Cookie 属性、站点功能以及受害者身份。

常见影响包括:

  • 篡改页面内容,伪造登录框、支付提示或客服窗口;
  • 诱导用户输入账号、密码、验证码等敏感信息;
  • 读取页面中本来只有当前用户可见的数据;
  • 以受害者身份发起页面内可执行的操作;
  • 窃取未配置 HttpOnly 的会话 Cookie;
  • 利用管理员访问的后台页面扩大影响范围;
  • 配合 CSRF、点击劫持、钓鱼等攻击形成组合攻击链。

需要注意的是:

HttpOnly Cookie 可以降低 Cookie 被 JavaScript 直接读取的风险,但它不能修复 XSS 漏洞本身。攻击者依然可能通过页面篡改、操作诱导、读取 DOM 数据等方式造成影响。


2. DVWA Reflected XSS(Low)

2.1 页面黑盒探测

在这里插入图片描述

首先在输入框中提交正常文本:

Alice

观察页面是否将输入内容原样显示。

在这里插入图片描述

页面显示:

Hello Alice

说明当前输入会被后端接收并回显到 HTML 响应中。

下一步,需要判断应用是将用户输入作为普通文本显示,还是直接交给浏览器解析。

在本地 DVWA 授权靶场中,可使用一个无害的验证 Payload:

<script>alert(document.domain)</script>

如果浏览器弹出当前站点域名,说明输入中的 <script> 标签没有被编码,浏览器已经把用户输入当成脚本执行。

在这里插入图片描述


2.2 漏洞利用原理

Low 级别的核心问题是:

用户输入没有经过任何过滤、转义或编码,直接被拼接到 HTML 页面中。

攻击者提交的内容:

<script>alert(document.domain)</script>

后端返回页面时,可能形成类似结构:

<pre>Hello <script>alert(document.domain)</script></pre>

浏览器在解析 HTML 时,不会把 <script> 当成普通文本,而是会把它识别为真正的脚本标签,因此 JavaScript 得以执行。

需要特别注意:

<pre>

标签只能保留文本格式和空白字符,并不能阻止内部 HTML 标签被解析

很多开发者误以为把用户输入放进 <pre><div><p> 等普通标签中就安全了,但只要没有进行输出编码,浏览器仍会解析用户输入里的标签。


2.3 源码审计:无过滤直接回显

DVWA Low 级别的核心逻辑通常类似于:

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Feedback for end user
    echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

逐行解读:

  1. header ("X-XSS-Protection: 0");
    主动关闭浏览器自带的XSS防护,避免浏览器拦截测试Payload,方便漏洞复现。
  2. 判断逻辑
    仅校验GET请求里name参数是否存在、内容不为空,不存在任何过滤、字符替换、HTML转义操作,用户传入的内容会原样获取。
  3. 漏洞关键语句
    echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
    程序直接将用户可控的name参数拼接进HTML代码中输出。
    即便内容被包裹在<pre>标签内部,<pre>仅用于保留文本空格与换行,不会阻止浏览器解析<script>这类HTML标签。

最终页面源码会拼接为:

<pre>Hello <script>alert(document.domain)</script></pre>

浏览器解析页面时识别script标签并执行JS代码,反射型XSS成功触发。

漏洞根源概括

用户可控输入未经HTML实体编码、无任何过滤,直接嵌入HTML页面返回客户端,浏览器将恶意内容当作脚本解析运行。

标准修复方式

使用htmlspecialchars()对输出内容转义,把< > & " '等特殊字符转为HTML实体,浏览器只会视作纯文本展示:

$name = htmlspecialchars($_GET['name'], ENT_QUOTES);
echo '<pre>Hello ' . $name . '</pre>';

3. DVWA Reflected XSS(Medium)

3.1 页面黑盒探测

Medium 级别通常会尝试过滤最常见的 <script> 标签。

当直接提交:

<script>alert(document.domain)</script>

会发现页面不再弹窗

在这里插入图片描述

这说明应用程序已经加入了一层过滤逻辑。

但这里必须明确:

“过滤了某一种 Payload”不等于“修复了 XSS 漏洞”。

如果防护逻辑只是删除固定字符串,例如删除小写的 <script></script>,那么攻击者仍然可以通过大小写变化、其他 HTML 标签、事件属性等方式绕过。


3.2 黑名单绕过思路

假设后端只过滤了:

<script>
</script>

那么大小写混合的标签就可能绕过检查:

<ScRiPt>alert(document.domain)</ScRiPt>

这里我们可以看到

在这里插入图片描述

原因在于 HTML 标签名通常不区分大小写,但 PHP 的普通字符串替换函数默认是区分大小写的。

也就是说:

<script>

和:

<ScRiPt>

对于浏览器来说都可能是脚本标签;但对于简单字符串黑名单来说,它们并不一定相同。

除了 <script> 标签外,浏览器中还存在大量可触发脚本执行的上下文,例如带有事件属性的元素:

<img src=x onerror=alert(document.domain)>

当图片加载失败时,浏览器可能触发 onerror 中的 JavaScript。

这也说明了黑名单方案的根本问题:

需要拦截的不是一个单词,而是浏览器中大量可能进入脚本执行上下文的语法组合。

黑名单很难覆盖完整,漏掉一个入口就可能被绕过。


3.3 源码审计:简单字符串替换的缺陷

Medium 级别后端PHP源码:

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Get input
    $name = str_replace( '<script>', '', $_GET[ 'name' ] );

    // Feedback for end user
    echo "<pre>Hello {$name}</pre>";
}

?>

代码逻辑拆解

页面依旧接收GET方式传入的name参数,相比Low级别新增了一处防护:使用str_replace('<script>', '', 变量),把内容里完整的小写<script>字符串直接清空删除,本意是屏蔽脚本标签,阻止<script>alert()</script>这类基础Payload执行。

其余逻辑没有改动:依旧关闭浏览器XSS防护、无HTML转义处理,替换完成后的内容直接拼接进HTML页面回显。

防护存在的致命缺陷

str_replace属于纯文本字面替换函数,只会机械检索固定字符串进行删除,不会解析HTML语法、不区分标签大小写、不识别浏览器的HTML容错解析规则,属于简陋的黑名单防御,漏洞依旧可以绕过。

常见绕过Payload原理

  1. 大小写混合绕过str_replace仅匹配小写<script>,大小写混杂后不会被识别替换
    Payload:<ScRiPt>alert(document.domain)</ScRiPt>
    字符串检索不到<script>,替换失效,浏览器不区分HTML标签大小写,脚本正常执行。
  2. 嵌套标签绕过
    Payload:<scr<script>ipt>alert(1)</scr</script>ipt>
    运行过程:
    函数先剔除中间的<script>,文本变为<script>alert(1)</script>,完整脚本标签重现,成功触发XSS。
  3. 舍弃script标签,使用事件属性标签
    防御仅拦截了script标签,没有管控img、svg等携带事件触发的HTML标签:
    <img src=1 onerror=alert(document.domain)>``<svg onload=alert(1)>
    这类载荷不含<script>,不会被替换删除,依靠加载事件执行JS,轻松绕过限制。

漏洞本质

依靠黑名单关键字删除的方式无法穷尽所有可执行JS的HTML写法,只要不对输出内容做HTML实体编码,特殊符号依旧会被浏览器解析为HTML语法,XSS漏洞无法彻底根除。

正确修复方案

摒弃黑名单替换,统一使用htmlspecialchars()进行输出转义,将< > & " '等HTML特殊字符转为实体字符,无论何种标签、大小写、事件属性,都会被渲染成纯文本,彻底杜绝反射型XSS:

$name = htmlspecialchars($_GET['name'], ENT_QUOTES);
echo "<pre>Hello {$name}</pre>";

4. DVWA Reflected XSS(High)

4.1 页面黑盒探测

High 级别一般会使用正则表达式检测 <script> 的变形,防护比 Medium 更严格。

例如,大小写混合的 <ScRiPt> 已经无法绕过。

在这里插入图片描述

此时说明防护逻辑不再是简单匹配固定字符串,而是开始尝试识别脚本标签的不同写法。

但 High 级别依旧存在一个核心问题:

它的防护目标仍然是“识别并删除危险标签”,而不是“让用户输入以普通文本方式安全显示”。

如果开发者只是把检测范围从字符串替换升级成正则表达式,依然无法真正覆盖全部 HTML 解析场景。


4.2 黑名单与正则过滤为什么仍然不可靠?

假设后端通过正则表达式尝试删除所有疑似 <script> 标签:

preg_replace('/<.*s.*c.*r.*i.*p.*t.*>/i', '', $name);

这种写法看起来比简单的 str_replace() 更严格,但仍然存在明显问题。

第一,它只关注 <script> 标签。

而 XSS 并不只依赖 <script> 标签,许多 HTML 标签及事件属性同样可能触发脚本执行。

第二,正则表达式并不适合完整解析 HTML。

HTML 的标签、属性、实体编码、浏览器容错机制、不同上下文嵌套关系都非常复杂。试图用一条正则表达式覆盖所有危险输入,最终通常会出现:

  • 该拦截的没拦住;
  • 正常内容被误杀;
  • 后续维护困难;
  • 新的浏览器解析差异无法及时覆盖。

第三,黑名单思路本身就是被动防御。

开发者需要先知道所有危险写法,再逐一拦截;攻击者只需要找到一个没有被考虑到的语法入口即可。

所以 High 级别的核心教训是:

黑名单从“字符串过滤”升级到“正则过滤”,并没有改变防护方向错误的问题。


4.3 黑盒绕过测试与Payload

绕过核心:彻底舍弃script标签,使用HTML事件属性触发JavaScript,载荷不含script字段,避开正则匹配规则。

  1. 首选Payload:img标签加载错误事件
    <img src=x onerror=alert(document.domain)>
    原理:src赋值无效地址,图片加载失败触发onerror事件,执行弹窗代码,无script内容,正则不会过滤,页面加载后成功执行XSS。
  2. svg加载事件Payload
    <svg onload=alert(document.cookie)>
    页面渲染完成后自动执行JS脚本,可用于窃取Cookie。
  3. input自动聚焦触发
    <input onfocus=alert(1) autofocus>

4.4 源码审计:正则过滤不等于安全编码

High级别后端完整PHP源码:

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Get input
    $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );

    // Feedback for end user
    echo "<pre>Hello {$name}</pre>";
}

?>

代码解析

  1. header ("X-XSS-Protection: 0"); 关闭浏览器自带XSS防护,方便漏洞测试;
  2. 获取GET参数name,仅校验参数是否存在、非空;
  3. 正则忽略大小写模糊匹配script字符串,所有包含script的标签都会被清空;
  4. 处理完毕的变量直接拼接进HTML页面输出,全程未进行HTML实体转义。

漏洞根源

Low、Medium、High三级均使用黑名单防御机制,区别仅在于过滤严苛程度。只拦截危险标签,不在输出时调用htmlspecialchars()转义特殊字符,用户可控内容始终会被浏览器解析为HTML,无法彻底抵御XSS攻击。

修复方案

<?php
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    $name = htmlspecialchars($_GET['name'], ENT_QUOTES);
    echo "<pre>Hello {$name}</pre>";
}
?>

对输出内容进行HTML编码,< > & " '全部转为实体字符,所有HTML标签都会以文本形式展示,XSS漏洞彻底修复。


5. DVWA Reflected XSS(Impossible)

5.1 源码审计

Impossible 等级摒弃黑名单过滤思路,采用HTML实体编码输出 + CSRF令牌校验双重安全机制,从根源防御反射型XSS,后端完整源码如下:

<?php
// 判断GET请求是否携带name参数且参数不为空
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // 校验CSRF防护令牌
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // 获取用户输入,并进行HTML实体编码
    $name = htmlspecialchars( $_GET[ 'name' ] );

    // 将处理后的内容回显至页面
    echo "<pre>Hello {$name}</pre>";
}

// 生成全新的会话CSRF令牌
generateSessionToken();
?>

代码分段解析

  1. Anti-CSRF 令牌校验checkToken() 会核对请求携带的令牌与服务端会话存储令牌是否一致;
    每次请求结束调用 generateSessionToken() 刷新令牌,令牌单次有效。
    攻击者无法伪造合法token构造恶意链接,大幅削减反射型XSS的利用条件。
  2. 核心防护函数:htmlspecialchars()
    该函数会剥夺特殊字符的HTML语法功能,将其转换为纯文本实体:
原始字符 转换后HTML实体
< &lt;
> &gt;
" &quot;
& &amp;

注:默认不会转义单引号 ',传入参数 ENT_QUOTES 才可将单引号转为 &#039;

编码效果演示

用户提交恶意载荷:
<script>alert(document.domain)</script>

经过函数转义后,页面最终源码:
&lt;script&gt;alert(document.domain)&lt;/script&gt;

浏览器渲染效果:
纯文本展示:<script>alert(document.domain)</script>
在这里插入图片描述

不会识别为脚本标签,JS代码无法执行,XSS攻击失效。


5.2 为什么输出编码才是正确修复方式?

旧方案(Low/Medium/High:黑名单拦截)

逻辑流程:

预判所有危险字符/标签 → 字符串替换/正则匹配 → 发现后删除过滤

弊端:
HTML可执行JS的方式不计其数,无法穷举全部攻击载荷;规则永远滞后于攻击手法,漏洞始终存在绕过空间。

正确方案(Impossible:输出HTML编码)

逻辑流程:

不区分输入好坏,所有用户可控内容统一转义 → 全部以普通文本形式输出

核心原理:
< > & " 等字符失去HTML标签、事件属性的语法意义,浏览器只会将内容当作文字解析,无需持续增补过滤规则,从底层阻断XSS触发条件。


5.3 Impossible 级别安全总结

  1. 彻底舍弃黑名单防御体系
    不再依靠正则、字符串替换拦截恶意标签与事件载荷,规避黑名单覆盖不全的固有缺陷;
  2. 输出点统一执行HTML实体编码
    利用 htmlspecialchars() 消解特殊字符的HTML语义,杜绝浏览器解析恶意脚本;
  3. 新增CSRF一次性令牌防护
    限制恶意链接批量传播利用,收缩反射型XSS攻击场景;
  4. 可拓展优化
    添加 ENT_QUOTES 参数可同时转义单、双引号,适配标签属性内回显场景,防护更加完备。

6. XSS 防护的正确姿势

6.1 第一原则:根据输出上下文进行编码

不同输出位置需要不同的编码策略。

输出位置 推荐防护方式
HTML 文本节点 HTML 实体编码,如 htmlspecialchars()
HTML 属性值 属性编码,并始终使用引号包裹属性值
JavaScript 字符串 JavaScript 上下文编码,避免直接字符串拼接
URL 参数 URL 编码,如 urlencode()
CSS 上下文 CSS 编码,尽量避免拼接用户输入

必须牢记:

输入过滤不能替代输出编码。

用户输入可能经过多个业务流程,最终出现在不同页面、不同上下文中。真正靠近风险点的位置,是“输出到浏览器之前”。


6.2 不要使用黑名单修复 XSS

以下写法都不推荐作为 XSS 的修复手段:

str_replace('<script>', '', $input);
preg_replace('/script/i', '', $input);
str_replace('onerror', '', $input);

原因是黑名单无法穷尽浏览器可以解析的所有危险语法。

开发者越试图维护“危险关键字列表”,越容易陷入规则膨胀、漏拦截和误拦截的问题。


6.3 必须支持富文本时,使用白名单净化方案

有些业务确实需要用户提交富文本,例如文章发布、评论编辑器、知识库系统。

此时不能简单使用 htmlspecialchars() 把所有标签都转义,否则格式会全部丢失。

正确思路是:

明确允许哪些标签、哪些属性、哪些协议
        ↓
删除其他所有内容
        ↓
使用经过安全验证的 HTML 净化库处理

例如,只允许:

p、br、strong、em、ul、ol、li、a

并且限制链接协议只能是:

http、https

不要自己用正则表达式“过滤 HTML”,应使用成熟且经过长期验证的 HTML Sanitizer / Purifier 类库。


6.4 CSP 可以降低风险,但不能代替修复漏洞

内容安全策略(Content Security Policy,CSP)可以帮助浏览器限制脚本加载和执行来源。

例如,可以限制页面只允许加载本站脚本,禁止内联脚本等。

但必须注意:

CSP 是纵深防御措施,不是 XSS 漏洞的替代修复方案。

正确顺序应该是:

优先修复输出编码问题
        ↓
再通过 CSP 降低残余风险
        ↓
配合 HttpOnly、Secure、SameSite Cookie 等安全配置

7. DVWA Reflected XSS 四个等级对比

安全等级 核心防护逻辑 防护效果 主要问题
Low 不做过滤,直接回显 完全可利用 用户输入直接进入 HTML
Medium 过滤固定 <script> 字符串 容易绕过 大小写、事件属性、其他标签未覆盖
High 正则匹配脚本标签 有一定拦截效果 本质仍是黑名单,无法完整解析 HTML
Impossible htmlspecialchars() 输出编码 可有效阻断当前反射型 XSS 正确的上下文编码思路

从 DVWA 的四个等级可以非常直观地看出防护演进:

无防护
   ↓
简单黑名单
   ↓
复杂黑名单
   ↓
输出编码

真正可靠的安全方案,不是把黑名单写得越来越长,而是:

不要让不可信输入以浏览器可执行语法的形式进入页面。


8. 总结

反射型 XSS 的漏洞原理并不复杂:

用户输入被服务端反射到页面中,且没有经过正确的输出编码,浏览器就可能把输入当成 HTML 或 JavaScript 执行。

DVWA 的不同安全等级分别展示了:

  • Low:直接回显,完全无防护;
  • Medium:简单字符串黑名单,容易绕过;
  • High:正则黑名单,仍然无法覆盖全部 XSS 场景;
  • Impossible:使用 htmlspecialchars() 进行 HTML 输出编码,从根源阻断标签解析。

最后记住 XSS 防御最重要的一句话:

不要试图猜测攻击者会输入什么;要确保无论用户输入什么,都只能以普通文本的形式被浏览器展示。


免责声明

  1. 本文所有内容仅用于网络安全技术研究与教学演示,所有测试均在本地授权搭建的 DVWA 靶场环境中进行,旨在帮助读者理解反射型 XSS 漏洞原理与防御机制。

  2. 请勿将本文涉及的技术与方法用于任何未经授权的真实系统测试。任何利用本文内容进行的非法攻击行为,均与作者无关,由使用者自行承担相应法律责任。

  3. 网络安全学习与测试应严格遵守相关法律法规,仅在获得明确授权的前提下开展安全测试。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐