文章总结: 文章剖析EspoCRMv9.3.3及更早版本中的漏洞链CVE-2026-33656,攻击者可利用公式脚本引擎绕过字段级ACL控制,篡改附件sourceId字段,结合未净化的路径构造实现任意文件读写,最终通过.htaccess配置实现RCE。CVSS评分9.1,影响9.3.4前所有版本。文章详细分析了访问控制缺口、路径拼接缺陷、完整攻击链,并提供修复方案:升级至9.3.4版本,该版本使用basename净化路径。 综合评分: 92 文章分类: 漏洞分析,WEB安全,渗透测试,漏洞POC,安全建设
灾难公式:串联EspoCRM脚本引擎实现远程代码执行
幻泉之洲
2026年3月28日 16:01 北京
本文剖析了EspoCRM(v9.3.3及更早版本)中一个完整的安全漏洞链。该漏洞允许拥有管理员权限的攻击者,通过内部的公式脚本引擎绕过字段级访问控制,篡改文件附件的sourceId字段,结合文件存储层未净化的路径构造,实现任意文件读写,并最终利用.htaccess技巧达成远程代码执行(RCE)。CVE编号:CVE-2026-33656。文章详细分析了技术原理、影响范围、完整的攻击利用链,并提供了修复建议。
漏洞概述
漏洞根源在于EspoCRM内部两个安全机制的失效。首先,其内置的“公式脚本引擎”在执行record\update等函数时,未能应用实体元数据中定义的字段级访问控制列表(field-level ACL)。这使得管理员可以覆盖被标记为readOnly的字段,特别是附件(Attachment)实体的sourceId字段。
其次,在读取或写入附件文件时,应用程序通过EspoUploadDir::getFilePath()方法构造文件路径,它直接拼接sourceId字段的值,未进行任何路径遍历净化(如basename())。
攻击者可以将sourceId设置为类似../../config.php的恶意值。控制读取路径导致任意文件读取(如数据库配置文件);控制写入路径,结合分块上传功能,可向Web目录写入任意文件。利用这个能力,配合一个精心构造的.htaccess文件,最终能在Apache环境下获得www-data(或等效Web服务用户)权限的远程代码执行能力。
该漏洞的利用需要管理员权限。根据CVSS v3.1标准,其基础评分为9.1(严重级),攻击范围(Scope)为“已更改”。
技术原理分析
1. 访问控制缺口:公式引擎的盲区
EspoCRM的访问控制主要定义在JSON元数据文件中。例如,附件实体的sourceId字段被标记为readOnly。
{“fields”: { “storage”: {“readOnly”: true}, “source”: {“readOnly”: true}, “sourceId”: {“readOnly”: true} } }
正常的API更新流程(通过Record\Service::filterInput())会调用getScopeForbiddenAttributeList(),剥离掉这些readOnly字段,确保它们不被用户输入修改。
然而,公式引擎的record\update函数走了另一条路。其核心代码在UpdateType.php中:
$entity->set($data); EntityUtil::checkUpdateAccess($entity); $this->entityManager->saveEntity($entity);
EntityUtil::checkUpdateAccess()只检查了非常有限的权限(例如防止将普通用户类型改为超级管理员),完全没有参照实体的ACL元数据来过滤readOnly或forbidden字段。
这就造成了访问控制的不对称:同一个readOnly字段,API路径保护它,公式引擎路径却敞开大门。
注:维护者认为公式引擎不应用ACL是设计使然,并非漏洞。其观点是:公式引擎专为受信任的管理员设计,且管理员已有其他代码执行途径(如扩展上传)。因此,最终的修复方案并未改变公式引擎的行为,而是加固了文件路径构造。
但这个设计决策的安全后果是实质性的:它引入了一条不受字段级ACL约束的、强大的数据操作通道。
2. 危险拼接:未净化的文件路径
sourceId字段用于确定附件文件的磁盘存储路径。在EspoUploadDir::getFilePath()中,问题代码一览无余:
protected function getFilePath(Attachment $attachment) { $sourceId = $attachment->getSourceId(); return ‘data/upload/’ . $sourceId; }
这里没有任何净化处理:没有basename(),没有realpath()检查,也没有拒绝../这类路径遍历序列。sourceId被直接拼接到基础路径后,这个完整的路径被用于后续所有的文件读写操作。
所以,一旦攻击者通过公式引擎控制了sourceId</code,就等于控制了文件系统的访问目标。
3. 攻击链条串联
漏洞链清晰呈现:
-
权限前提
:获取管理员凭证(或通过其他方式获得管理员会话)。
-
ACL绕过
:利用公式引擎的
record\update函数,修改一个已存在附件的sourceId为恶意路径(如../../config.php)。 -
任意文件读取
:直接请求该附件的文件下载API,服务器会读取并返回目标路径的文件内容。
-
任意文件写入
:创建一个状态为“正在上传”(
isBeingUploaded: true)的新附件。先通过公式引擎将其sourceId重定向到Web可访问目录(如../../client/shell.php),然后通过附件分块上传API(POST /api/v1/Attachment/chunk/{id})向其写入任意内容。 -
升级至RCE
:在默认Apache部署中,
client/目录可能不执行PHP。此时可再写入或修改.htaccess文件,添加处理器指令,强制Apache将特定文件(如shell.php)作为PHP解析。
影响范围
此漏洞直接影响所有运行EspoCRM 9.3.4之前版本的实例。具体影响组件包括:
-
核心功能
:公式脚本引擎(
record\update,record\attribute函数)。 -
文件存储组件
:基于
EspoUploadDir存储的附件处理。 -
相关实体
:Attachment实体及其
sourceId字段。
虽然利用链需要管理员权限,但考虑到以下情况,其实际风险依然很高:
- 管理员账户可能因弱密码、凭证复用或社会工程学而遭窃。
- 内部威胁。
- 攻击者可能结合其他中危漏洞(如XSS)诱使管理员执行恶意操作,间接获得利用条件。
此外,公式引擎的ACL盲点不仅影响sourceId。通过record\attribute函数,攻击者可以读取被标记为internal的敏感字段,例如:
-
User.password:所有用户的bcrypt哈希,可用于离线破解。
-
AuthToken.token:活跃用户的会话令牌,可用于直接会话劫持。
-
存储在邮件账户实体中的SMTP凭据。
这为攻击者提供了在不触碰文件系统的情况下进行横向移动的能力。
漏洞复现与PoC说明
完整的远程代码执行利用链涉及6个HTTP请求。以下为关键步骤概述,完整的自动化PoC脚本可在GitHub查看(JivaSecurity/ESPOCRM-RCE-POC-CVE-2026-33656)。
前置条件
拥有有效的管理员Espo-Authorization令牌。
步骤简述
-
创建文件写入句柄
:发送
POST /api/v1/Attachment,创建一个isBeingUploaded: true的附件,记下其id和sourceId(初始时二者相同)。 -
重定向sourceId
:使用公式引擎,将该附件的
sourceId更新为指向web目录的路径,例如../../client/shell.php。 -
写入Webshell
:向
POST /api/v1/Attachment/chunk/{ATTACH_ID}发送请求,将Base64编码的PHP Webshell(如<?php system($_GET["c"]); ?>)作为内容块上传。 -
配置PHP解析(可选)
:如果
client/目录默认不执行PHP,则需要重复步骤1-3,但将第二个附件的sourceId指向../../.htaccess,并写入SetHandler application/x-httpd-php这样的指令,且只针对shell.php文件生效,避免破坏应用。 -
执行命令
:访问
http://target/client/shell.php?c=id,即可看到命令执行结果。
uid=33(www-data) gid=33(www-data) groups=33(www-data)
整个过程不依赖任何第三方组件、特殊的PHP配置或服务器错误配置。
修复建议与缓解措施
官方修复(EspoCRM 9.3.4)
维护者迅速响应,在报告后24小时内发布了修复。核心修复是对sourceId进行净化处理。
在EspoUploadDir::getFilePath()中,修复后的代码为:
protected function getFilePath(Attachment $attachment): string { $sourceId = $attachment->getSourceId(); $file = basename($sourceId); return ‘data/upload/’ . $file; }
使用basename()函数剥离所有目录部分,只保留文件名,从根本上阻断了路径遍历的可能。此修复不仅应用于getFilePath(),还一次性清理了其他五个可能使用未信任ID构造路径的代码位置。
行动项:所有EspoCRM用户应立即升级至9.3.4或更高版本。
缓解措施(对于无法立即升级的情况)
-
审查和限制公式使用
:检查系统中所有通过公式引擎定义的业务逻辑,特别是包含
record\update调用的部分,确保其没有对敏感字段(尤其是Attachment相关字段)进行操作。 -
网络层防护
:部署Web应用防火墙(WAF),设置规则检测和阻断对
/api/v1/Formula/action/run端点的异常请求,或包含路径遍历序列的sourceId值。 -
权限最小化
:严格管理管理员账户,使用强密码并启用多因素认证(如果EspoCRM支持),减少管理员账户泄露的风险。
给开发者的启示
-
内部引擎也需边界防护
:任何提供给用户(即使是管理员)执行数据操作的内部脚本引擎,都必须强制执行与应用外部API一致的访问控制策略。“仅管理员可用”不能替代精确的字段级ACL。
-
净化所有从数据库到文件系统的路径
:不要信任存储在数据库中的、用于构造文件路径的任何字段。使用
basename()或realpath()进行严格的验证和限制。 -
审计只读和内部字段
:定期审查ACL元数据中标记为
readOnly或internal的字段,并思考:“如果攻击者绕过了限制写入/读取了这个字段,会发生什么最坏的情况?”
披露时间线
| 日期 | 事件 |
| — | — |
| 2026-03-21 | 通过GitHub安全公告(GHSA-7922-x7cf-j54x)报告漏洞 |
| 2026-03-21 | 维护者响应,开始就公告框架进行协作讨论 |
| 2026-03-21 | 维护者接受报告,并提交基于basename()的修复(commit 3fab34e) |
| 2026-03-23 | 维护者申请CVE,被分配为CVE-2026-33656 |
| 2026-03-23 | CVSS分数被维护者从9.1(严重)改为7.2(高危),未提前讨论 |
| 2026-03-24 | 研究者恢复原分数并给出“攻击范围已更改”的技术依据 |
| 2026-03-24 | 维护者锁定公告,阻止进一步评论,后恢复研究者协作权限 |
| 2026-03-24 | 维护者最终确认并恢复CVSS v3.1分数为9.1(严重) |
| 2026-03-24 | EspoCRM 9.3.4正式发布,包含修复补丁 |
| 2026-03-25 | 本技术分析文章发布 |
本文章发布时(2026年3月25日),EspoCRM 9.3.4修复版本已公开可用。尽管官方的安全公告可能仍处于草稿状态,但由于补丁已发布,用户可立即采取行动升级,因此决定公开完整技术细节,以帮助社区充分理解风险。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:幻泉之洲 《灾难公式:串联EspoCRM脚本引擎实现远程代码执行》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论