导语 XSS作为OWASP top10之一,是不容小视的安全威胁。
简介 人们经常将跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。 跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。 XSS的分类 绿盟这张图我觉得挺好 但是由于广泛被接受的分类是反射型、存储型、DOM-based,并且DVWA也是这三类,因此我们还按照反射型、存储型和DOM-based型来简单分析
DVWA之反射型XSS low 开局一个输入框,直接回显html,无任何过滤 因此直接用<script>就可以利用了 查看源代码
<?php header ("X-XSS-Protection: 0" ); if ( array_key_exists( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { echo '<pre>Hello ' . $_GET [ 'name' ] . '</pre>' ; } ?>
确实直接就echo了输入的name,且没有过滤。
medium 开局一个输入框,发现会过滤<script>,但是不会过滤</script> 于是直接双写,输入<scr<script>ipt>
,发现成了。 查看源代码
<?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>" ; } ?>
果然仅仅简单的将<script>删掉了罢了 于是还可以用大小写绕过,比如<scRipt>
high emmm直接看源码了
<?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>" ; } ?>
可以看到用了正则表达式过滤了。 但是可以通过img、body等标签的事件或者iframe等标签的src注入恶意的js代码 比如酱紫<img src="" οnerrοr="alert('XSS')">
impossible 众所周知,DVWAimpossible就差不多是impossible了。
<?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()把<>都给过滤了,还用了session,且session并没有在response里。 好!impossible!
DVWA之存储射型XSS low 开局一个注册,可以提交的有name和message 直接回显输入的所有内容,包括name和message。 但是name有长度限制,直接改前端 在name输入<script>alert("name")</script>
,然后在message输入<script>alert("message")</script>
(而且还会把库里所有的东西都echo出来) 看源代码
<?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>' ); } ?>
和预想的几乎一样,只用了一个trim过滤空格、换行、缩进等,并没有针对DOM的标签。
medium 猜测和反射型一样,于是用大小写绕过 发现message还加了一个引号过滤。 查看源码
<?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>' ); } ?>
可以看到和我们的预想一样,但是只给message用了addslashes,此外还用了一个htmlspecialchars,因此message基本不能注入了。但是name的注入依旧只是简单的过滤罢了没有影响。
high 直接看代码
<?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像medium一样,过滤的很完善,而name加了正则,同样和反射型一样用含有src的标签用onerror即可
impossible <?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(); ?>
好,这波name也用了htmlspecialchars,合理。但是要注意的是,如果htmlspecialchars函数使用不当,攻击者就可以通过编码的方式绕过函数进行XSS注入,这个具体看其他博客吧,也是我仍然需要学习的知识。
DVWA之DOM型XSS low 在选了English之后,会发现url里有一个?default=English,于是我们把English改为<script>alert('123')</script>
,在访问时可以看到确实被执行了。 查看源码
就说了句无防备……
medium 尝试和low一样的操作,发现不行,于是尝试双写和大小写绕过,发现也并不行。 于是尝试用onerror绕过,输入<img src="" onerror=alert("123")>
发现也不行。 晕了,看下源码
<?php if ( array_key_exists( "default" , $_GET ) && !is_null ($_GET [ 'default' ]) ) { $default = $_GET ['default' ]; if (stripos ($default , "<script" ) !== false ) { header ("location: ?default=English" ); exit ; } } ?>
这里用了一个stripos,查找字符串在另一字符串中第一次出现的位置,如果有就为true,即就不等于false,就会执行header ("location: ?default=English");
也就是说第一步的想法是正确的,要用onerror绕过。 可以看到,我们的img已经插入了html,但是只是一个value罢了。 于是我们尝试闭合option标签,输入</option><img src="" onerror=alert("123")>
发现还是不行,原来此时img还在<select>标签,于是再把select标签页闭合了</option></select><img src="" onerror=alert("123")>
high <?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参数必须是这几个之一。 这里不太会,去抄答案了。https://www.jianshu.com/p/001daa7cf1f5 ?default=English #<script>alert(/xss/)</script>
由于 form表单提交的数据 想经过JS 过滤 所以注释部分的javascript 代码 不会被传到服务器端(也就符合了白名单的要求)
impossible
可以看到前端变成了
<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 + "'>" + (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 >
这里用另一个js脚本,将我们赋给default的值赋给option,而浏览器会将这些值进行url编码,因此就可以防止注入。
后记 其实在刚开始用dvwa了解xss漏洞的时候,我一直不太理解这漏洞到底能造成什么危害,因为说到底各式各样的解都只是POC罢了,也没有利用,直到后来看到了一些真正恶意的js才终于明白js也能是很恶意的东西,比如这篇文章。https://cloud.tencent.com/developer/article/1038173?from=information.detail.js%E6%81%B6%E6%84%8F%E4%BB%A3%E7%A0%81%E6%9C%89%E5%93%AA%E4%BA%9B 给我的收获是,学习网络安全不能只看poc,一定要看看exp,甚至亲自去操作一些exp实践,才能真正的理解、掌握相应的技巧
参考:https://www.jianshu.com/p/001daa7cf1f5 http://blog.nsfocus.net/xss-advance/ https://baike.baidu.com/item/XSS%E6%94%BB%E5%87%BB/954065?fr=aladdin https://blog.csdn.net/lay_loge/article/details/90440207