Skip to content

Security: Global Authorization Bypass via Ajax Method Name Convention #181

@lighthousekeeper1212

Description

@lighthousekeeper1212

Summary

During a security review, I identified a critical authorization bypass in ZenTao's permission system. Any authenticated user (including guest accounts and users with minimal permissions) can access ALL controller methods containing "ajax" in their name, bypassing the group-based ACL system entirely.

Root Cause

In module/common/model.php, line 456, the isOpenMethod() function contains:

if(stripos($method, 'ajax') !== false) return true;

This blanket check runs BEFORE any group/role-based permission verification (hasPriv()). If it returns true, the entire ACL system is bypassed. This affects approximately 338 out of 360 ajax-prefixed methods across 60+ modules, since only ~22 methods implement their own secondary permission checks.

Impact

The bypass exposes sensitive functionality including:

  1. Database credential disclosuresystem.ajaxDBAuthUrl returns full database credentials (host, port, username, password) as a URL. No admin check.

  2. Task/Document/Story data leakstask.ajaxGetByID, doc.ajaxGetDoc, story.ajaxGetDetail return full records via raw SELECT * without permission checks, while their non-ajax counterparts (task.view, doc.view, story.view) correctly check project/product access.

  3. CI/CD job executionmr.ajaxExecJob triggers pipeline jobs without permission checks.

  4. Database table modificationadmin.ajaxChangeTableEngine runs ALTER TABLE without admin checks.

  5. Webhook secret disclosurewebhook.ajaxGetFeishuDeptList exposes Feishu/Lark API credentials.

  6. AI token disclosurezai.ajaxGetToken returns AI integration tokens.

  7. Kanban card manipulationkanban.ajaxMoveCard directly updates story status/stage.

Contrast with Secure Code

Some ajax methods DO implement their own checks (showing developers are aware this is needed):

  • sso.ajaxSetConfig — checks $this->app->user->admin
  • instance.ajaxUninstall — checks hasPriv('space', 'browse')
  • instance.ajaxStop / ajaxStart — checks privileges

But the vast majority (~94%) do not, relying on the framework-level ACL that line 456 bypasses.

Recommended Fix

Primary: Remove line 456 from isOpenMethod():

-            if(stripos($method, 'ajax') !== false) return true;

Then add genuinely universal ajax methods (UI helpers like misc.ajaxIgnoreBrowser, tutorial.ajaxSetTasks) to the $config->logonMethods whitelist.

Secondary (defense-in-depth): Add explicit admin/privilege checks to high-sensitivity methods like system.ajaxDBAuthUrl, admin.ajaxChangeTableEngine, and mr.ajaxExecJob, regardless of the framework fix.

Disclosure

This report is submitted in good faith to help improve the security of ZenTao. I am available to provide additional details or clarification.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions