XSS(跨站脚本攻击) 是一种网络安全漏洞,攻击者利用它将恶意的脚本代码“注入”到其他用户信任的网页中。当受害者的浏览器执行这些恶意代码时,攻击者就可以盗取用户信息、冒充用户身份进行操作等。XSS分为DOM型XSS、反射型XSS、存储型XSS。
1.DOM型XSS
1.1 什么是DOM
DOM(文档对象模型)是HTML和XML文档的编程接口。它将一个文档表示成一个由节点和对象组成的树形结构,允许编程语言(如JavaScript)动态地访问和更新文档的内容、结构和样式。
当浏览器加载一个网页时,它会解析HTML并创建这个DOM树。之后,JavaScript可以通过DOM提供的方法(如document.getElementById、element.innerHTML等)来修改这个树,从而改变显示在用户面前的页面。
1.2 DOM的树形结构
假设我们有一个这样简单的HTML:
<!DOCTYPE html>
<html>
<head>
<title>我的页面</title>
</head>
<body>
<h1>我的标题</h1>
<a href="https://example.com">我的链接</a>
</body>
</html>
浏览器会为它构建如下所示的 DOM 树:
文档 (Document)
└── HTML根元素 (<html>)
├── 头部 (<head>)
│ └── 标题 (<title>)
│ └── 文本节点 ("我的页面")
└── 主体 (<body>)
├── 一级标题 (<h1>)
│ └── 文本节点 ("我的标题")
└── 链接 (<a href="https://example.com">)
└── 文本节点 ("我的链接")
在这个树中,每个标签(如 <html>, <body>, <h1>)都是一个元素节点,标签内的文字是文本节点,而属性(如 href="https://example.com")是属性节点。
1.3 DOM的作用
DOM的核心作用是让JavaScript程序能够与网页内容进行交互。没有DOM,网页就是静态的、无法改变的。有了DOM,我们才能创造出丰富的动态交互体验。
具体来说,通过DOM,JavaScript可以:
(1)动态改变内容。你可以选中一个段落,然后改变它的文字。
document.querySelector(‘p’).textContent = ‘这段文字被JS修改了!’;
(2)动态改变样式。你可以让一个按钮在点击时改变颜色。
document.getElementById(‘myButton’).style.backgroundColor = ‘blue’;
(3)对用户交互做出反应。你可以为按钮、链接、输入框等元素绑定“点击”、“鼠标悬停”、“按键”等事件。
document.getElementById(‘myButton’).addEventListener(‘click’, function() {
alert(‘按钮被点击了!’);
});
(4)动态地创建、添加和删除元素。你可以根据用户的操作,在列表中动态添加新的项目,或者删除不需要的项目。
let newItem = document.createElement(‘li’); // 创建一个新的<li>元素
newItem.textContent = ‘新列表项’; // 设置它的内容
document.querySelector(‘ul’).appendChild(newItem); // 把它添加到<ul>中
1.4 DOM型XSS攻击
DOM型XSS攻击是一种发生在受害者浏览器中的攻击,攻击者通过操纵页面的文档对象模型来注入并执行恶意代码。整个攻击过程可以完全在客户端完成,服务器的响应(初始的HTML)可能是“干净”的、完全没有问题的,问题出在页面本身的JavaScript逻辑上。
攻击举例:
low难度
这是DVWA靶场中没有针对DOM型XSS攻击任何防护的网站。


查看前端JavaScript代码是有缺陷的,代码判断当前页面URL是否包含"default="参数,如果存在,从URL中提取"default="后面的值作为语言代码创建一个选中该语言的选项。也就是说可以注入一些 恶意的JS 代码进去,然后这部分会被包含到 lang 变量中,最终回显到页面上。
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>");
HTML DOM 有个 alert() 方法,用于显示带有一条指定消息和一个 OK 按钮的警告框。
document.cookie 里面可以读到 cookie 信息,把 cookie 放在一个 alert() 生成的警告框中,回显时就会得到我们想要的信息了。注入的payload 如下:
<script>alert(document.cookie)</script>

