☰
文件上传绕过实战:从日志包含到RCE的完整攻击链分析
2026/10/21 15:59:45 网站建设 项目流程

1. 从"能上传"到"能利用":这道题真正考的是什么

先说结论:这道CTF题作业2最坑人的地方,恰恰在于它看起来"太简单了"。很多人一看到文件上传,脑子里立刻蹦出三个字——"马"呢?然后就开始往表单里塞一句话木马、改Content-Type、试后缀名,折腾半天却拿不到flag。

我最初接手这道题时也犯了同样的毛病。花了一晚上把常见的绕过手法轮了一遍:大小写、双写后缀、加空格、加点、%00截断、图片马……结果全部吃瘪。后来冷静下来重新读题,才发现自己完全走偏了——题目描述里根本没说"上传一句话木马",而是给了几个看起来八竿子打不着的线索词,比如"日志""session""临时目录"。这几个词一出来,整道题的思路就该立刻切换。

这道题的核心考点其实是一个在网络攻防里经常被忽略、但真实业务里非常致命的问题:当上传点本身"百毒不侵"时,能不能换一个角度,通过文件上传去污染服务端其他组件,从而间接达成RCE(远程代码执行)?换句话说,上传点只是入口,真正的战场在服务端如何处理我们传上去的内容、如何存储、如何回显、以及哪些"本来不该被当成代码"的东西被当成了代码。

这篇文章我会把这道"CTF 文件上传与其它作业2"从读题、探测、绕过、利用到复盘,完整拆给你看。不仅还原每一步操作,还会把每步背后的判断逻辑讲清楚。文章面向的是有一定Web安全基础、想从"会套模板"进阶到"会分析"的读者;如果你是纯新手,前两章的思路部分请多看几遍,比直接抄exp有价值得多。

2. 第一轮探测:先别急着上传,把"敌人"的长相摸清楚

2.1 从题目描述和附件里提取线索

大多数CTF文件上传题,题目描述都会光明正大地漏点东西。这道题也不例外,只是因为夹杂了"和 其他 作业2"这种字样,一开始让人摸不着头脑。我建议把所有线索词列出来,不要放过任何一个:

  • 关键字"文件上传":说明入口是上传功能,大概率存在一个网页表单。
  • "其他作业2":这个看起来很乱的词,实际暗示题目不止一个考点,可能存在需要结合的"其他"模块,比如任意文件读取、日志包含、目录穿越等。
  • 关键词"日志"、"session"、"临时目录":这些属于典型的服务端"隐藏组件"线索。在真实渗透里,攻击面往往不在主业务逻辑上,而在这类辅助组件上。

如果你拿到的题目环境还附带了源码文件(很多这类作业会附带一个zip),那更要优先看源码,而不是急着开Burp。源码能告诉你:上传目录在哪、过滤规则是什么、文件重命名规则、是否存在白名单、有没有include函数、日志路径是否可控。没有源码的话,就用第二步的纯黑盒探测。

2.2 黑盒信息收集:确认Web容器、语言与上传逻辑

在没有源码的情况下,我的探测顺序是这样:

  1. 打开上传页面,F12看表单结构,记录name、action、enctype。
  2. 上传一个无害的txt文件,观察返回信息:是否提示后缀不允许、大小超限、还是"上传成功"。
  3. 访问上传后的文件路径,看能否直接解析。
  4. 故意构造一些畸形请求,观察响应头的Server字段、X-Powered-By字段。

这一步我拿到了关键信息:环境是PHP + Nginx,上传的txt能访问,但无法通过直接访问的方式触发代码执行。再加上题目线索里出现"日志"和"session",我基本可以锁定攻击链路是:上传文件 -> 文件内容被写入某个服务端可读取的文本文件(日志/session/临时文件)-> 通过文件包含或条件竞争让该文本被PHP解析 -> RCE。

这里有个新手容易忽略的原则:上传点只是投递通道,能不能执行取决于服务端最终怎么用这个文件。判断一个上传点是否存在可利用价值,不要只盯着"我能不能传php",而是要看"我能不能把恶意内容送到服务端某个会被include或eval的位置"。

2.3 判断过滤规则:黑白名单与内容校验的分水岭

上传题说到底是"过滤规则"与"绕过技巧"的博弈。过滤规则通常分三类:

  • 后缀名黑名单:拦截php、php3、phtml等,但可能漏掉pht、php5、Php、phP。
  • 后缀名白名单:只允许jpg、png、gif,这种一般得走文件包含或解析漏洞。
  • 内容校验:检查文件头(如GIF89a)、MIME类型、甚至调用getimagesize。

