1. 项目概述:为什么“绕过”是SQL注入的核心进阶技能
如果你已经接触过WEB安全,或者玩过一些CTF靶场,那么“SQL注入”这个词对你来说肯定不陌生。简单来说,就是攻击者通过在用户输入中插入恶意的SQL代码,欺骗后端数据库执行非预期的命令。新手入门时,往往是从‘ or ‘1’=‘1这种万能密码开始的。但现实中的安全防护,尤其是WAF(Web应用防火墙)和开发者自写的过滤逻辑,早已不是这种简单字符串匹配就能对付的了。
这就引出了我们今天要深入探讨的核心:SQL注入的绕过手法。这绝不是为了炫技,而是安全测试人员、渗透测试工程师乃至开发者在构建更健壮防御时必须掌握的一课。当你发现一个输入点存在注入嫌疑,但常规的union select、and 1=1直接被拦截或过滤时,真正的较量才刚刚开始。理解绕过,就是理解防御方的思路,从而找到其思维和实现上的盲点。无论是CTFshow、DVWA、Pikachu这些经典靶场,还是真实世界的渗透测试,绕过手法的精熟程度,直接决定了你的测试深度和成功率。
本专题旨在系统性地拆解SQL注入中常见的绕过技术。我们不只讲“怎么做”,更要深挖“为什么能这么做”,以及在实际场景中如何组合运用这些技巧。从基础的编码绕过,到利用数据库特性、逻辑缺陷,再到高阶的混淆变形,我们将逐一剖析,并提供可直接复现的测试用例和思考逻辑。
2. 绕过手法核心原理与分类框架
在开始具体手法之前,我们必须建立一个清晰的认知框架。所有的绕过手法,本质上都是基于一个核心矛盾:应用程序(或WAF)的过滤/检测逻辑与数据库SQL解析引擎的逻辑存在差异。
防御方在代码层或网络层对输入进行清洗和检查,但最终这个输入要拼接成SQL语句交给数据库(如MySQL、MSSQL、PostgreSQL)去解析执行。如果我们的恶意载荷能“骗过”前者的检查,同时又能被后者正确理解,那么绕过就成功了。
基于这个原理,我们可以将绕过手法分为以下几大类:
2.1 基于编码与特殊字符的绕过这是最基础也最常用的一层。防御方可能只过滤了某些关键词的空格或特定写法,但我们可以用其“等价形式”来替代。
- 原理:利用URL编码、HTML编码、数据库字符集等多种编码方式,或使用特殊字符作为分隔符,改变payload的“面貌”,使其对检测引擎不可读,但对数据库解析器依然有效。
- 关键点:需要了解不同环节(浏览器、服务器中间件、应用代码、数据库)对数据的解码顺序和规则。
2.2 基于SQL语法特性的绕过数据库为了兼容性和灵活性,其SQL语法解析往往比我们想象中更“宽松”。这留下了许多可乘之机。
- 原理:利用数据库对大小写不敏感、注释符的灵活使用、内联注释、字符串拼接、特殊运算符等特性,构造出语法正确但形态异常的SQL语句。
- 关键点:需要熟悉目标数据库的具体方言和特性。MySQL、MSSQL、Oracle的绕过技巧常有不同。
2.3 基于逻辑与协议层的绕过这类手法跳出了单纯的payload变形,从请求的整体逻辑和传输协议上寻找突破口。
- 原理:例如利用HTTP参数污染(HPP)、请求方法转换(GET/POST互换)、多部分表单数据(Multipart)编码、以及慢速攻击等,干扰WAF的检测逻辑,使其无法正确提取或关联待检测的参数。
- 关键点:需要对HTTP协议和WAF的工作原理有较深理解。
2.4 混淆与变形技术这是绕过现代AI或语义分析型WAF的进阶手段。
- 原理:通过插入大量无意义的空白、注释、换行,或者将关键字拆散(如
SEL/**/ECT),甚至使用动态查询执行(如MySQL的PREPARE和EXECUTE语句),使得payload在静态分析下难以识别。 - 关键点:目标是增加payload的熵,破坏基于正则表达式或简单模式匹配的检测。
注意:在实际测试中,很少单独使用某一种手法。一个成功的绕过payload通常是多种技术的组合拳。例如,可能先通过HPP将参数拆散,然后对关键参数进行URL双重编码,最后在SQL语句中使用内联注释替换空格。
3. 编码与特殊字符绕过实战详解
让我们从最实操的部分开始。假设我们面对一个简单的登录框,后端SQL语句可能是:SELECT * FROM users WHERE username = ‘$user‘ AND password = ‘$pass‘
防御方在$user处过滤了空格和union、select等关键字。
3.1 URL编码绕过这是网络传输中最天然的“伪装”。浏览器会自动对URL中的特殊字符进行编码,服务器端通常会解码一次。但如果WAF检测在解码前,而应用代码在解码后拼接SQL,就存在绕过可能。
- 常规注入:
admin‘ union select 1,2,3-- - 过滤后:
union和空格被过滤。 - 绕过尝试:
- 单次URL编码:将空格 编码为
%20,单引号‘编码为%27。admin%27%20union%20select%201,2,3--%20但聪明的WAF会统一解码后再检测,所以可能依然被拦。 - 双重URL编码:这是关键。对
%20再进行编码,%变成%25,于是%20变成了%2520。admin%2527%2520union%2520select%25201,2,3--%2520如果WAF只做一次解码,它看到的是admin%27%20union...,其中的%27和%20可能不会被识别为关键词。而应用服务器(如Apache/PHP)可能会进行第二次解码,还原出admin‘ union select...,成功注入。 - 实战心得:双重编码在针对老旧WAF或自定义过滤逻辑时非常有效。使用Burp Suite的Decoder模块可以轻松进行多次编码解码操作。
- 单次URL编码:将空格 编码为
3.2 十六进制与Unicode编码绕过主要用于处理关键字和字符串值。
- 十六进制绕过:MySQL中,字符串可以用十六进制表示,
0x开头。例如,select可以写成selselectect,然后通过十六进制替换中间被过滤的select?不,更直接的是用十六进制表示整个查询值。- 假设我们要注入的字段值是
admin,可以写成0x61646D696E(admin的十六进制)。在注入时:admin‘ and password=0x...可以绕过对字符串‘admin‘的过滤检查。 - 更高级的,可以将整个SQL语句部分进行十六进制编码,配合
EXECUTE执行。
- 假设我们要注入的字段值是
- Unicode编码:在某些上下文(如JSON、宽字节注入环境)中有效。例如,单引号
‘的Unicode编码是%u0027或%u02b9等变体。著名的“宽字节注入”就是利用了GBK等字符集下,%df%27(%df和%27)会被数据库解析为一个汉字字符,从而“吃掉”反斜杠转义符\(%5c)的原理。虽然这不完全是Unicode,但属于字符集编码层面的绕过。- 关键点:需要准确知道目标应用处理输入时使用的字符集。
3.3 注释符与空白符替代空格是SQL注入中最常用的分隔符,也是最常被过滤的。我们需要找替代品。
- MySQL注释符:
/**/是多行注释,但在注入中常被当作可插入的“空白”使用。/*! ... */是内联注释,其中的代码在MySQL中会被执行。- 空格绕过:
union/**/select/**/1,2,3 - 内联注释妙用:
/*!union*/ select/*!1,2,3*/。某些WAF的规则可能不会匹配/*!和*/中间的内容。
- 空格绕过:
- 其他空白符:
%09(Tab)%0a(换行)%0b(垂直制表符)%0c(换页)%0d(回车)%a0(不换行空格)- 这些符号在SQL解析时大多被视为空白,但一些简单的正则表达式
\sunion\sselect可能只匹配标准的空格或%20。
- 括号绕过:在特定语法中,括号可以用于分隔而不需要空格。例如,在
select之后直接跟括号包裹的字段。union(select(1),(2),(3))from(users)这在一些极端的过滤环境下可能有效。
实操心得:不要只尝试一种替代。用Burp Intruder加载一个包含各种空白符和注释符的字典,对疑似注入点的参数进行模糊测试(Fuzzing),观察哪些符号能被系统接受且返回正常,这是快速探测过滤规则的最佳实践。
4. 利用SQL语法特性进行高级绕过
当编码变形效果有限时,我们需要更深入地利用数据库引擎自身的“宽容度”。
4.1 大小写与关键字拆分
- 大小写绕过:最简单的,
UnIoN SeLeCT。对于不区分大小写的数据库(如MySQL默认)或不进行大小写归一化检测的WAF有效。 - 关键字拆分:用注释或空白将关键词拆开。
uni/**/on sel/**/ectu%0bnion s%0belect(利用换行)- 更隐晦的:
U/*!00000NION*/ SE/*!00000LECT*/,利用内联注释中的特定数字(MySQL中,/*!50001 ... */表示在MySQL版本>=5.00.01时执行),这些数字和注释符本身构成了干扰。
4.2 字符串拼接与等价函数替换
- 字符串拼接:如果
union和select被整体过滤,尝试用拼接构造它们。- MySQL:
concat(‘u‘, ‘nion‘)得到union。但注意,这通常需要在已经可以执行SQL函数的环境下,用于构造查询内容而非关键字本身。对于关键字,更常用的是CHAR()函数。 CHAR()函数:将ASCII码转换为字符。union可以写成:CHAR(117, 110, 105, 111, 110)即u=117, n=110, i=105, o=111, n=110那么注入语句可能变成:admin‘ and 1=1 and (CHAR(117, 110, 105, 111, 110) select ...)。这能绕过纯文本关键字匹配。
- MySQL:
- 等价函数/运算符替换:
and 1=1被过滤?试试and 1 like 1、and 1 regexp 1、and true、and 1 in (1)。or ‘1‘=‘1‘被过滤?试试or ‘1‘ like ‘1‘、or ‘1‘ between ‘0‘ and ‘2‘。substring()被过滤?试试mid()、substr()。
*4.3 内联注释(/*! .../)的深度利用MySQL的内联注释是绕过利器,因为它对数据库是“可见”的代码,但对许多文本过滤器却是“注释”。
- 版本特异性:
/*!50001 select * from users */只有在MySQL版本大于等于5.00.01时才会执行。这本身可以用于探测数据库版本,也可以作为干扰。 - 包裹整个语句:可以将整个恶意SQL段包裹起来,特别是当WAF以分号
;或--作为语句结束标志进行分段检测时。id=1/*!union/*!select/*!1,2,3*/;*/这种嵌套和分散的注释可能会破坏检测引擎的语法分析。
4.4 利用数据库解析特性:非常规语法
- 浮点数表示法:在MySQL中,
1‘1会被解释为浮点数1.1。这可以用于绕过对整数和字符串的简单类型判断。例如id=1‘1‘可能被解析为id=1.1,如果字段是数值型,可能会触发类型转换错误,从而用于报错注入。 - 科学计数法:
1e0表示数字1,可以用于混淆。 - 反引号与引号:MySQL中可以使用反引号```来包裹标识符(如表名、字段名),当单引号被过滤时,可以尝试在特定上下文使用反引号,但这通常用于字段名而非字符串值。
5. 逻辑层与协议层绕过手法剖析
这类手法跳出了SQL语句本身的变形,从更上层寻找WAF或应用逻辑的漏洞。
5.1 HTTP参数污染(HPP)原理:当同一个参数名在HTTP请求中出现多次时,不同的服务器端技术(PHP、ASP.NET、J2EE等)解析获取的参数值可能不同。WAF可能只检测第一个或最后一个值,而应用程序实际使用的却是另一个。
- 场景:注入点在参数
id上。 - 正常请求:
GET /page.php?id=1‘ and ‘1‘=‘1 - HPP攻击请求:
GET /page.php?id=1‘ and ‘1‘=‘1&id=1- PHP/Apache环境下,通常取最后一个
id值,即1,所以WAF检测第一个id(恶意),但应用使用第二个id(正常),攻击失败?不,这里需要策略。 - 更有效的方式:
GET /page.php?id=1&id=1‘ and ‘1‘=‘1- 某些WAF可能只检查第一个
id=1(干净),就放行了整个请求。 - 而PHP在
$_GET[‘id‘]时,如果收到多个同名参数,行为取决于配置,可能是第一个,也可能是最后一个,或者是数组。在$_REQUEST中,顺序可能受request_order影响。通过模糊测试确定服务器行为是关键。
- 某些WAF可能只检查第一个
- PHP/Apache环境下,通常取最后一个
- 实战技巧:使用Burp Suite的“Param Miner”等扩展可以帮助自动探测HPP的可能性。在测试时,要同时观察WAF的拦截日志和应用程序的实际响应,判断哪个参数值真正生效。
5.2 请求方法转换与参数隐藏
- GET/POST互换:有些应用同时接受GET和POST方式传递参数,但WAF可能只严格检查了其中一种(通常是GET)。如果一个注入点在GET请求中被拦截,尝试将相同的参数和值放到POST Body中提交。
- 参数置于非常规位置:
- Cookie:有些应用会将Cookie中的某些值用于数据库查询(如会话标识、用户偏好)。WAF对Cookie的检测可能弱于对Query String或Body的检测。
- HTTP Headers:如
X-Forwarded-For,User-Agent,Referer。这些头部信息有时会被记录到数据库,如果过滤不严,就可能成为注入点。测试时,可以尝试在这些头部中插入探测载荷。
- JSON/XML参数注入:现代API常使用JSON或XML传输数据。WAF可能配置了针对这些格式的解析器,但如果解析配置不当,攻击者可以通过破坏JSON/XML结构(如未闭合的引号、多余的逗号),将注入载荷“逃逸”到被解析的字符串中。例如,提交
{"id”: “1‘ and ‘1‘=‘1"},如果后端直接拼接“1‘ and ‘1‘=‘1”到SQL中,且WAF未深入解析JSON值,则可能绕过。
5.3 分块传输编码(Chunked Transfer Encoding)这是一种HTTP协议特性,允许客户端将请求体分块发送。有些WAF为了性能,可能不会完整地重组和检查分块传输的请求体,而是直接拦截或放行。通过构造特殊的分块数据,可以将恶意payload隐藏在某个分块中,以期绕过检测。实施此攻击需要手动构造复杂的HTTP请求,通常借助Burp Suite的插件(如“Chunked”插件)来完成。
5.4 慢速攻击(Slow HTTP Attack)严格来说,这不完全是SQL注入绕过,而是一种消耗WAF资源的攻击。通过极慢的速度发送HTTP请求(例如,每次只发送一个字节,间隔很长),可能使WAF的连接池耗尽或超时,从而让后续的恶意请求在WAF“疲惫”或超时后直接到达应用服务器。这种手法更适用于混合攻击的场景。
6. 混淆变形与自动化绕过思路
面对越来越智能的WAF,手动构造一个复杂的绕过payload效率低下。我们需要掌握一些系统性的混淆方法和自动化工具的思路。
6.1 注释与空白混淆这是最基础的混淆,目的是增加payload的“噪音”,干扰基于正则表达式的检测。
- 随机插入注释:在SQL语句的各个位置随机插入
/**/、/*!12345*/等。u/*random*/ni/*xyz*/on se/*aaa*/le/*bbb*/ct 1,2,3 - 混合使用多种空白符:不要只用
%20或/**/,混合使用%09,%0a,%0b,%0c,%0d,%a0。 - 换行分割:将一条完整的SQL语句分成多行发送。某些WAF可能按行检测,而数据库的SQL解析器会忽略换行符。
admin‘ and 1=1 union select database(),2,3 --
6.2 语法等价替换与嵌套
- 使用不常见的语法结构:
- 用
SELECT * FROM (SELECT 1)a代替SELECT 1。 - 用
CASE WHEN ... THEN ... END代替if或and条件判断。 - 用
LIKE ‘%‘代替=‘%‘。
- 用
- 嵌套查询与别名:大量使用子查询和别名,增加语句复杂度。
SELECT (SELECT column_name FROM (SELECT 1)a)
6.3 利用数据库预编译语句的动态执行这是非常高级的绕过方式,适用于已经有一定注入能力(能执行多语句或特定函数)的环境。
- MySQL PREPARE/EXECUTE:
在这个例子中,恶意的SET @sql = CONCAT(‘S‘,‘ELECT * FROM users WHERE id = ‘, ‘1‘); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;SELECT语句是通过CONCAT函数在运行时拼接的,然后通过PREPARE准备,EXECUTE执行。在静态分析或简单匹配下,CONCAT的参数是分散的字符串,极难被检测为SQL注入。
6.4 自动化工具与模糊测试手动尝试所有组合是不现实的。安全研究人员通常会借助工具:
- SQLMap Tamper脚本:SQLMap内置了大量tamper脚本(如
space2comment.py,between.py,charencode.py),它们能自动对payload进行各种编码和混淆。你可以组合使用多个tamper脚本,例如:sqlmap -u “target.com/page?id=1“ --tamper=space2comment,charencode --level=3 --risk=3 - 自定义模糊测试(Fuzzing):使用Burp Suite Intruder或自定义Python脚本,针对一个已知的注入点,用包含各种绕过技巧的字典(如不同的编码、空白符、注释组合、语法变体)去轮询测试,观察哪些payload能返回正常的响应(即未被过滤或拦截),从而反推出过滤规则。
- WAF指纹识别与规则推测:首先识别目标使用的WAF(如Cloudflare, ModSecurity, 阿里云盾等)。然后通过发送一些边缘case的payload(如
union select、union all select、union distinct select),观察其拦截响应,可以推测WAF规则的大致模式(是匹配union+select的组合,还是匹配union后跟空格和select的特定模式),从而有针对性地设计绕过。
7. 实战案例串联与综合绕过思路
让我们通过一个虚构但综合的场景,将上述手法串联起来。假设目标URL为:https://vuln.com/news.php?id=1,存在数字型注入,但部署了某品牌WAF。
7.1 初步探测与规则分析
- 正常访问:
id=1返回正常新闻。 - 基础探测:
id=1 and 1=1被WAF拦截。返回403或特定拦截页面。 - 尝试编码:
id=1%20and%201=1同样被拦截。说明WAF解码后检测。 - 尝试注释绕过空格:
id=1/**/and/**/1=1成功!页面正常。说明WAF对/**/的识别可能有问题,或者其规则是匹配and后面紧跟空格和数字的模式。 - 探测关键字过滤:
id=1/**/union/**/select/**/1,2,3被拦截。说明union select作为组合被识别。
7.2 逐步绕过
- 拆分union select:
id=1/**/uni/**/on/**/sel/**/ect/**/1,2,3可能成功,也可能失败。取决于WAF是否有关键字碎片检测。 - 尝试大小写混合:
id=1/**/UnIoN/**/SeLeCt/**/1,2,3被拦截。说明WAF做了大小写归一化。 - 尝试内联注释:
id=1/*!union*//*!select*/1,2,3被拦截?可能/*!和*/本身被列入特征。 - 尝试HPP:
id=1&id=1/*!union*//*!select*/1,2,3。观察响应。如果WAF检查第一个id=1(干净)就放行,且应用使用第二个id值,则可能绕过。假设这里应用使用了第一个id值,攻击无效。 - 换一种HPP方式:
id=1/*!union*//*!select*/1,2,3&id=1。这次将恶意负载放在前面。如果WAF只检查最后一个参数,则可能绕过。需要测试。 - 结合编码与HPP:
id=1%252f%252a%252a%252funion%252f%252a%252a%252fselect%252f%252a%252a%252f1,2,3&id=1。这里对/**/进行了双重URL编码(/是%2f,*是%2a)。%252f%252a%252a%252f解码一次后是%2f%2a%2a%2f,即/**/。WAF如果只解码一次,看到的是%2f%2a%2a%2f,可能不将其识别为注释符。而应用服务器解码两次后,得到/**/,被数据库解析为空格。 - 使用等价函数与CHAR():如果以上都失败,考虑终极手段,用
CHAR()构造关键字。但前提是已经能执行函数。我们可以先测试函数是否可用:id=1/**/and/**/substring(‘abc‘,1,1)=‘a‘。如果正常,说明函数调用未被过滤。 然后尝试构造:id=1/**/and/**/(CHAR(117,110,105,111,110)/**/CHAR(115,101,108,101,99,116)/**/1,2,3)。这相当于union select 1,2,3。这个payload非常隐蔽,但构造复杂。
7.3 自动化工具辅助在手动找到一些有效载荷(如/**/可替代空格)后,可以借助SQLMap的tamper脚本进行深度利用。
- 使用
space2comment.py脚本将空格转为/**/。 - 结合
randomcase.py对字母进行随机大小写变换。 - 使用
charencode.py进行URL编码。sqlmap -u “https://vuln.com/news.php?id=1“ --tamper=space2comment,randomcase,charencode --level=5 --risk=3 --batchSQLMap会自动组合这些变形技术,尝试绕过防护。
8. 防御视角与总结反思
作为一名渗透测试人员,精通绕过的目的是为了更好地防御。从防御方来看,如何构建难以绕过的SQL注入防护呢?
- 根本措施:使用参数化查询(预编译语句)。这是唯一从根源上杜绝SQL注入的方法。将用户输入始终作为参数传递,而非SQL语句的一部分。无论输入如何变形,都无法改变查询结构。
- 严格的输入验证与规范化:在参数化查询的基础上,实施白名单验证。对于已知类型(如数字、特定枚举值)进行严格匹配。对字符串进行规范化处理,统一字符编码,过滤或转义所有非预期字符。
- 最小权限原则:数据库连接账户应仅具有应用所需的最小权限(如只有SELECT,无DROP、UPDATE等)。这样即使注入成功,危害也有限。
- WAF的深度防御:WAF规则需要不断更新,并采用多层检测机制:
- 语义分析:不仅仅匹配关键字,而是尝试理解SQL语句的语法结构,识别异常。
- 规范化解码:对输入进行多次、多种编码的解码,直到最底层的形式,再进行检测。
- 行为分析:监测异常的数据库查询模式,如短时间内大量报错、异常的
UNION查询、尝试访问information_schema等系统表。
- 代码审计与安全测试:定期进行白盒代码审计和黑盒渗透测试,模拟攻击者的绕过手法,主动发现漏洞。
个人实操体会:绕过的过程就像一场攻防博弈。没有永远有效的绕过技巧,也没有绝对安全的防御。最重要的不是记住所有payload,而是掌握其背后的思维模式:寻找差异与盲点。差异存在于编码、解析、逻辑的各个环节;盲点存在于规则覆盖不到的地方。在测试中,保持耐心,系统性地从简单到复杂进行尝试,并善用工具进行模糊测试和辅助,往往能发现意想不到的突破口。同时,时刻从防御角度思考,才能让你的安全技能更加全面和深入。真正的安全,始于对攻击的深刻理解。