medium难度
前端代码不变,后端代码如下所示。stripos(string,find,start) 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写),header() 函数向客户端发送原始的 HTTP 报头。也就是说现在服务器通过一个模式匹配,过滤了 “script” 标签,不能直接注入 JS 代码了。

可以利用标签闭合 + 事件处理器绕过过滤。HTML 的 < img > 标签定义 HTML 页面中的图像,该标签支持 onerror 事件,在装载文档或图像的过程中如果发生了错误就会触发从而输出cookie。构造payload如下:
</option></select><img src = 1 onerror = alert(document.cookie)>

high难度
前端代码不变,后端代码如下所示。服务器设置了白名单,default 参数只接受 French,English,German 以及 Spanish 这几个单词。

可以在注入的 payload 中加入注释符 “#”,注释后边的内容不会发送到服务端,但是会被前端代码所执行。在 URL 中,# 被称为片段标识符。它的作用是告诉浏览器:“这个锚点后面的内容,是给前端(浏览器)自己看的,不需要发给服务器。构造payload如下:
English #<script>alert(document.cookie)</script>

2.反射型XSS
2.1 反射型XSS攻击
放射型 XSS(Reflected XSS) 是一种跨站脚本攻击(Cross-Site Scripting, XSS)类型,指攻击者将恶意脚本代码作为参数注入到用户的请求中,并通过服务器原样返回这些参数到网页中,导致脚本被立即执行。
攻击举例:
low难度
这是DVWA靶场中没有针对反射型XSS攻击任何防护的网站。

通常浏览器中设置了XSS防护,这里将其关闭来方便演示漏洞。后端代码只是判断了name参数是否为空,如果不为空的话就直接打印出来,并没有对name参数做任何的过滤和检查,没用进行任何的对XSS攻击的防御措施,存在非常明显的XSS漏洞,用户输入什么都会被执行。

用alert进行弹窗测试验证是否存在XSS,注入payload如下:
<script>alert(document.cookie)</script>

medium难度
后端代码检查GET请求中是否存在"name"参数,并且该参数的值不为NULL。从GET请求中获取"name"参数的值,并尝试移除其中出现的所有"<script>"字符串。注意,这里只移除了完整的"<script>",但不会移除大小写不同的变体(如"<SCRIPT>")或者嵌套在其他字符串中的情况,也不会处理其他形式的XSS向量(比如使用其他标签或事件处理器)。

这里通过大写<SCRIPT>的方式来绕过后端检测,注入payload如下:
<SCRIPT>alert(document.cookie)</SCRIPT>

high难度
正则表达式使用了i修饰符(不区分大小写),所以大小写混合无法绕过,但是因为正则表达式匹配的是<script,所以如果使用其他标签则不受影响。

使用img标签来绕过,注入payload如下:
<img src=x onerror=alert(document.cookie)>

3.存储型XSS
3.1 存储型XSS攻击
存储型XSS是指恶意脚本被永久存储在目标服务器的数据库、文件或其他存储介质中,当其他用户访问包含这些恶意内容的页面时,脚本会自动执行。
攻击举例:
low难度

这段代码是一个简单的留言板功能,用户可以在其中输入姓名和留言,然后将其存储到数据库中。在低安全级别下,代码没有对用户输入进行充分的过滤,导致存储型XSS成为可能。
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
用alert进行弹窗测试验证是否存在XSS,当上传留言后,页面会显示留言的内容,会自动自行恶意脚本,注入payload如下:
<script>alert(document.cookie)</script>

先进入HOME页,再回到XSS(Stored)页面,仍然可以得到cookie,证明存储型XSS攻击成功。


如果需要删除数据库中存在的XSS代码,进入dvwa数据库中guestbook表,删除恶意脚本。