我测试的结果是:后端采用白名单,只允许jpg、png、gif,同时文件头也校验了。这意味着常规上传可执行文件这条路彻底堵死。但注意,它没有ban掉"#"、"?"、"空格"这些在URL解析里可能产生歧义的字符——这就是后文要利用的缝隙。

提示:判断出白名单后不要沮丧,这是好事。白名单往往意味着过滤逻辑简单,攻击面可以转向"服务端如何处理白名单文件",比如图片马结合文件包含、上传目录解析规则、POST包里的额外字段。

3. 上传绕过实验:把"白名单"逼到极限

3.1 常规后缀绕过逐一测试与其失效原因

很多教程喜欢列一串后缀绕过路径:php、php2、php3、php4、php5、php7、pht、phtml、phar、phps……但我在本题实测,全部被拦截。原因很简单:服务端的拦截方式是后缀名白名单,不在名单内的一律拒绝,黑名单那套绕过手法自然无效。

另外我还试了双写扩展名(pphphp、.php.jpg)、加空格、加点、加::data=流、%00截断(PHP 5.x的老玩法),也都无效。原因分两类:

  • 加空格/加点/双写,在白名单判断下会被一刀切:最终的后缀解析不是jpg/png/gif就拒绝。
  • %00截断需要Web容器和PHP版本都够老,现代环境默认安全模式开启,废弃了这种玩法。

这一轮虽然全失败,但收获很大:我确认了过滤是基于"最后的后缀名是否在白名单内"的严格判断。从攻防角度看,这就意味着上传环节本身没有解析漏洞可钻,必须寻找"上传之外的辅助点"。

3.2 图片马与文件头校验的对抗

既然后缀绕不过,那就传jpg,内容写PHP代码,走"文件包含"路线。这类"图片马"的核心是:文件的扩展名和MIME校验能通过,但内容被人为塞了PHP代码。 因为服务端只是用getimagesize或文件头检查,并不会真正去验证像素数据,所以代码可以藏在图片末尾。

我构造了最经典的方式:先写一个1x1像素的PNG文件头,再把<?php @eval($_POST['x']);?>追加到文件末尾。上传后程序提示成功,访问这个jpg文件,浏览器直接当图片渲染,没有任何异常。

到现在为止,一切都很平静。真正的转折发生在下一步——我尝试去触发服务端对这个jpg的内容解析。

3.3 换个思路:上传不是终点,而是"污染源"

普通的文件包含题,会直接给你一个?page=xx之类的参数,让你包含上传的jpg。但本题没有这种明显的include入口。于是我开始围绕题目里的"日志"、"session"线索做文章。

这里分享一个实战判断法:把目标想象成一个真实业务系统。系统每天会产生大量日志、session文件、缓存文件,这些文件本质上是服务端写入的文本文件。如果我能把攻击代码注入到这些文本文件中,再想办法让PHP去包含它们,不就和包含图片马一样吗?

所以核心问题变成两个:

  1. 如何让我的恶意PHP代码进入某个服务端可写的文件?
  2. 如何触发包含?

针对问题1,通常有三个入口:User-Agent等请求头写入日志、Session内容写入session文件、上传文件名写入日志/临时目录。我用Burp重放了上传请求,修改User-Agent字段为<?php phpinfo();?>,然后去访问日志文件路径。如果日志开启且可读可包含,这发就能直接看到phpinfo输出。

日志路径通常需要猜,常见位置有:/var/log/nginx/access.log、/var/log/apache2/access.log、/tmp/access.log。本题环境在尝试/var/log/nginx/access.log时成功读到了页面,说明日志文件可通过URL访问并被PHP解析。到这一步,整道题的攻击链已经闭环。

提示:日志包含这种方式,在生产环境里也非常常见。日志文件在真实攻击中是最容易利用的"免费写文件点",而且大部分时候日志功能是默认开启的。

4. 核心利用链:从日志污染到拿到flag

4.1 为什么日志和session文件会变成"代码执行点"

先解释原理,这是理解本题的关键。

Nginx的access.log会把每个请求的Method、URI、User-Agent、Referer等原样记录下来。这些字段由客户端(也就是我)完全控制。如果我在User-Agent里塞一段<?php ... ?>,服务端不会对日志内容做任何转义或过滤,直接落盘。 之后,如果存在一个include函数能读到这个日志文件,那么日志内容就会被当成PHP代码来执行。

