Skip to content

关闭 MacCMS 的自动升级(含二次开发关法) ​

改了半天模板、动过几个控制器,结果后台点一下「在线升级」,官方升级包解压一盖,自己改的东西全没了——这是自建 MacCMS 最容易踩的坑之一。本文结合 MacCMS V10(magicblack/maccms10)真实源码,先讲清「自动升级/版本检测」到底是怎么运作的,再给出从「后台层面屏蔽入口」到「二次开发彻底禁用」的几种关法。

结论来自实际读源码

下面所有文件路径、控制器、接口地址都来自实际阅读 d:\maccms10 源码(只读,未改动任何源码文件)。涉及行号处仅作定位参考,不同发行版可能略有偏移,以你本地源码为准。想先看整体架构,见源码结构分析;安全加固见安全加固。

一、先说清:MacCMS 的「自动升级」到底是什么 ​

很多人以为 MacCMS 会「后台偷偷自己升级」。实际读源码后,这里要先纠正一个概念:MacCMS V10 没有「无人值守的自动升级」(不会定时自己拉包覆盖,定时任务配置 application/extra/timming.php 里没有任何升级任务)。它真正存在的是两件事:

  1. 版本检测提示——登录后台首页时,浏览器会去请求官方接口,比对版本号,有新版就在页面上弹一个「有新版本可用」的提示。这一步是自动发生的,容易被误当成「自动升级」。
  2. 一键在线升级——你手动点提示框里的「点击进入在线升级」后,后台才会去官方服务器下载升级包、解压覆盖源码、跑数据库升级脚本。这一步要人点,不是自动的。

所以严格说,要「关自动升级」,关的其实是第 1 件(自动跑的版本检测请求)和第 2 件(一键覆盖源码的升级功能)。下面逐个拆。

1.1 后台首页的版本检测(自动请求官方) ​

入口在后台首页模板 application/admin/view_new/index/index.html。页面加载时有一段 JS,直接向官方升级服务器发请求:

js
// application/admin/view_new/index/index.html(版本更新检测片段)
if (typeof MAC_VERSION !== 'undefined' && ...) {
    $.ajax({
        url: 'https://update.maccms.la/v10/?c=check&v=' + MAC_VERSION
             + '&p=' + PHP_VERSION + '&tp=' + THINK_VERSION + '&t=' + Math.random(),
        type: 'GET',
        dataType: 'text',
        success: function (response) {
            // 从返回文本里 eval 出 update_content、解析 new_v
            var newV = ...;
            if (newV > MAC_VERSION) {
                // 显示「有新版本可用」提示
                $('#update-notification, #update-notification-mobile').show();
            }
        }
    });
}

要点:

  • 请求地址是官方的 https://update.maccms.la/v10/?c=check&...,把你的 CMS 版本号、PHP 版本、ThinkPHP 版本都带了过去。
  • MAC_VERSION 来自 config('version')['code'],由后台 Base 控制器注入模板(application/admin/controller/Base.php 结尾 $this->assign('MAC_VERSION', ...));版本号本身存在 application/extra/version.php 的 code 字段。
  • 返回的文本会被 eval 执行一段 var update_content = [...]。这是从官方域名拉回来的脚本在你后台页面里执行——信任的是 update.maccms.la 这个第三方升级源。

这一步会把信息发给第三方

只要有人打开后台首页,浏览器就会主动连 update.maccms.la 并上报版本信息。即使你从不点升级,这个请求也一直在发。想完全不与官方升级源发生关系,就得把这段请求也关掉(见三、2)。

1.2 一键在线升级:Update 控制器(会覆盖源码) ​

真正「动你文件」的是 application/admin/controller/Update.php。首页提示框里那个「【点击进入在线升级】」链接,指向的就是它:

admin.php/admin/update/step1.html?file=maccms10_update

Update 控制器分三步(step1 / step2 / step3),落地方式是拉远程 zip 包解压覆盖源码目录,不是只更新数据或模板:

步骤action做了什么
第一步step1($file)从 https://update.maccms.la/v10/ 下载 maccms10_update.zip,做 SHA1 校验防篡改,然后用 PclZip 解压到站点根目录、覆盖同名文件(PCLZIP_OPT_REPLACE_NEWER)
第二步step2()若包里带 application/data/update/database.php,执行其中的 SQL,做数据库结构升级
第三步step3()清缓存,提示升级完成

源码里升级服务器地址是 base64 编码藏起来的,解开就是官方域名:

php
// application/admin/controller/Update.php 构造方法
$this->_url = base64_decode("aHR0cHM6Ly91cGRhdGUubWFjY21zLmxhLw==")."v10/";
//            解开即 https://update.maccms.la/ + "v10/"
$this->_save_path = './application/data/update/';