medium难度
后端代码对留言内容做了严格处理:
addslashes():在特殊字符前添加反斜线(防SQL注入)
-
strip_tags():移除HTML、XML和PHP标签 -
mysqli_real_escape_string():转义SQL特殊字符 -
htmlspecialchars():将特殊字符转换为HTML实体
但是对名称这里只移除了完整的"<script>",但不会移除大小写不同的变体(如"<SCRIPT>")或者嵌套在其他字符串中的情况,也不会处理其他形式的XSS向量(比如使用其他标签或事件处理器)。
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
这里通过大写<SCRIPT>的方式来绕过后端检测,因为前端代码对名称有长度限制,所以需要burp suite该参数,注入payload如下:
<SCRIPT>alert(document.cookie)</SCRIPT>

先进入HOME页,再回到XSS(Stored)页面,仍然可以得到cookie,证明存储型XSS攻击成功。


如果需要删除数据库中存在的XSS代码,进入dvwa数据库中guestbook表,删除恶意脚本。


high难度
后端代码对留言内容的防护和中等难度的一样;对名称的检测使用了正则表达式,正则表达式使用了i修饰符(不区分大小写),所以大小写混合无法绕过,但是因为正则表达式匹配的是<script,所以如果使用其他标签则不受影响。
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
使用img标签来绕过,因为前端代码对名称有长度限制,所以需要burp suite该参数,注入payload如下:
<img src=1 onerror=alert(document.cookie)>

先进入HOME页,再回到XSS(Stored)页面,仍然可以得到cookie,证明存储型XSS攻击成功。


如果需要删除数据库中存在的XSS代码,进入dvwa数据库中guestbook表,删除恶意脚本。


4.区别特征
| 特征 | 反射型XSS | DOM型XSS | 存储型XSS |
|---|---|---|---|
| 核心原理 | 恶意输入由服务器"反射"回HTML页面 | 恶意输入由客户端JavaScript写入DOM | 恶意输入被永久存储在服务器,然后返回给所有用户 |
| 是否经过服务器 | 是,恶意载荷是HTTP请求的一部分 | 不一定,恶意载荷可能只在URL片段中,不会发送到服务器 | 是,恶意载荷会发送到服务器并被永久存储 |
| 执行原因 | 服务器返回的HTML中包含了恶意脚本,浏览器将其作为页面的一部分解析 | 客户端JS不安全地操作了DOM | 服务器从存储中读取恶意脚本并返回,浏览器将其作为页面的一部分解析 |
| 数据流 | 客户端 → 服务器 → 客户端 | 客户端 → (客户端) | 客户端(攻击者) → 服务器(存储) → 客户端(所有受害者) |
| 漏洞位置 | 服务器端代码(输出未转义) | 客户端JavaScript代码(逻辑不安全) | 服务器端代码(存储和输出都未正确处理) |
| 持久性 | 非持久(一次性) | 可能持久,可能非持久 | 持久(永久存储在服务器) |
| 触发条件 | 用户点击特定恶意链接 | 用户访问包含漏洞JS的页面 | 用户访问正常的受感染页面 |
5.XSS防御
核心在于永远不要信任用户的输入,并对所有不可信的数据进行严格的验证和适当的处理。
| 防御层面 | 核心措施 | 具体做法与说明 |
|---|---|---|
| 输入处理 | 输入验证与过滤 | - 白名单验证:只接受符合预期格式和范围的数据(如只允许数字、特定格式的邮箱)。 - 过滤敏感字符:移除或转义 <, >, ", ', & 等可能用于构造脚本的字符。 |
| 输出处理 | 输出编码 | - HTML编码:使用 htmlspecialchars() 等函数,将数据作为纯文本安全插入HTML。关键点:根据数据将要放入的上下文(HTML、JavaScript、URL等)选择合适的编码方式。 |
| 代码编写 | 使用安全的DOM操作方法 | - 避免 innerHTML/outerHTML:这些属性会解析字符串中的HTML标签,非常危险。- 使用安全属性:优先使用 textContent 或 innerText来设置纯文本内容。- 使用DOM方法:通过 document.createElement()、setAttribute() 和 appendChild() 等API来安全地创建和操作元素。 |
| 整体防护 | 实施内容安全策略 (CSP) |
- 限制脚本源:通过HTTP头 -HttpOnly:防止JS读取敏感Cookie |

1187

被折叠的 条评论
为什么被折叠?



