CVE-2026-78088:Contest Gallery 路径校验缺失导致文件覆盖

漏洞影响什么

Contest Gallery 用于管理 WordPress 站点中的图库。它更新分享页面标题和元数据的后台逻辑存在路径校验缺失:服务端接受请求指定的文件路径,读取并重建文件内容,再写回同一位置,却没有确认目标属于当前图库。拥有插件后台访问权的攻击者因此可以将写入目标指向图库以外的文件,造成内容篡改;满足额外条件时,还可进一步执行 PHP 代码。

声明的受影响版本范围为 32.0.1 及更早版本,修复版本为 33.0.0。实际利用演示针对 32.0.1,不能将这一结果等同于已逐一验证范围内的所有版本。

利用需要哪些权限和条件

攻击者需要有效的 WordPress 登录凭据和 Contest Gallery 后台访问权。32.0.1 的角色检查允许管理员、编辑和作者进入相关功能;作者角色已成功触发漏洞,订阅者请求则收到 HTTP 403 和 MISSINGRIGHTS。贡献者被源码中的角色检查排除,但没有单独的利用测试。CVE 记录中的“未认证文件上传”和“订阅者级别访问”表述,不能由这条已确认的路径支持;没有已确认的未认证入口。

请求还必须关联一个现有图库,且该图库在数据库中保存的版本值满足 dbVersion >= 10,处理流程才会进入受影响分支。这里的版本值用于选择图库处理逻辑,不是插件发行版本号。

目标必须是已存在、且 Web 服务账户同时具有读取和写入权限的文件。任意路径选择并不意味着能够无视文件系统权限,也不意味着攻击者可以自由替换文件的每个字节。要把受控内容插入文件,目标中还需存在处理函数能够匹配的标题或分享元数据标记行。

从请求参数到文件覆盖

请求通过 WordPress 的 /wp-admin/admin-ajax.php 进入 post_contest_gallery_action_ajax 后台处理动作。处理函数先调用 cg_require_backend_access() 检查后台访问权,再检查 cg_nonce 和 cgBackendHash。前者是请求校验令牌,后者是与 WordPress 认证盐值关联的校验值;拥有插件后台访问权的用户可以从后台页面的隐藏字段中取得两者。这些检查限制了谁能进入功能,却没有约束获准用户最终可以改写哪个文件。

图库版本和编辑操作条件满足后,流程进入 v10/v10-admin/gallery/change-gallery/1_content-fb-like.php。该文件把 fbcontent[<imageId>][baseUrlForFacebook] 直接当作服务端文件路径使用。虽然参数名称含有 Url,它在这里控制的是本地文件读写目标。代码仅用 file_exists() 确认目标存在,没有把路径限制在图库预期的文件范围内。

处理函数打开目标后,用 fgets() 逐行读取,并将首个包含 <title> 的整行替换成新的标题元素。新标题来自攻击者控制的 fbcontent[<imageId>][title],没有经过清理或 HTML 转义,因此输入中的特殊语法不会被可靠地当作普通文本保留。

后续逻辑还会改写 Open Graph 标记。Open Graph 是网页供社交平台提取分享标题、描述等信息的一组元数据,其中 og:title 表示分享标题,og:description 表示分享描述。代码替换 og:title 所在行后,无条件用新的 og:description 元素替换紧接着的一行,并替换首个 version=v2.8 字符串;其他行则被保留。由于替换以整行为单位,标题行上的其他内容,以及紧随分享标题之后的原有一行,也可能被删除。

重建内容后,代码用 fopen(..., 'w') 打开同一个目标,再通过 fwrite() 写回。写入模式会截断原文件,然后写入重建结果。漏洞的核心是:攻击者能够在文件系统权限允许的范围内任意选择现有目标,而内容控制限于上述改写位置。后台权限校验没有补上文件路径边界,未经安全处理的替换内容又使这次越界写入具备了进一步利用的条件。

文件覆盖何时变成代码执行

远程代码执行需要目标同时满足两个额外条件:改写位置能够容纳有效的 PHP 代码,且该 PHP 文件能通过 HTTP 请求交给 PHP 解释器执行。已演示的目标是 WordPress 核心端点 /wp-links-opml.php。攻击者先通过已认证的后台请求修改文件,再请求该端点执行注入内容;后续 GET 请求本身无需认证,但首次文件修改仍然依赖前述后台权限。

WordPress 的 wp_magic_quotes() 会对 POST 输入中的引号和反斜杠进行转义,这会影响插入内容的实际语法。已演示的代码执行路径要求注入内容避开引号和反斜杠,以免转义破坏 PHP 语法。因此,能够控制标题字段不等于任意 PHP 片段都能直接执行。

注入代码以 Web 服务账户运行,已演示的执行身份为 www-data。代码能够读写该账户权限允许的文件;只要修改后的文件持续存在并被执行,后续请求就可能继续触发注入内容。现有结果没有证明主机或数据库权限提升,启用 OPcache(PHP 编译后代码缓存)或采用只读文件系统的部署也未纳入验证范围。

文件损坏和站点不可用是覆盖机制可能带来的后果,但没有逐项演示。即使目标无法执行 PHP,整行替换和截断写回仍可能破坏原有文件内容。

修复如何收紧写入范围

应将 Contest Gallery 升级到修复版本 33.0.0。该版本最关键的变化是由服务端确定输出位置:处理函数根据 wp_upload_dir() 提供的上传目录、图库标识和图片记录构造输出 HTML 文件路径,不再接受 POST 参数直接指定写入目标。

修复逻辑按 GalleryID 查询图片记录,检查经 realpath() 解析后的实际路径前缀,并拒绝图片名称字段 NamePic 中的斜杠、反斜杠和空字节。这些检查共同约束文件所属图库和目标位置,针对的是原先缺失的路径边界。

33.0.0 同时使用 sanitize_text_field() 清理文本,并用 esc_html() 和 esc_attr() 分别处理 HTML 文本与属性中的特殊字符,减少替换内容被解释为语法的机会。受影响文件还新增了用于检查 WordPress 加载环境的 ABSPATH 保护、fbcontent 数组类型检查,以及对 cg_require_backend_access() 的调用。路径来源约束和内容处理分别修复了“写到哪里”和“写入内容如何被解释”这两个问题;原有的登录、角色和请求校验不能替代它们。

参考资料