这就是会覆盖你改动的地方

step1 用 PclZip 把官方包解压覆盖到源码目录。只要官方包里有和你改过的同名文件(控制器、框架文件、部分模板),就会被盖掉。你在 application/ 里改的二开代码、被官方包覆盖的模板,都可能一次升级没了。这正是「关掉它」的核心动机。

补充两个源码细节,帮你判断关哪里才安全:

  • Update 控制器被豁免了权限校验。application/admin/controller/Base.php 里,$this->_cl != 'Update' 的判断让 Update 控制器跳过 check_auth() 权限检查(第 45 行附近)。也就是说只要登录了后台,不管这个管理员有没有「升级」权限,都能触达升级 action。所以靠「角色权限」不一定能挡住它,想稳妥还得从路由/源码层封。
  • 有文件完整性校验。Base 控制器会用 md5_file('application/admin/controller/Update.php') 和 version.php 里的 update_hash 比对,不一致就 exit。这意味着你直接改 Update.php 内容后,若不同步改 version.php 的 update_hash,整个后台(除 Update 自身)会被这段校验卡死。下面的二开关法会提醒你怎么绕开这个坑。

1.3 顺带区分:这几个「升级」不是一回事 ​

读源码时还会碰到别的「升级/update」,别混了:

  • application/data/update/database.php:数据库升级脚本,只在你跑在线升级第二步时才执行,本身不覆盖源码。
  • 后台「应用插件」里的「云插件升级」(addon/upgrade,见 application/admin/common/auth.php、AddonCloudService.php):那是插件的升级,和程序源码在线升级是两套,本文不涉及。
  • application/extra/maccms.php 里的 'new_version' => '':源码注释说明这个键已废弃(见 common/behavior/Init.php),不再用于判断版本,别指望改它能关掉检测。

二、后台/配置层面怎么关(不改源码) ​

如果你只是不想让升级被「误点」,又不想动源码,可以从入口可见性和路由上屏蔽。

没有现成的「关闭自动升级」开关

实话实说:本版本源码里没有一个 maccms.php 配置项能一键关掉版本检测或在线升级。所以「配置层面关」本质上是「屏蔽入口 + 封路由」,不是改个开关。想彻底干净,还是得走二次开发那一节。

  • 别点就不升级。在线升级要手动点「进入在线升级」才会执行。只要团队约定不点,源码不会被动覆盖。这是最轻量但最不保险的办法(挡不住手滑和新同事)。
  • 收紧后台管理员。虽然 Update 控制器本身豁免了权限校验(见上),但进后台首页本身要登录。把后台只给可信的人、入口文件改名(强制改名)、开好登录防护,能把能点升级的人缩到最小。
  • 用 WAF / Nginx 封升级路径(最推荐的不改源码办法)。直接在 Web 服务器层把升级 URL 拦掉,谁都点不动:
nginx
# Nginx:拦掉后台在线升级入口(admin.php 换成你改名后的后台入口文件名)
location ~* /admin\.php/admin/update/ {
    return 403;
}

路径要用你自己的后台入口名

MacCMS 强制要求把 admin.php 改名(见安全加固),上面规则里的 admin.php 要换成你实际的后台入口文件名,否则拦不到。

三、二次开发(改源码)层面怎么彻底禁 ​

想一劳永逸,就从源码层把升级能力拆掉。改前务必备份、用 Git 管好改动,并且尽量别碰框架核心(thinkphp/ 目录),只动 application/ 下的业务文件。

改 Update.php 前先搞清完整性校验

前面说过,Base 控制器会校验 Update.php 的 md5 与 version.php 的 update_hash。如果你直接改 Update.php 的内容而不同步 update_hash,会导致除 Update 外整个后台 exit。两种安全做法:

  1. 改完 Update.php 后,重新算一遍 md5_file('application/admin/controller/Update.php'),把结果写回 application/extra/version.php 的 update_hash;
  2. 或者干脆不改 Update.php 的内容,改用下面「封路由 / 封 Web 服务器」的方式从外部挡住它(推荐,省心且不触发校验)。

3.1 禁用在线升级 action(让它直接返回) ​

最直接的是让 step1 不再下载覆盖。但如上所述,直接改 Update.php 会触发完整性校验,所以推荐用路由把这几个 action 劫持到一个安全响应,而不是改控制器内容。在 application/route.php 顶部加规则,把升级地址重定向/拦截:

php
// application/route.php
use think\Route;

// 把在线升级三步全部封死:命中即 403,不进 Update 控制器
Route::rule('admin/update/:action', function () {
    return response('在线升级已被站长关闭', 403);
});

为什么优先用路由而不是改控制器

