大洞速修--Typecho ≥ 1.2.0 全版本无条件rce

影响范围 = Typecho ≥ 1.2.0 全部版本(1.2.0 / 1.2.1 / 1.3.0 及其间 rc/beta)

漏洞涉及 Typecho 的异步服务组件(/action/service 接口),由以下两点构成:

成因一:松散比较导致令牌校验可被类型绕过

sha1() 函数恒返回 40 位非空十六进制字符串。令牌校验中 非空字符串 == true 在 PHP 松散比较语义下恒成立。当请求体中令牌字段传入布尔值 true 时,校验函数无条件返回真值,令牌的密码学防伪造意义被完全短路,等同于鉴权不存在。

成因二:异步服务将请求参数直接作为方法名分发

异步处理流程对请求中传入的"方法名"不做白名单约束。由于插件初始化方法为公开静态方法,PHP 允许在对象上下文直接调用,攻击者可先覆写进程内插件回调表为可控内容,随后调用任意方法名命中被注入的回调,最终经 call_user_func_array 执行任意 PHP 回调,形成"覆写回调表 + 任意代码执行"的完整利用原语。

前置条件:请求需携带目标站点同源 Referer 头(Typecho 原生同源校验要求),除此之外无任何凭据要求。

  • 未授权远程代码执行:任何能访问站点的人(携带同源 Referer)可获得 Web 服务用户身份的任意 PHP 回调执行、任意文件读写。
  • 敏感信息泄露:应用配置文件、系统文件等均可被读取。
  • 影响范围广:自 2022 年 4 月 1.2.0 发布至今四年余,官方未发布修复版本;当前最新版本仍受影响。
  • 利用门槛低:单个 HTTP 请求,无加密、无验证码依赖。
if (sha1($secret . '&' . $i) == $token) {
    return true;
}

PHP 里字符串和布尔值拿双等号比 字符串这头会先转布尔 而 sha1 出来永远是四十位非空串 转布尔永远是 true 那么你从 JSON 里传个布尔 true 进去 true 等于 true 恒成立 密钥是什么根本不用知道 校验形同虚设

第二个在 var/Widget/Service.php 的 asyncHandle 过了校验之后它会遍历你传的 requests 数组 拿数组里的方法名直接调插件对象 方法名完全可控 而 Plugin 类有个 public static 的 init 方法 传个数组进去就能把整个插件回调表替换掉 回调表里放什么函数名它就 call_user_func_array 执行什么 到这一步想干嘛就干嘛了

这个路由本身不挂任何鉴权 注册在 Action 的映射表里 匿名 POST 即达 也就是说这两个条件一叠 任何人都能在你博客上执行任意 PHP 函数

至于回调表是怎么被换掉的 Plugin 类的 init 方法是 public static 传个数组进去 self::$plugin 整个就换了 里面 handles 就是回调表

真正干活的是 Plugin 的魔术方法 __call 按回调表逐个 call_user_func_array 表里放什么函数名就执行什么

我自己博客实测打穿到落webshell RCE一路畅通 命令执行被 disable_functions 拦截了 但任意文件读取仍可执行
mvezhbq2.png

修复

修复一:令牌校验改恒时强比较

将 var/Typecho/Common.php 中时间令牌校验方法内的松散比较改为恒时强比较:

// 修改前
if (sha1($secret . '&' . $i) == $token) {

// 修改后
if (hash_equals(sha1($secret . '&' . $i), (string) $token)) {
hash_equals 兼具强类型比较与恒时特性,可同时防御类型绕过与时序侧信道。

修复二:异步服务入口增加令牌类型约束

在 var/Widget/Service.php 的两个令牌校验入口(异步请求处理方法与 Ping 处理方法)中,于校验前增加字符串类型判断:

// 修改前
$token = $data['token'] ?? '';
if (!Common::timeTokenValidate($token, $this->options->secret, 3)) {
    throw new Exception(_t('禁止访问'), 403);
}

// 修改后
$token = $data['token'] ?? '';
if (!is_string($token) || !Common::timeTokenValidate($token, $this->options->secret, 3)) {
    throw new Exception(_t('禁止访问'), 403);
}
以上两处最小补丁已在测试环境部署并回归验证:修复后鉴权绕过路径在两个入口均失效,未发现旁路。
评论区
头像
    头像
    Whitebear
      

    我选择直接把service文件里边action方法中那两个入口直接注释掉表情
    对我来说没啥用

    头像
    obaby
      

    每次出个漏洞就得折腾半天

      头像
      云烟
        
      @obaby

      这个洞挺严重的 如果涉及的话尽早修复吧

文章目录