XSS学习
XSS
跨站脚本攻击(Cross Site Scripting),为了与重叠样式表CSS区分,换了另一个缩写XSS。
概念阐述
XSS攻击的核心是将可执行的前端脚本代码(一般为JavaScript)植入到网页中。
反射型
1、攻击者将JS代码作为请求参数放置URL中,诱导用户点击 示例:
http://localhost:8080/test?name=<script>alert("you are under attack!")</script>
2、用户点击后,该JS作为请求参数传给Web服务器后端
3、后端服务器没有检查过滤,简单处理后放入网页正文中返回给浏览器
4、浏览器解析返回的网页,中招!
存储型
上述方式攻击脚本直接经服务器转手后返回浏览器触发执行,存储型与之的区别在于能够将攻击脚本入库存储,在后面进行查询时,再将攻击脚本渲染进网页,返回给浏览器触发执行。常见的套路举例如下:
1、攻击者网页回帖,帖子中包含JS脚本
2、回帖提交服务器后,存储至数据库
3、其他网友查看帖子,后台查询该帖子的回帖内容,构建完整网页,返回浏览器
4、该网友浏览器渲染返回的网页,中招!
DOM型
特点与反射型xss相同,区别在于脚本存在于hash值中,并未向后端传送。
形成原因
形成XSS漏洞的主要原因是程序中输入和输出的控制不够严格
测试流程
① 在目标上找输入点,比如查询接口、留言板
② 输入一组 “特殊字符(>,’,"等)+唯一识别字符” ,点击提交后,查看返回源码,看后端返回的数据是否有处理
③ 通过搜索定位到唯一字符,结合唯一字符前后语法确定是否可以构造执行js的条件(构造闭合)
④ 提交构造的脚本代码(以及各种绕过姿势),看是否可以成功执行,如果成功执行则说明存在XSS漏洞
DVWA实例
反射型
low
输入“<xss>
”显示如下
可以看出是有反应的,尝试输入<script>alert(/xss/)</script>
,得到如下结果
写入成功,这时候返回头再看源码
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
嚯!果然是,没有什么防范措施。
medium
输入low一样的代码<script>alert(/xss/)</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>";
}
?>
发现其中加入了对<script>
的限制,因为只有对此标签的限制,很好绕过。
方法一:换标签
<img src=a onerror=alert(/xss/)>
方法二:双写
<scr<script>ipt>alert(/xss/)</script>
方法三:大小写
<sCRIpT>alert(/xss/)</script>
写入成功
可见遇到限制有时候未必是全限制,可以多试几个标签,万一限制也有限,就撞出来了
high
输入<script>alert(/xss/)</script>
输入<scr<script>ipt>alert(/xss/)</script>
结果一样
查看源码
<?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>";
}
?>
发现双写和大小写都给堵死了,<script>
标签已经不能再用,只能改换标签
输入<img src=a onerror=alert(/xss/)>
写入成功
impossible
直接查看源码
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$name = htmlspecialchars( $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,代码使用htmlspecialchars函数把预定义的字符&、”、 ’、<、>转换为 HTML 实体
存储型
low
尝试可以发现,name输入框有字数限制,可以抓包写入,也可以直接在Message中写入<script>alert(/xss/)</script>
写入成功
而且这个时候,也可以看到写入的信息以及存储并输出在下方
存储型xss的特点就是“存储”,只要不清除下方的存储,每次刷新都会有弹窗显示,因为每次刷新都是进行一次新的查询,都会把数据库中的数据调到前端,而前端每执行一次都会引发一次xss
源码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$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)) ? "" : ""));
// Sanitize name input
$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();
}
?>
medium
源码
<?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 = 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)) ? "" : ""));
// 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();
}
?>
从medium难度开始就对message输入框加上了htmlspecialchars函数进行编码,因此无法再通过message参数注入XSS代码,但是对于name参数,只是简单过滤了<script>
字符串,仍然存在存储型的XSS,可以抓包修改输入来注入。
限制了<script>
标签,但可以使用双写和大小写来绕过。
high
彻底限制了<script>
标签,但仍可以用其他标签绕过,如<img>
等
DOM型
选择的结果会显示在url中,因此直接在url中注释即可
low
查看网页源代码可得
属于由document.write
属性造成的DOM型xss漏洞
直接在url中写入<script>alert(/xss/)</script>
即可
这个时候再试验一下<img src=a onerror=alert(/xss/)>
,发现无法通过
再次查看网页源代码,发现输入的部分是在select和option标签内部,select标签内只允许内嵌option标签,而option标签中能内嵌script标签但不能内嵌img等标签,因此需要在注入时先闭合option和select标签,从而使注入的标签逃逸出来执行XSS
查看源码
未做任何防御机制
medium
先尝试<script>alert(/xss/)</script>
,发现会重定向至English的页面
试验过后发现,双写和大小写也无法绕过
但可以用上述的<img>
标签,前提是要闭合option和select标签
查看源码
调用stripos()函数获取“<script”字符串出现在参数的哪个位置(不区分大小写),因此重复内嵌和大小写等方式无法绕过该过滤机制
high
上述方法一一尝试后发现失败,查看源码
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
发现使用了白名单过滤,已经限制死了
这时只能尝试应用#号,URL栏的#号之后的内容并不会发送至服务器端,JS应用该符号实现在页面创建加载过程中定向到指定的页面内容上。
输入后是没有反应的(按回车也是),需要手动点击浏览器的刷新按钮,才会弹出xss弹窗
漏洞利用
窃取cookie——createElement()
使用document.cookie
来获取cookie字段。
下面顺便来看一下DVWA的CSRF的high难度
通过<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>
可获取user token,直接在csrf的包中添加即可
pikachu实例
反射型xss(get)
输入内容后,输入的内容会经过后台并回显
并在url中显示
get和post的特征区别在于是否在url中显示,get方法就可以直接在url中修改message的值
获取cookie成功
反射型xss(post)
先是一个登录页面
提示中有账号密码,登录后遇到与get型一样的形式,不同在于无法在url中直接修改,只能更改页面源码或者抓包修改
获取cookie成功
存储型xss
同DVWA中的存储型xss,输入的内容会保存,而每一次进入页面,从数据库调取数据时都会产生xss
一直到删除相应的内容为止
DOM型xss
随意输入后查看页面源码
尝试用双引号闭合,发现失败,查看源代码发现提示用‘ >
来闭合,尝试后发现<script>
标签无反应,尝试换用' > <img src=a onerror=alert(document.cookie)>
获取成功
DOM型xss-x
当输入完成后会出现链接,点击链接后会出现第二条链接,此时出现的链接与上题一样,查看源码后根据限制写出条件即可
需要点击第一个链接才会触发,算是多了一重保障。
xss之盲打
在两个输入框输入不同的xss弹窗,进行试验
提交后进入后台
可见两个输入框都存在xss漏洞
xss之过滤
查看源码发现如DVWA的高级难度,对<script>
标签进行了限制,便可使用其他标签进行xss注入
xss之htmlspecialchars
htmlspecialchars()函数把预定义字符转换成HT,L实体
预定义字符为:
1、& (和 )&(编码)
2、” (双引号)&qupt
3、’ (单引号)'
4、< (小于) &It
5、> (大于) >
可用引号类型:
ENT_COMPAT—默认,仅编码双引号
ENT_QUOTES—编码双引号和单引号(推荐)
ENT_NOQUOTES—不编码任何引号
我们发现特殊字符除了单引号都被进行了编码,因此我们可以我进输入单引号进行注释掉。输入:' onclick='alert(document.cookie)'
获取成功
xss之href输出
虽然有htmlspecialchars()函数,但由于输入的url拼接在 < a href=‘输入的内容’>属性中,所以可以使用javascript语法来构造使用伪协议payload:
javascript:alert(document.cookie)
xss之js输出
查看页面源码
输入的代码被生成再js中并赋值给$ms。既然我们的输入被带入到js中,可以闭合
</script><script>alert(document.cookie)</script>