Hall of Fame image from https://openclipart.org/detail/120343/trophy
Back to Hall of Fame Contents Back to Wekan Website

Contents / TokenBleed

CVE Vulnerability name Date Responsible Security Disclosure by Vulnerabilities
TokenBleed CVE requested

TokenBleed

2026-06-10 Zion Boggan (coordinated disclosure) and Claude

Did send detailed report with PoC!


Details

1. TokenBleed — unauthenticated login-token minting via un-awaited auth check (CWE-863, CWE-287)

Authentication.checkUserId in server/authentication.js is an async function. Both of its throws — the 401 when userId is undefined, and the 403 when the user is not an admin — therefore become rejected promises rather than synchronous exceptions.

The REST handler for POST /api/createtoken/:userId in server/models/users.js called the check without await, inside a plain synchronous try/catch:

WebApp.handlers.post('/api/createtoken/:userId', function(req, res) {
  try {
    Authentication.checkUserId(req.userId);            // not awaited — rejection is detached
    const id = req.params.userId;
    const token = Accounts._generateStampedLoginToken();
    Accounts._insertLoginToken(id, token);             // mints a login token for :userId
    sendJsonResult(res, { code: 200, data: { _id: id, authToken: token.token } });
  } catch (error) {
    sendJsonResult(res, { code: 200, data: error });
  }
});
    

A synchronous try/catch cannot catch a rejected promise, so the failed check never stopped execution. The handler went on to mint and return a usable login token for whatever user ID was in the URL. An unauthenticated request (req.userId is undefined) hits the 401 branch, but that throw is also async and detached, so the bypass worked with no credentials at all.

Impact: POST /api/createtoken/<admin-user-id> returned a usable authToken for that admin. An attacker could obtain a target admin's user ID from GET /api/users (exposed by the same problem), then mint an admin token and take over the instance. Precondition: REST API enabled (WITH_API=true), which is the default in the Snap and Docker builds.

2. Systemic detached-rejection bypass across the Admin REST API

The same pattern affected every other handler that called an async Authentication check without await from a synchronous try/catch. In server/models/users.js and server/models/boards.js these were:

POST /api/createtoken/:userId was the most serious because it hands out a working session.

Fix

Every async Authentication check is now awaited so a failed check rejects before any privileged code runs, and the two handlers that were not async (POST /api/users/ and POST /api/createtoken/:userId) were made async. The POST /api/boards/:boardId/copy check was also moved inside the handler's try/catch so a denied caller gets a clean error response.



Timeline Details
2026-06-10 Report received from Zion Boggan (coordinated disclosure, CVE requested).
Upcoming release Fixed at Upcoming WeKan release, by awaiting every async authentication check across the Admin REST API.


Back to Hall of Fame Contents Back to Wekan Website