application/route.php 是业务配置文件,不在 Update.php 完整性校验范围内,改它不会把后台卡死。而且规则集中、好维护、升级包一般也不会覆盖你的自定义路由(但仍建议纳入 Git,万一被盖能一眼看出来)。

如果你确实要改控制器本体(比如要完全删掉下载逻辑),记得改完同步 update_hash:

php
// application/admin/controller/Update.php —— step1 开头直接拦住
public function step1($file='')
{
    // 二开:站长已禁用在线升级,避免升级包覆盖自定义代码
    return $this->error('在线升级功能已关闭');
    // …原下载/解压逻辑不再执行…
}

改完在命令行算新 hash 并写回 version.php:

bash
php -r "echo md5_file('application/admin/controller/Update.php');"
# 把输出填进 application/extra/version.php 的 'update_hash'

3.2 注掉后台首页的版本检测请求 ​

要让后台不再偷偷连 update.maccms.la,编辑后台首页模板 application/admin/view_new/index/index.html,把那段「版本更新检测」的 $.ajax({...}) 注释或删掉即可:

js
/* ── 版本更新检测(二开已关闭:不再请求 update.maccms.la) ── */
/*
if (typeof MAC_VERSION !== 'undefined' && ...) {
    $.ajax({ url: 'https://update.maccms.la/v10/?c=check&v=' + MAC_VERSION + ... });
}
*/

这段在模板里,不受 Update.php 完整性校验影响,改它是安全的。关掉后,后台首页就不再上报版本信息,也不会再弹「有新版本可用」。

升级包可能盖回模板

view_new/index/index.html 这类模板文件,有可能被官方升级包覆盖。既然你已经把在线升级也关了(3.1),一般不会再被盖;但仍建议把这类改动纳入 Git,方便发现被改回。

3.3 从菜单/提示上彻底隐藏入口 ​

版本检测关掉后(3.2),「有新版本可用」的提示本身就不会出现,升级入口自然看不到。如果你只想隐藏入口、保留检测,也可以在同一个模板里把 #update-notification、#update-notification-mobile 两个元素或 showUpdateDialog() 调用去掉。注意 Update 控制器在菜单权限表 application/admin/common/auth.php 里并没有独立菜单项(它靠首页提示框的链接触达),所以不用去菜单里找它。

3.4 用 Web 服务器兜底(和不改源码那节同一招) ​

即便做了上面的改动,也建议在 Nginx/WAF 上再封一层升级路径(见二里的 Nginx 规则)。源码改动可能被升级包覆盖,Web 服务器规则不会,双保险最稳。

四、为什么要关,以及关掉后的责任 ​

关掉自动升级的理由,核心就一个字:稳。

  • 保护二开成果:在线升级会用官方包覆盖同名文件,你改过的控制器、模板、框架补丁都可能被盖掉。关掉它,改动才稳得住。
  • 不被第三方升级源左右:版本检测和升级包都来自 update.maccms.la。关掉后,你的后台不再主动连第三方、不再执行从远端 eval 回来的脚本,减少供应链风险面。
  • 可控的发布节奏:升级什么、什么时候升、升之前先在测试环境验证,全部由你决定,而不是被一个「有新版本」的红点推着走。

关掉不等于不用管安全

关掉自动升级后,打补丁的责任就落到你自己头上了。框架和 CMS 仍会不断修漏洞(尤其 ThinkPHP5 历史上的 RCE / SQL 注入系列),你得自己盯官方仓库、手动把安全补丁合进来。具体做法见安全加固 · 及时升级打补丁——那里讲的「及时升级」和本文「关掉自动升级」不矛盾:关的是「自动覆盖式升级」,但安全补丁要你用可控的方式亲手打上。

推荐的落地姿势:关掉自动覆盖式升级 + 用 Git 管理源码 + 盯官方仓库手动合安全补丁。这样既不会被升级包毁掉二开,又不至于漏掉关键的安全修复。

五、小结 ​

  • MacCMS V10 没有「无人值守的自动升级」,真正存在的是:后台首页自动的版本检测请求(连 update.maccms.la)+ 需手动点的一键在线升级(Update 控制器拉包解压覆盖源码)。
  • 入口文件:版本检测在 application/admin/view_new/index/index.html;在线升级在 application/admin/controller/Update.php(升级地址 base64 藏的 https://update.maccms.la/v10/)。
  • 没有现成配置开关。不改源码就用 Nginx/WAF 封 admin/update/ 路径;要彻底禁就用 application/route.php 劫持升级 action、注掉首页检测请求,直接改 Update.php 则务必同步 version.php 的 update_hash。
  • 关掉后记得自己盯补丁,别把「关自动升级」做成「从此不管安全」。

源码无法 100% 确认的细节(比如官方升级服务器返回格式、具体行号),以你本地实际版本为准。