• DVWA——Insecure CAPTCHA (不安全的验证码)


    Insecure CAPTCHA,意思是不安全的验证码,CAPTCHA是Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)的简称。但个人觉得,这一模块的内容叫做不安全的验证流程更妥当些,因为这块主要是验证流程出现了逻辑漏洞。

     这一模块的验证码使用的是Google提供reCAPTCHA服务,

    服务器通过调用recaptcha_check_answer函数检查用户输入的正确性。

    recaptcha_check_answer($privkey,$remoteip, $challenge,$response)

    参数$privkey是服务器申请的private key,$remoteip是用户的ip,$challenge是recaptcha_challenge_field字段的值,来自前端页面 ,$response是recaptcha_response_field字段的值。函数返回ReCaptchaResponse class的实例,ReCaptchaResponse类有2个属性 :

    $is_valid是布尔型的,表示校验是否有效,

    $error是返回的错误代码。

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

      可以看到,服务器将改密操作分成了两步,第一步检查用户输入的验证码,验证通过后,服务器返回表单,第二步客户端提交post请求,服务器完成更改密码的操作。但是,这其中存在明显的逻辑漏洞,服务器仅仅通过检查Change、step 参数来判断用户是否已经输入了正确的验证码。

    漏洞利用


    直接输入密码提交,利用Burpsuite抓包

     因为我们没有翻墙,所以没能成功显示验证码,发送的请求包中也就没recaptcha_challenge_field、recaptcha_response_field两个参数,将step改为2即可

    Medium

    源码

    <?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" />    ////提交passed_captcha参数
                        <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' ] ) {//验证step1中提交的参数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);
    }
    
    ?> 
    

      可以看到,Medium级别的代码在第二步验证时,参加了对参数passed_captcha的检查,如果参数值为true,则认为用户已经通过了验证码检查,然而用户依然可以通过伪造参数绕过验证,本质上来说,这与Low级别的验证没有任何区别。

    关键代码已经注释标红。

    漏洞利用


    我们通过Burpsuite抓包,将其中的step=1更改为step=2,将Change=Change更改为Change=Change&passed_captcha=true即可绕过。

    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' ],
    $_SERVER[ 'REMOTE_ADDR' ],
    $_POST[ 'recaptcha_challenge_field' ],
    $_POST[ 'recaptcha_response_field' ] );
    
    // Did the CAPTCHA fail?
    if( !$resp->is_valid && ( $_POST[ 'recaptcha_response_field' ] != 'hidd3n_valu3' || $_SERVER[ 'HTTP_USER_AGENT' ] != 'reCAPTCHA' ) ) {	//当验证码错误并且提交的参数有一个不正确时,就会提示验证不通过
    // 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 ) {
    $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;
    }
    }
    
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
    }
    
    // Generate Anti-CSRF token
    generateSessionToken();
    
    ?>
    

      

    可以看到,服务器的验证逻辑是当$resp(这里是指谷歌返回的验证结果)是对的,或者参数recaptcha_response_field等于hidd3n_valu3并且http包头的User-Agent参数等于reCAPTCHA时,就认为验证码输入正确,满足其一即可,验证码这里肯定是不对的,所以我们只要构造参数即可。

    漏洞利用


    由于$resp参数我们无法控制,所以来修改recaptcha_response_field、User-Agent两个参数上。
    更改参数recaptcha_response_field以及http包头的User-Agent如下

     

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

      可以看到,Impossible级别的代码增加了Anti-CSRF token 机制防御CSRF攻击,利用PDO技术防护sql注入,验证过程终于不再分成两部分了,验证码无法绕过,同时要求用户输入之前的密码,进一步加强了身份认证。

  • 相关阅读:
    【HeadFirst设计模式学习笔记】10 组合模式
    【HeadFirst 设计模式学习笔记】12 代理模式
    【HeadFirst 设计模式学习笔记】15 享元模式拾零
    【HeadFirst 设计模式学习笔记】16 建筑者(Builder)模式拾零
    【HeadFirst 设计模式学习笔记】11 状态模式
    搜索引擎设计实用教程(1)以百度为例 之一:查询处理以及分词技术
    最容易写错的100个字
    看完这个我流泪了,人生有太多的误会和无奈……
    哈佛成功金言
    上海的朋友注意了,周六气温可能骤降到1℃
  • 原文地址:https://www.cnblogs.com/c1047509362/p/12599314.html
Copyright © 2020-2023  润新知