zoukankan      html  css  js  c++  java
  • 1. DVWA_Brute Force

    1. DVWA_Brute Force

     

     

    1. low级别

    方法思路:

    服务器只是验证了参数Login是否被设置,没有任何的防爆破机制,且对参数username、password没有做任何过滤,存在明显的sql注入漏洞。可采用暴破或sql注入的方式绕过登录。

    代码审计(核心部分):

    <?php
    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>' );
       // z = x or y型,若x为真,则执行x,否则执行y。

    if($result && mysqli_num_rows( $result )==1) {
    // Get users details
    $row    = mysqli_fetch_assoc( $result );
    $avatar = $row["avatar"]; // 特别注意,sql注入时我们仅能返回的一条用户信息,否则这个位置会报错。
    // 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);
    }
    ?>
       
    /*   关于$GLOBALS["___mysqli_ston"]:
    MySQLConverter设置该全局变量为您的数据库连接对象。如果MySQLConverter发现系统的数据库连接请求mysql_connect(),它将mysqli_connect函数的执行结果包含到$GLOBALS[“___mysqli_ston”]中,如下所示:
    $GLOBALS[“___mysqli_ston”] = mysqli_connect($hostname,$username,$pwd);
    */

     

    1.1 方法一:SQL注入-万能密码

    初步思路:

    见到这样无验证码的登录框,条件反射该想到暴破和sql注入。先尝试下SQL注入,随手用户名框输入单引号',发现有如下报错:

    可发现是单引号字符型注入的特征,但疑惑的是除了单引号我在用户名框并没有输入任何其他内容,为啥多了串d41d8cd98f00b204e9800998ecf8427e,特征疑似MD5哈希值,解密后发现代表null,空字符串。故又一步印证源码里是单引号闭合,因为闭合后无任何内容,故返回一个代表NULL的MD5值,于是就有了注入点。

    payload:

    --------------  1 -----------------
    用户名:' or '1'='1
    -----------------------------------
    注入无效。
    通过对源码分析,我们在用户名框注入后原句中WHERE user = '' or '1'='1' AND password = '',相当于WHERE 0 or 1 and 0,也就是WHERE 0,自然不能成功登录。这里要说明两点:
    1.由于PHP内嵌的SQL语句里WHERE子句用了AND运算符,因为AND运算优先级比OR大,很多情况下会导致逻辑运算后的实际结果与我们预期的不同
    2.再者,WHERE子句里条件未赋值,默认就为NULL,而NULL作逻辑判断是默认是False。
    这样的话可能会想通过密码框注入,就能解决这个优先级造成的问题。但也失败了,查看源码发现后端获取密码后做了一个MD5加密的处理,并不是直接明文存储,故我们注入的语句也随之变为MD5值而失效。

    --------------- 2 -------------------
    用户名:' or 1=1; #
    -------------------------------------
    注入无效。
    由上一个分析可知,注入的点只能在用户名框处,随即想到用注释符来直接把后面的SQL语句注释掉,暴力的解决AND与OR运算符优先级的问题,但构造的注入语句' or 1=1; # 依然无效,查看源码后发现,虽然刚开始顺利通过if语句登录,但在接下来的提取用户图片等操作中,由于我们返回的是所有用户的信息,而源码中是仅从一个用户的返回信息中提取图片路径,这必然会导致错误,故转入else分支,登录失败。

    --------------- 3 ------------------
    用户名:' or 1=1 limit 0,1; #    
    ------------------------------------
    注入有效。
    由上一个分析可知,我们虽成功进入if语句,但在后续代码执行过程中因返回了全部的用户信息触发了错误,导致转入执行else语句而登陆失败。故可在构造的注入语句中加入limit来限制返回结果数,便可登录成功。

    --------------- 4 ------------------
    用户名:admin' or '1'='1          
    ------------------------------------
    注入有效。
    因为有了正确的用户名故不再受原SQL语句中的AND的优先级的影响,故我们在用户名框注入后原句中WHERE user = 'admin' or '1'='1' AND password = '',相当于WHERE 1 or 1 and 0,也就是WHERE 1,便可成功登录。

    ---------------- 5 ----------------
    用户名:' or '1'='1
    密码:letmein
    -----------------------------------
    注入有效。
    因为有了正确的密码,使得我们按原句AND优先运算也能得到预期的结果,即WHERE user = '' or '1'='1' AND password = '0d107d09f5bbe40cade3de5c71e9e9b7',相当于WHERE 0 or 1 and 1。但要注意,这种方法存在失败的可能,因为若是对方数据库中有多个用户是用该密码,则我们返回的值就是多个,又回到了第二个分析上,且这次还无法limit限制返回个数,因为密码框的输入后端均用MD5加密。

     

    1.2 方法二:暴破

    由于登录界面无验证码,服务器仅通过if(isset($_GET['Login']))验证是否设置了Login参数来接收和处理用户登录请求。

    故burpsuite代理 —> 拦截包 —> 爆破模块一条龙服务,成功暴破出正确的用户名密码。

     

     

     

    2. medium级别

    方法思路:

    相比Low级别的代码,Medium级别的代码主要增加了mysql_real_escape_string()函数,这个函数会对字符串中的特殊符号进行转义,会被转移的字符有NULL(ASCII 0), , , \, ', " 和 Control-Z,基本上能够抵御sql注入攻击,但不是绝对的,因为MySQL5.5.37以下版本(高版本未知,并未测试)如果设置编码为GBK,可能会被宽字节注入进而绕过mysql_real_escape_string()函数;同时,$pass做了MD5校验,杜绝了通过参数password进行sql注入的可能性。

    但是!!!依然没有加入有效的防爆破机制,虽然设置了sleep(2),登录失败时服务端会延迟两秒才继续执行当前脚本,但实在算不上一种有效防御方式,可忽略,同low级别一样暴破完事。

    代码审计(核心部分):

    <?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)) ? "" : ""));
    /*
    mysqli_real_escape_string()函数会转义字符串中的特殊字符,实现用户输入进行过滤,会被转义的特殊字符有NULL(ASCII 0), , , \, ', " 和 Control-Z。
    */
       
       // 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
           echo "<p>Welcome to the password protected area {$user}</p>";
           echo "<img src="{$avatar}" />";
      }
       else {
           // Login failed
           sleep( 2 );
           echo "<pre><br />Username and/or password incorrect.</pre>";
      }

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

     

     

    3. high级别

    方法思路:

    medium就加入了mysql_real_escape_string()函数防止sql注入,high级别一定更加巩固,故不考虑SQL注入。

    挂Bp后截包分析,可以看到参数user_token,说明服务器上存在Token机制。repeater进一步分析该Token机制,即可发现,每次提交请求后,不管是否成功登录,服务器返回的HTML主页里都会携带一个新的随机生成的Token,供客户下一次提交请求时携带使用。后端处理的脚本里会先验证客户端请求包里的Token值与本地session里存储的Token值是否一致,确定一致后才会执行后续的登录验证等操作,若不一致则采取其他措施,如此处网站设计者让服务器返回一个302重定向包,让我们重定向回到登录框页面。

    这种机制本来是用于防止CSRF攻击的,但其实对我们的暴破也间接增加了难度,因为需要我们在暴破时,让每次发送的请求包,都得携带着上一次请求服务器返回的包里新生成的那个Token。

    这种情况下,不再适用Bp的常规暴破手段(当然看网上也有人用Bp提供的py脚本模块进行暴破),为了锻炼自己写脚本的能力,所以决定自己写个py脚本进行暴破。于是有了下面这个简陋的暴破脚本:

    运行脚本,虽然线程少,暴破速度慢了点,但好歹还是成功爆破出用户名和密码:admin,password。

     

    代码审计(核心部分):

    # high.php的完整代码

    <?php

    if( isset( $_GET[ 'Login' ] ) ) {
       // Check Anti-CSRF token
       checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
    /*
    High级别的代码加入了Token机制,可以抵御CSRF攻击,同时也增加了爆破的难度;
    登录验证时提交了四个参数:username、password、Login以及user_token。
    每次服务器返回的登陆页面中都会包含一个随机的user_token的值,用户每次登录时都要将user_token一起提交。
    */
       
       // Sanitise username input
       $user = $_GET[ 'username' ];
       $user = stripslashes( $user );  
       // stripslashes()函数删除字符串中的反斜杠,用于过滤用户输入中的反斜杠。若有两个连续的反斜线,则只去掉一个,进一步抵御sql 注入。
       
       $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)) ? "" : ""));
    // 同medium级别一样,使用mysqli_real_escape_string()函数转义用户输入的特殊字符。
       
       // 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
           echo "<p>Welcome to the password protected area {$user}</p>";
           echo "<img src="{$avatar}" />";
      }
       else {
           // Login failed
           sleep( rand( 0, 3 ) );
           echo "<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();     // 每次服务器返回的登陆页面中都会包含一个随机的token。
    ?>
    # dvwaPage.inc.php中定义的几个需关注的函数

    function generateSessionToken() {  # Generate a brand new (CSRF) token
    if( isset( $_SESSION[ 'session_token' ] ) ) {
    destroySessionToken();
    }
       // 删除上一次用户请求生成的Token,并在接下来重新创建一个。
       
    $_SESSION[ 'session_token' ] = md5( uniqid() );
       // 生成一个随机Token并储存在服务端本地Session中。
       // Token实现方式:uniqid() 函数基于以微秒计的当前时间,生成一个随机且唯一的ID。
    }

    function tokenField() {  # Return a field for the (CSRF) token
    return "<input type='hidden' name='user_token' value='{$_SESSION[ 'session_token' ]}' />";
    }
    // 该函数用于将我们每次随机生成的Token插入index主页返回给客户端,这样每次服务器返回的登陆页面中才会包含一个随机的token。

    function checkToken( $user_token, $session_token, $returnURL ) {  # Validate the given (CSRF) token
    if( $user_token !== $session_token || !isset( $session_token ) ) {
    dvwaMessagePush( 'CSRF token is incorrect' );
    dvwaRedirect( $returnURL );
    }
    }
    // 该函数用于核查用户提交的Token(即$user_token)是否和我们服务器本地Session里存储的Token(即$session_token)一致。

     

     

    4. impossible级别

    安全设计分析:

    Impossible级别被视为不存在此类漏洞的安全代码。

    测试后发现,当服务器检测到某账户频繁的登录失败后,系统会将该账户锁定,暴破也就无法继续,这是一种比较可靠的防暴破机制。同时,后端PHP脚本采用了更加安全的PDO机制(PDOstatement对象)防御sql注入。

    代码审计后,将其安全设计流程简要总结如下图:

    代码审计(核心部分):

    <?php

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

    // Sanitise username input
    $user = $_POST[ '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 = $_POST[ '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 );

    // Default values
    $total_failed_login = 3;
    $lockout_time       = 15;
    $account_locked     = false;


       // 加入了PDO机制,通过使用PDO预处理对象来防范SQL注入攻击。
       // 对于用户每次提交的登录,首先查询该用户提交的账号总共进行登录尝试(登录失败)的次数。
    $data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );   //声明别名所绑定的变量的数据类型为字符串类型。
    $data->execute();
    $row = $data->fetch();

    // Check to see if the user has been locked out.
       // 当用户尝试登录次数超过限定的次数,判断是否到了解封时间,并可以继续下一次有效的登录提交
    if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
    //$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
           $timenow    = strtotime( "now" );
           $last_login = $row[ 'last_login' ];    
    $last_login = strtotime( $last_login );   // 将日期转化为unix时间戳
    $timeout    = strtotime( "+ {$lockout_time} minutes",$last_login );  // 用户账户解封时间        
           /*
           注意:此处源码不对,不能正常执行返回出$timeout的时间戳
           - 原句源码:$timeout   = strtotime( "{$last_login} + {$lockout_time} minutes" );
           - 正确格式:$b = strtotime("+7days", $a);//获取在以$a时间戳为基础的七天后的时间戳
           - 参照正确格式修改后:$timeout   = strtotime( "+ {$lockout_time} minutes",$last_login );
           */

    // Check to see if enough time has passed, if it hasn't locked the account
           // 判断该锁定状态的账号,是否到达预设的解封时间。
    if( $timenow > $timeout ){
    //echo "<br />";
    //echo "ok";
    $account_locked = false;   //源码这里也错了,源码是true。
    }else{
    //echo "<br />";
    //echo "on";
    $account_locked = true;
    }
    }

    // Check the database (if username matches the password)
       // 加入了PDO机制,通过使用PDO预处理对象来防范SQL注入攻击。
       // 此处才正式开始用户登录验证,即用户名和密码验证。
    $data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR);
    $data->bindParam( ':password', $pass, PDO::PARAM_STR );
    $data->execute();
    $row = $data->fetch();

    // If its a valid login...
       // 若用户登录验证成功(即提交的账号密码在数据库里存在),再进一步判断该账户是否处于锁定状态。
    if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
    // Get users details
           // 若该账户状态为未锁定,则该用户登录成功。
    $avatar       = $row[ 'avatar' ];
    $failed_login = $row[ 'failed_login' ];
    $last_login   = $row[ 'last_login' ];

    // Login successful
    $html .= "<p>Welcome to the password protected area <em>{$user}</em></p>";
    $html .= "<img src="{$avatar}" />";

    // Had the account been locked out since last login?
           // 若该用户之前尝试登录次数超过我们安全预设的限制次数,则在此处对用户提出一个警告。
    if( $failed_login >= $total_failed_login ) {
    $html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
    $html .= "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
    }

    // Reset bad login count
           // 用户一旦成功登录,则重置其尝试登录的次数为0
    $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
    }
    else {
    // Login failed
           // 若用户提交的账号密码在数据库里不存在、或该账户状态为锁定,则该用户登录失败。
           
           // 随机2-4秒延迟再执行当前脚本(impossible.php),即造成了2-4秒的响应延迟
           // 这样也可以一定程度上加大客户端进行暴破的代价(虽然和high级别一样鸡肋,但有总比没有好)
    sleep( rand( 2, 4 ) );

    // Give the user some feedback
    $html .= "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";

    // Update bad login count
           // 将该用户用户登录失败次数加1
    $data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
    }

    // Set the last login time
       // 最后设置该用户尝试登陆的最后时间。
    $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
    }

    // Generate Anti-CSRF token
    generateSessionToken();

    ?>

     

  • 相关阅读:
    function与感叹号
    js中的|| 与 &&
    [转] html屏蔽右键、禁止复制
    ExtJS 5.1 WINDOW BLUR
    ExtJS 网页版执行工具
    Excel 随即获得一组数据集中的数据
    [转] Spring Data JPA Tutorial: Pagination
    Set up eclipse for Ext js and spket IDE plugin
    ExtJS Alias, xtype and widget
    ExtJS stores
  • 原文地址:https://www.cnblogs.com/P201821460033/p/13861361.html
Copyright © 2011-2022 走看看