所以利用条件有三个:

  1. 日志文件可被Web服务读取。
  2. 存在文件包含逻辑(可能是file_get_contents+eval,也可能是include)。
  3. 日志写入和包含访问路径都可控。

Session同理:PHP默认把session序列化后存到/tmp/sess_<sessionid>。如果我能控制session中的某个字段(比如用户名、昵称、数据内容),写入<?php ... ?>,再通过包含session文件来触发执行。这比日志包含更隐蔽,但需要能控制session内容,而且session文件的路径通常需要猜测。

4.2 实操:注入User-Agent并触发日志包含

我按顺序做了以下操作:

  1. 先访问一个不存在的路径,确认日志确实在记录,然后找到日志文件路径/var/log/nginx/access.log。
  2. 用Burp发送一个GET请求,把User-Agent改为<?php echo md5('test');?>。
  3. 再请求/var/log/nginx/access.log,如果页面回显了MD5字符串,说明日志被成功解析。

我这里上手直接写了一个完整一句话马:<?php @eval($_POST['cmd']);?>,然后访问日志文件确认无报错后,用蚁剑/菜刀连接/var/log/nginx/access.log,密码为cmd。

不过这里有个非常容易被坑的点:日志文件里含有大量原本的请求记录,一行一行拼接起来可能造成PHP语法错误。比如某一行日志里有未闭合的括号或引号,就会让整段PHP解析失败。解决办法是:

  • 用日志中包含的某一行作为"闭合边界"——比如先注入<?php @eval($_POST['cmd']);?>,然后确保同一行里没有特殊字符干扰;
  • 或者给一句话马加上?><?php前缀来强行闭合前面可能的php标签;
  • 更稳妥的做法是先构造一个确定无干扰的请求,例如专门用一个只有User-Agent的GET请求,日志那条记录里只有固定格式,不会带入非法符号。

我最终的做法:先发送一个GET /rce.php HTTP/1.1,User-Agent为<?php @eval($_POST['cmd']);?>,然后再访问日志路径,用蚁剑连接,成功拿到RCE。

4.3 利用获取的Webshell寻找flag:遍历目录与文件读取技巧

拿到RCE后,找flag是最后一步。这一步常用命令包括:

find / -name "*flag*" 2>/dev/null find / -name "*.txt" -exec grep -l "flag" {} \; 2>/dev/null cat /flag cat /tmp/flag cat /var/www/html/flag.txt

我当前环境里用find / -name "*flag*"直接定位到/var/www/html/flag.txt,cat出来就是flag。

如果flag被藏得更深,比如根目录、docker环境变量、甚至隐藏在图片文件末尾,需要用env、grep -r "flag" /、strings等手段逐步排查。这类"找flag"的收尾工作本身也值得练,因为比赛就是这么设计的——run起来容易,找到藏点才是真正区分度。

4.4 另一个分支:通过session文件路径执行

日志这条路一旦走通,session就只是换汤不换药。如果题目没有日志,但给了可控session字段,可以尝试找到session文件存储位置。常见路径:

  • /var/lib/php/sessions/sess_<sessionid>
  • /tmp/sess_<sessionid>

操作方式:登录或构造一个请求,让服务端把<?php ... ?>写进session某个字段,然后包含对应sess文件。与日志相比,session文件内容更干净,触发成功率更高,但前提是你得能预测session文件名。如果你已经通过其他漏洞读到了PHPSESSID或者能控制session_id,这条路就很稳。

注意:当包含session文件时,由于session文件内容里常有username|s:10:"..."这种序列化格式,直接包含容易报语法错误。建议写入字段时不要把代码放在序列化数据的值里,而是尝试覆盖整个session文件内容,或者用不易破坏结构的注入方式。

5. 把这道题放进更大的攻击面:实际项目里文件上传还能怎么玩

5.1 真实场景里文件上传漏洞的常见变体

CTF里学会这一套,放到真实渗透或实战中也不能只盯着日志和session。我梳理一下文件上传漏洞在真实系统里的常见衍生利用方式:

  • 存储型XSS:上传SVG文件,内部嵌套JavaScript。现在很多头像、附件上传点允许SVG,SVG一旦被浏览器加载就能执行脚本,危害面很可能超出预期。
  • 存储型HTML注入:上传HTML文件到同域路径,诱导用户点击后,可钓鱼、可盗Cookie、可利用同源策略操作其他页面。
  • CSV注入:上传包含Excel公式的CSV,服务端展示或下载时,如果用户用Excel打开,就可能触发公式执行,常用于钓鱼。
  • 恶意文档(docx/xlsx):Office文档本质是zip,可以在其内部XML里嵌入宏或外部实体,如果上传点允许Office文件且后端有解析逻辑,可能引发XXE。
  • WebShell变种:上传.phar、.htaccess、.user.ini等,利用Apache/Nginx/PHP的配置特性间接执行代码。这类思路也很重要——特别是.htaccess可以给当前目录增加AddType配置,直接把jpg当php解析。

