1. 从一次“被拒之门外”的测试说起
大家好,我是老张,一个在安全圈摸爬滚打了十来年的老兵。今天想和大家聊一个非常接地气,但又让很多新手朋友头疼的话题:如何与宝塔面板的WAF“过招”,特别是针对SQL注入的攻防博弈。你可能用过宝塔,它确实是个好东西,一键部署、可视化监控,让服务器管理变得像搭积木一样简单。但它的WAF(Web应用防火墙)有时候也挺“倔”,一个简单的1‘ and 1=1测试,可能就直接给你弹个拦截页面,让人瞬间懵圈。
这背后的场景,其实是我们安全测试或日常运维中经常遇到的。你想验证一下自己的网站有没有SQL注入漏洞,结果宝塔的防护规则先跳出来说“此路不通”。是网站真的固若金汤,还是我们的“敲门”方式不对?今天,我们就抛开那些复杂的理论,直接进入靶场实战。我会把自己在测试中踩过的坑、试出来的有效绕过方法,以及宝塔WAF可能的工作逻辑,掰开揉碎了讲给你听。目标很简单:不是教你去攻击别人的网站,而是让你彻底理解这套防护机制的“脾气”,从而更好地加固自己的阵地,或者在进行授权测试时,能更精准地发现问题所在。
我们这次的“战场”是一个经典的SQL注入靶场环境,但上面部署了宝塔面板的WAF防护。你会发现,很多教科书式的注入语句在这里直接失效。别急,这恰恰是乐趣的开始。攻防的本质就是博弈,是规则与绕过规则的智力游戏。通过一步步的试探、观察响应、调整Payload,我们不仅能学会绕过技巧,更能反向揣摩出WAF的过滤规则,这种“知其然更知其所以然”的体验,才是实战的精髓。
2. 初探战场:宝塔WAF的“第一道防线”
当我们把经典的1‘ or 1=1、1‘ and 1=1甚至1‘ || 1=1提交上去时,宝塔WAF几乎毫不犹豫地返回了拦截页面。这给我们传递了一个明确信号:它对一些常见的SQL逻辑运算符和关键字(如or, and, ||)建立了非常基础的“黑名单”匹配规则。 这种规则通常基于简单的字符串匹配或正则表达式,一旦发现请求参数中出现这些敏感词,就直接阻断请求。
这种防护思路直接有效,能拦住绝大部分自动化扫描工具和初级的攻击尝试。但它的弱点也在于“简单”。SQL语言是灵活的,同一种逻辑意图往往有多种表达方式。WAF的规则库不可能也无必要穷尽所有变形,这就给我们留下了迂回的空间。我的第一个突破口,是从一个看似不起眼的细节开始的:&&运算符。
在MySQL中,&&和and是等价的逻辑与运算符。当我尝试id=1 && 1=1时,发现请求居然通过了!但先别高兴太早,直接这么写,在HTTP请求中,&符号是参数分隔符,会被解析。所以我们需要对它进行URL编码。&编码后是%26,因此完整的Payload变成了:id=1 %26%26 1=1。这次,WAF放行了。
这个成功的绕过说明了什么?首先,宝塔WAF的规则库可能没有将&&或其编码形式%26%26纳入对“AND”逻辑的拦截规则中。其次,它提示我们一个非常重要的思路:编码是绕过WAF的常见且有效的手段之一。 不仅仅是URL编码,还有十六进制编码、Unicode编码等等,都可能用来混淆关键字符,欺骗过滤规则。
但这只是万里长征第一步。1 && 1=1只是一个布尔判断,真正的注入攻击需要获取数据,这就离不开select、from、where这些核心关键字。当我们尝试1 && (select 1)=1时,拦截再次降临。这说明,WAF的检测链条是分层的,它不仅仅看孤立的单词,还会看组合起来的“短语”或“语法片段”。
3. 见招拆招:绕过关键字过滤的“组合拳”
面对select关键字被拦截,我们有哪些武器?实战中我试了几种常见方法:
第一招:空白符干扰。 这是最简单粗暴但往往有效的一招。在select前面加一个空格,变成 select,Payload:1 && ( select 1)=1。成功了!为什么一个空格就能救命?这很可能是因为WAF的正则匹配规则是写死的,比如它匹配的是\bselect\b或类似的模式,旨在匹配完整的单词。当我们前置一个空格,可能破坏了它预设的匹配边界条件。除了普通空格( ),还可以尝试换行符(%0a)、制表符(%09)、注释符(/**/)等。这些空白符在SQL解析时通常被忽略,但对字符串匹配规则可能是致命的干扰。
第二招:函数名混淆。 当我们想用database()函数获取库名时,1 && (select database()='security')被拦截了。这说明database()这个函数名本身也在黑名单里。我尝试了注释符分割函数名:database/**/(),但失败了。接着,我尝试了注释符加换行:database--%0a()。注意,--是SQL的单行注释符,%0a是换行符的URL编码。这个Payload 1 && (select database--%0a()='security') 竟然通过了!
这里的原理需要理解一下:在MySQL中,--注释掉其后的所有内容直到行尾。我们写database--%0a(),意思是database后面开始注释,然后换行(%0a),下一行的()会被执行吗?实际上,在一些上下文和MySQL版本中,这种写法可能导致语法错误或行为不一致。但在这个靶场环境里,它绕过了。我推测,WAF的规则可能是匹配database()这个完整的字符串令牌。我们用--%0a将其从视觉和字符串匹配上“切断”了,变成了database和()两部分,而MySQL解释器在某种宽松模式下仍能将其识别为函数调用。这揭示了WAF的另一个潜在弱点:语法分析与字符串匹配的鸿沟。 WAF基于字符串或正则匹配,而数据库引擎基于语法解析。利用两者之间的差异,是高级绕过的核心。
第三招:寻找“替身”函数。 宝塔可能拦截了database()、user()等常见信息函数,但MySQL内置函数成百上千,它不可能全部封禁。我们的任务就是找到那些“漏网之鱼”。我测试了多个函数后,发现locate()函数可用。LOCATE(substr, str)函数返回子串substr在字符串str中第一次出现的位置,如果不存在则返回0。这简直是为布尔盲注量身定做的“神器”!我们可以构造这样的Payload来猜解数据库名第一个字符是否为‘s’:
id=1 && locate('s', (select database--%0a())) && 1=1
如果返回真(即页面正常),说明locate返回值大于0,数据库名第一个字符是‘s’。通过不断变换子串,从单个字符到多个字符,我们就能一步步“拼”出完整的数据库名security。
4. 深入腹地:应对复杂查询与GET/POST的差异
知道了库名,下一步就是猜表名、字段名和具体数据。按照常规思路,我们需要查询information_schema这个系统数据库。但当我们构造如下Payload时,又被拦截了:
id=1 && locate('e', (select table_name from information_schema.tables where table_schema='security' limit 0,1)) && 1=1
问题出在哪里?通过反复测试和减法排查(即逐步移除Payload中的部分进行测试),我发现**from关键字**似乎是触发拦截的关键。单独select可能没事,但select ... from ...这个组合模式被WAF盯上了。更有趣的现象出现了:同样的Payload,在GET请求(参数在URL中)中被拦截,但改成POST请求(参数在请求体中)提交时,竟然通过了!
这个差异非常值得深究。它可能揭示了宝塔WAF(或某些规则集)的检测策略:对GET和POST请求应用了不同的检测严格度或规则集。 通常,GET请求的参数直接暴露在URL中,更易被自动化工具扫描,因此WAF可能会对GET参数施加更严格的检查。而POST请求体相对“隐蔽”,规则可能稍松。这给我们一个实用提示:在测试时,如果GET型注入受阻,不妨尝试将参数改为POST方式提交,或许能打开新局面。
那么,对于GET请求,如何绕过from的拦截呢?我们无法在盲注中完全不用from。但我们可以利用之前“空白符干扰”和“编码变形”的思路进行组合尝试。例如,对from进行各种编码变形(如双URL编码、十六进制编码),或者在from前后插入非常规空白符(如%0b垂直制表符)。另一个思路是,利用MySQL的“无列名”注入或子查询技巧,有时可以避免直接使用from information_schema。不过,在我们的靶场案例中,既然POST方式可行,我们可以直接切换到POST注入点(例如靶场的Less-11)继续测试,这更高效。
在POST请求中,我们顺利猜解出第一个表名为emails,第一个字段为id。但在最终读取数据时,又遇到了新麻烦:
1 && locate('1', (select id from emails limit 0,1)) && 1=1 被拦截。
1 && (select locate('1',id) from emails limit 0,1) && 1=1 也被拦截。
看来,select ... from ...这个结构在数据提取阶段依然被重点关照。我尝试了加注释换行:1 && (select--%0alocate('1',id) from emails limit 0,1) && 1=1,还是不行。最后,在select关键字前再加一个空格,成功了:1 && ( select--%0alocate('1',id) from emails limit 0,1) && 1=1。这再次印证了,空格这个最简单的字符,在对抗基于模式匹配的WAF时,常常有奇效。它可能不断调整我们的策略,从单一技巧到组合拳,从硬碰硬到迂回穿插。
5. 攻防背后的思考:WAF规则逻辑与防御建议
通过这一系列的实战试探,我们可以尝试反向勾勒出宝塔面板WAF(或其中某套规则)的一些过滤逻辑:
- 基于黑名单的关键字/词法匹配:这是第一层,也是最基础的防护。它对
or,and,select,from,union,database()等明显的关键字和函数名进行直接匹配拦截。 - 对特定语法结构的检测:例如
select ... from ...这种组合。单独的select可能放过,但加上from形成查询结构就触发规则。这说明它有一定的“上下文”感知能力,不止看单词,还看“短语”。 - 对标准化输入进行检测:我们的绕过大量使用了URL编码、空白符、注释符。这说明WAF的规则可能是在对输入进行一定程度的规范化(如URL解码)之前或之后进行匹配的。如果它在解码前匹配,那么
%26%26就能绕过and的规则。如果它对空白符的处理不严谨,那么加空格就能干扰匹配。 - 请求方法差异:GET和POST请求的检测强度可能不同,这可能是出于性能和误报率的权衡。GET请求更频繁,参数暴露,检查更严;POST请求体检查成本高,规则可能略有不同。
那么,作为防御方(服务器运维人员),我们能从这场博弈中学到什么?
- 不要迷信单一防护:WAF是重要的安全层,但绝非银弹。它主要基于规则,总有被绕过的可能。真正的安全应该建立在“最小权限原则”和“纵深防御”之上。 确保应用程序本身没有SQL注入漏洞(使用参数化查询/预编译语句),才是治本之策。
- 理解WAF的告警:当WAF告警时,不要只看“拦截”这个结果。要去分析告警日志,看它匹配了哪条规则,攻击载荷是什么。这能帮助你理解正在遭受何种攻击,甚至发现WAF规则本身的盲点。
- 自定义规则与学习模式:宝塔等WAF通常允许用户自定义规则。如果你发现某种绕过手法对你的站点有效,可以针对性地添加更严格的规则。同时,可以开启WAF的学习模式(如果有),让它在一段时间内观察正常流量,减少对合法请求的误报。
- 定期更新与测试:WAF的规则库需要定期更新,以应对新出现的攻击手法。同时,可以定期对自己的网站进行安全的渗透测试(或在授权下请专业人员进行),主动发现WAF可能遗漏的问题。
6. 实战心得:思维比工具更重要
回顾整个绕过过程,我最大的感触是:自动化工具固然高效,但手动的、基于理解的测试往往能发现更隐蔽的问题。 一开始,工具可能被WAF轻易挡住,报告“无漏洞”。但通过我们一步步的手工构造、观察响应、分析拦截点、调整Payload,最终找到了多条可行的路径。
这个过程锻炼的是一种“对抗性思维”。你需要站在防御者(WAF)的角度去思考:它会检查什么?怎么检查?它的检查逻辑可能存在哪些盲区?然后,你再作为攻击者,去尝试利用这些盲区。这种思维不仅适用于SQL注入绕过,也适用于其他类型的WAF绕过、漏洞挖掘。
最后必须再次强调:所有技术讨论仅限用于授权测试、安全研究和自身系统加固。 未经授权对他人系统进行测试是违法行为。希望这篇文章能帮你更深入地理解Web应用防火墙的工作机制和SQL注入攻防的实质,在保护自己系统的道路上走得更稳、更远。安全是一场持续的动态博弈,保持学习,保持敬畏。
201



被折叠的 条评论
为什么被折叠?



