About

CAPTCHA (验证码)是一个程序,它可以判断访问网页的用户是人还是机器人,我们在生活中经常需要输入,例如在网页注册表格底部有扭曲文字的彩色图片。许多网站都使用验证码来防止“机器人程序”或通常用来生成垃圾邮件的自动程序的滥用。没有一个计算机程序能够像人类一样读取失真的文本,因此机器人程序无法导航受验证码保护的站点。

验证码通常用于保护敏感功能免受自动机器人的攻击,该功能通常在用户注册和更改、密码更改和发布内容中使用。在本例中,验证码保护用户帐户的更改密码功能,这对 CSRF 攻击和暴力穷举等攻击提供了有限的保护。

注意 DVWA 验证码使用的是 Google 提供 reCAPTCHA 服务,我们暂时使用不了该服务,这个对实验没有任何影响,因为我们根本不会去动验证码而是直接绕过。

Insecure-CAPTCHA - 图1

Object

你的目标是更改当前用户的密码,因为该站点使用的验证码的校验流程很差劲。

001 Low

这个验证码的问题是它很容易被绕过。开发人员假设所有用户都将通过屏幕1,完成验证码,然后进入下一个屏幕,在那里密码将实际更新。通过将新密码直接提交到更改页面,用户可以绕过验证码系统。

根据源码的逻辑判断修改密码被分成了 2 个步骤。

  • 首先后端会调用recaptcha_check_answer()检查验证码,如果验证码校验通过,会把step参数设置为2
  • step的值为2时,比较输入的新密码是否相同,如果相同就直接更改数据库;
  1. <?php
  2. if(isset($_POST['Change']) && ($_POST['step'] == '1')){
  3. // Hide the CAPTCHA form
  4. $hide_form = true;
  5. // Get input
  6. $pass_new = $_POST['password_new'];
  7. $pass_conf = $_POST['password_conf'];
  8. // Check CAPTCHA from 3rd party
  9. $resp = recaptcha_check_answer(
  10. $_DVWA['recaptcha_private_key'],
  11. $_POST['g-recaptcha-response']
  12. );
  13. // Did the CAPTCHA fail?
  14. if(!$resp) {
  15. // What happens when the CAPTCHA was entered incorrectly
  16. $html .= "
  17. <pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
  18. $hide_form = false;
  19. return;
  20. }
  21. else {
  22. // CAPTCHA was correct. Do both new passwords match?
  23. if($pass_new == $pass_conf){
  24. // Show next stage for the user
  25. echo "
  26. <pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
  27. <form action=\"#\" method=\"POST\">
  28. <input type=\"hidden\" name=\"step\" value=\"2\" />
  29. <input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
  30. <input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
  31. <input type=\"submit\" name=\"Change\" value=\"Change\" />
  32. </form>";
  33. }
  34. else {
  35. // Both new passwords do not match.
  36. $html .= "
  37. <pre>Both passwords must match.</pre>";
  38. $hide_form = false;
  39. }
  40. }
  41. }
  42. if(isset($_POST['Change']) && ($_POST['step'] == '2')){
  43. // Hide the CAPTCHA form
  44. $hide_form = true;
  45. // Get input
  46. $pass_new = $_POST['password_new'];
  47. $pass_conf = $_POST['password_conf'];
  48. // Check to see if both password match
  49. if($pass_new == $pass_conf) {
  50. // They do!
  51. $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)) ? "" : ""));
  52. $pass_new = md5($pass_new);
  53. // Update database
  54. $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
  55. $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '
  56. <pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
  57. // Feedback for the end user
  58. echo "
  59. <pre>Password Changed.</pre>";
  60. }
  61. else {
  62. // Issue with the passwords matching
  63. echo "
  64. <pre>Passwords did not match.</pre>";
  65. $hide_form = false;
  66. }
  67. ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
  68. }
  69. ?>

我们可以通过控制step参数直接绕过验证码:

1.png

进入数据库检查一下:

2.png

002 Medium

开发人员试图在会话中设置状态变量,并跟踪用户在提交数据之前是否成功地完成了验证码。因为状态变量位于客户端,因此攻击者也可以对其进行绕过操作。

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

从源码来看,后端逻辑仅仅多了一个passed_captcha变量的验证:

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

我们在 Brupsuite 加上即可:

3.png

到数据库验证一下:

4.png

003 High

有些开发时的废弃代码遗留在页面中,在网页上线后从未删除过。因此可以模拟这些代码中的变量,允许将无效值注入验证码字段。

您需要欺骗您的用户代理以及使用 CAPTCHA 值来绕过检查。

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

?>

我们在 Firefox 浏览器中按右键,选择 View Page Source 查看网页源代码:

5.png

我们根据注释和后端源码,在 Brupsuite 的 Repeater 中添加相关参数:

  • 由后端 PHP 源码可知,我们要在 POST data 中加一个参数g-recaptcha-response,它的值为hidd3n_valu3
  • 我们要把 Request Header 中的 User-Agent 的值改为:reCAPTCHA

6.png

去数据库检查一下:

7.png

References