5.2 从上传点反推后端架构:看见"隐藏组件"的能力

这道题最有价值的训练点,我认为不是某一条利用链,而是从入口反推整体架构的能力。你会不会在看到上传功能时,自动联想到:

  • 文件存哪里?Web可访问吗?
  • 存储路径会不会拼接用户输入?有没有路径穿越?
  • 文件名如何处理?是否可控制?是否可包含路径分隔符?
  • 文件内容是否会被二次读取/解析/展示?
  • 有没有异步处理、解压、缩略图生成、AI审核等后处理流程?
  • 日志/session/缓存/临时目录这些"隐形文件",是否可能成为投毒点?

如果每次测试上传点,都能在脑子里过一遍这张检查清单,你会比只会套用"上传php改后缀绕过"脚本的人高一个段位。这也是这道"文件上传 与 其它 作业2"真正想培养的能力——名字里的"其他"就是在提醒你,别被困在单一漏洞类型里。

6. 复现练习与避坑清单:照着这个流程,你可以少走一整晚弯路

6.1 本地环境快速搭建建议

如果你想自己复现、加深印象,我用的是Docker搭的漏洞环境。大致步骤:

  1. 拉一个含Nginx + PHP的镜像。
  2. 写一个upload.php:只允许jpg/png/gif上传,校验文件头,移动到uploads/目录。
  3. 写一个包含入口:比如page.php?file=,直接用include读文件(仅限本地测试)。
  4. 确认日志路径/var/log/nginx/access.log可通过Web访问(模拟低权限配置错误或题目故意敞开的路径)。
  5. 按上文流程,先传jpg马,再尝试日志包含,最后用session包含做额外验证。

这种环境搭建半小时内就能完成,但跑通后的理解深度,比看十篇writeup都值。

6.2 常见踩坑点与调试技巧

我在复现和实战中踩过不少坑,列几个高频的:

  1. 日志解析失败:日志行内含引号、括号、分号等符号,破坏了PHP语法。解决:先注入简单payload(如phpinfo();),看到回显再往一句话马方向构造;构造时尽量在Payload前后增加空格和?>闭合符。
  2. 蚁剑连不上日志Webshell:检查请求地址是否真的返回了正常HTTP 200,且响应中无PHP警告输出;如果日志文件过大,可能导致连接超时,建议清空日志或仅包含特定路径的请求。
  3. 包含路径失效:include参数可能限制了目录或文件后缀,尝试php://filter/convert.base64-encode/resource=...等伪协议绕过(前提是PHP配置允许)。
  4. session文件路径猜不到:尝试用find / -name "sess_*"这类命令在获取RCE后反查,比赛时也可以通过报错回显泄露路径。
  5. 上传点根本没落地:有些题目上传后端只是把文件内容写入数据库或消息队列,这时攻击面又变了——可能需要往数据库内容里投毒,再找一处把数据库内容渲染出来的功能。

6.3 这道题给你留下的"能力清单"

最后,我想把这道题背后值得沉淀的能力点明确化,方便你之后迁移到新题目:

  • 信息收集不只看页面,还要看响应头、报错、附件源码。
  • 过滤规则要分类判断,而不是盲目套绕过字典。
  • 攻击面不止上传点,要顺着文件流转路径找"服务端二次读取/包含/渲染"的地方。
  • 日志、session、临时文件、缓存,是几个随手可用的"免费写文件"入口。
  • 拿到RCE后找flag,要有自己的命令套路,避免手忙脚乱。

我个人在实际练习里有个习惯:每做一道上传题,就会写一份"入口-流转-触发点"三段式笔记,把上传文件之后的每条可能路径都画出来——上传文件被存到哪、哪里在用它、用什么方式用它。这个笔记积累多了以后,再遇到陌生题,第一反应就不是"上传一个马试试",而是先画数据流,再定点打击。

这道题做完之后,我最大的体会是:文件上传漏洞从来不是一个孤立的技术点,它更像一把钥匙,能不能打开门,取决于你有没有找到门后面的锁芯。多从"服务端会怎么处理这个文件"的角度去想问题,很多看似无解的上传题,都会变得豁然开朗。

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

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

立即咨询