), // 现有 rce-guard 只认官方利用链,拦不住这种形态,故按 PHP 代码特征识别。 $__tpl_bad = '/(\?\>\s*$|<\?php|<\?=|\{\$|\{:|";\s*(eval|assert|system|exec|passthru|shell_exec|file_put_contents|base64_decode|fwrite|copy|curl_exec)\s*\(|eval\s*\(\s*\$_|assert\s*\(\s*\$_)/i'; $__tpl_hit = false; $__tpl_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; $__tpl_qs = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; foreach (array($__tpl_uri, rawurldecode($__tpl_uri), $__tpl_qs, rawurldecode($__tpl_qs)) as $__tpl_s) { if ($__tpl_s !== '' && @preg_match($__tpl_bad, $__tpl_s)) { $__tpl_hit = true; break; } } if (!$__tpl_hit) { foreach (array($_GET, $_POST, $_COOKIE) as $__tpl_arr) { if (!is_array($__tpl_arr)) { continue; } foreach ($__tpl_arr as $__tpl_k => $__tpl_v) { $__tpl_pool = array((string)$__tpl_k); if (is_string($__tpl_v)) { $__tpl_pool[] = $__tpl_v; $__tpl_pool[] = rawurldecode($__tpl_v); } foreach ($__tpl_pool as $__tpl_s) { if ($__tpl_s !== '' && @preg_match($__tpl_bad, $__tpl_s)) { $__tpl_hit = true; break 3; } } } } } if ($__tpl_hit) { if (!headers_sent()) { http_response_code(403); } header('Content-Type: text/plain; charset=utf-8'); echo 'Blocked by yunucms-tpl-rce-guard'; exit; } // === /yunucms-tpl-rce-guard === // 1) 从原始请求串/URI 直接拦截——避免 ThinkPHP 从 REQUEST_URI 重新解析出被剥离的参数 // 【关键】只拦截「明确的 RCE 特征」,绝不拦截普通业务参数。 // 历史上曾因拦截裸参数 register= / route= / data= / method=get 误杀了大量正常 URL // (包括后台登录 iframe 目标 index.php?s=/admin/index/login 所在文件),导致 // 前台/后台整片 403「无法访问」。本版本仅拦截数组形态 register[ / route[ / method[ / data[ // (真正的 RCE 注入载体),标量形态(?data=1 等)一律放行。 $raw_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; $raw_qs = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; $raw_path = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : ''; $check_str = strtolower($raw_uri . "\n" . $raw_qs . "\n" . $raw_path); // === THINKPHP-RCE-GUARD v2 (20260922) === // 参数级 PHP 代码注入拦截:对 URI/查询串做双重 urldecode 后匹配代码特征。 // 背景:2026-07/08 攻击者批量投递形如 // index.php?s=wap/index/index&<任意键名>=aa + 分号 + 落盘函数调用 + 注释符 // 的载荷(模板编译注入)。旧版守卫只拦固定键名(register[ / route[ / data[), // 对「任意键名 + 值内嵌 PHP 代码」完全放行,这是反复中毒的直接原因。 // 兼容性:下列特征在正常业务查询串中不会出现;命中即硬拦截。 // ⚠️ 铁律:本块内(含注释)禁止出现任何 PHP 定界符字面量,必须用 \x 转义书写。 // 2026-09-22 踩坑:注释里写了一个裸的闭标签,PHP 提前结束代码块, // 整站把 index.php 当纯文本输出(HTTP 200 + 源码泄露)。且 php -l 查不出来。 $__g2 = strtolower(urldecode(urldecode($raw_uri . "\n" . $raw_qs . "\n" . $raw_path))); $__bad2 = array( "\x3c\x3fphp", "\x3f\x3e", "\x3c\x3f=", 'eval(', 'assert(', '$_request', '$_post', '$_get', '$_cookie', '$_server', '$globals', 'file_put_contents', 'fwrite(', 'base64_decode', 'shell_exec', 'passthru(', 'create_function', 'preg_replace', 'call_user_func', 'invokefunction', 'ddyyok', 'ad10523', 'tiaojs', 'baiduapi', 'wanwo88', ); foreach ($__bad2 as $__p2) { if (strpos($__g2, $__p2) !== false) { http_response_code(403); header('Content-Type: text/plain; charset=utf-8'); header('Cache-Control: no-store'); echo 'Blocked by yunucms-rce-guard v2'; exit; } } unset($__g2, $__bad2, $__p2); // POST 体:只查高置信度标记(PHP 定界符原文 / 已知 C2),绝不查通用函数名,避免误伤文章正文 if (!empty($_POST)) { $__pv = strtolower(urldecode(urldecode(is_array($_POST) ? http_build_query($_POST) : (string) $_POST))); $__pb = array("\x3c\x3fphp", "\x3c\x3f=", 'ddyyok', 'ad10523', 'tiaojs', 'baiduapi', 'wanwo88', 'gxfengzhonginc'); foreach ($__pb as $__p3) { if (strpos($__pv, $__p3) !== false) { http_response_code(403); header('Content-Type: text/plain; charset=utf-8'); header('Cache-Control: no-store'); echo 'Blocked by yunucms-rce-guard v2 (post)'; exit; } } unset($__pv, $__pb, $__p3); } // === /THINKPHP-RCE-GUARD v2 === $rce_patterns = array( '_method=__construct', 'invokefunction', 'filter[]=', 'filter%5b', 'filter[0]', 'filter%5b0%5d', 's=index/think', 's=%2findex%2fthink', 's=/index/think', 's=\\think', 's=%5cthink', 'phpmyadmin', '/pma/', // 仅拦截数组形态(真正的 RCE 注入载体);标量形态是正常业务参数,绝不拦截 'register[', 'route[', 'method[', 'data[', ); foreach ($rce_patterns as $pat) { if (strpos($check_str, strtolower($pat)) !== false) { http_response_code(403); header('Content-Type: text/plain; charset=utf-8'); echo 'Blocked by yunucms-rce-guard'; exit; } } // 2) 剥离已进入 PHP 超全局变量的 RCE 键 // - 「绝不可能是正常业务参数」的键:_method / filter / _override / capture —— 无条件剥离 // - register / route / method / data:仅在「数组形态」时剥离(RCE 注入载体); // 其标量形态(如表单字段 method/data)是正常业务字段,绝不剥离,否则会破坏后台表单提交。 $always_strip = array('_method', 'filter', '_override', 'capture'); foreach ($always_strip as $k) { if (isset($_GET[$k])) unset($_GET[$k]); if (isset($_POST[$k])) unset($_POST[$k]); if (isset($_COOKIE[$k])) unset($_COOKIE[$k]); if (isset($_REQUEST[$k])) unset($_REQUEST[$k]); } $array_only_strip = array('register', 'route', 'method', 'data'); foreach ($array_only_strip as $k) { if (isset($_GET[$k]) && is_array($_GET[$k])) unset($_GET[$k]); if (isset($_POST[$k]) && is_array($_POST[$k])) unset($_POST[$k]); if (isset($_REQUEST[$k]) && is_array($_REQUEST[$k])) unset($_REQUEST[$k]); if (isset($_COOKIE[$k]) && is_array($_COOKIE[$k])) unset($_COOKIE[$k]); } // 3) 【已移除泛化 s= 拦截,见下方说明】 // ⚠️ 历史版本在第 3 段用单词级正则 // /think|invokefunction|call_user_func|system|eval|assert/i 匹配 $arr['s'] // 会导致「只要 s= 路由里出现 system 就 unset 掉 s 参数」。 // 而 yunucms 后台有一个真实的同名控制器 System(s=admin/system/basic 等), // 于是所有后台 system/xxx 菜单被误杀 → TP5 拿不到 s → 默认渲染前台 index/index // → 症状就是「后台点菜单直接跳到网站首页」。所有站点无一例外必现。 // ✅ 真实 TP5 RCE 的 s= 载荷形态已被第 1 段 $rce_patterns 全覆盖: // s=index/think、s=%2findex%2fthink、s=/index/think、s=%5cthink、s=\think、invokefunction、 // _method=__construct、filter[]=、filter[0]、phpmyadmin 等。 // 第 3 段属冗余防御,且因 system/eval/assert/system 单词匹配造成大面积误杀, // 故整段删除。此修复不影响 RCE 防护强度。 // === RCE 防护结束 === // === 官方引导段(2.3.5+)=== if (version_compare('5.5', PHP_VERSION, '>')) {die('PHP 5.5 or greater is required!!!');} if (version_compare('7.5', PHP_VERSION, '<')) {die('PHP version should not exceed 7.5!!!');} if (PHP_VERSION_ID >= 70300) { ini_set('session.cookie_samesite', 'Lax'); } define('APP_PATH', __DIR__ . '/app/'); define('APP_AUTO_BUILD', TRUE); define('APP_DEBUG', FALSE); define('CONF_PATH', __DIR__.'/config/'); require './system/start.php'; ?>