Skip to content
Wall of Fame
critical · 9.8CVE-2026-36755OH-2026-001

Authentication Bypass via Missing Await in TUS Upload Endpoint

Product
Papermark
Vendor
Papermark (mfts/papermark)
Affected versions
<= v0.22.0
Fixed in
Patched in main (no release yet)
CVSS
9.8
Published
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Summary

A missing await keyword on getServerSession() in Papermark's TUS file upload endpoint causes a complete authentication bypass, allowing unauthenticated attackers to upload arbitrary files up to 2 GB.

Description

The TUS (resumable file upload) endpoint at pages/api/file/tus/[[...file]].ts in Papermark calls getServerSession() without the await keyword. Since getServerSession() is an async function, it returns a Promise object rather than the resolved session value. In JavaScript, a Promise object is always truthy regardless of whether the session actually exists. As a result, the if (!session) guard never triggers, and all requests — including completely unauthenticated ones — are passed through to the TUS upload server.

The TUS server is configured with maxSize: 2 GiB, meaning an attacker can upload files up to 2 GB in size without any credentials. The upload metadata includes a teamId field, which is also not validated against the (non-existent) session, allowing uploads to be associated with any team.

Technical Details

The vulnerable code at line 104 of pages/api/file/tus/[[...file]].ts:

// VULNERABLE: missing await
const session = getServerSession(req, res, authOptions);
if (!session) {
  return res.status(401).json({ message: "Unauthorized" });
}
return tusServer.handle(req, res);

getServerSession() returns Promise<Session | null>. Without await, the variable session holds the Promise object itself, which is always truthy. The authentication check !session evaluates to false in all cases, so execution always falls through to tusServer.handle().

Exploitation is trivial using the standard TUS resumable upload protocol — a POST request with Tus-Resumable: 1.0.0 and Upload-Length headers initiates an upload, followed by PATCH requests to stream file data. No cookies, tokens, or authentication headers are required.

Remediation

Add the await keyword before getServerSession() and ensure the handler function is async:

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const session = await getServerSession(req, res, authOptions);
  if (!session) {
    return res.status(401).json({ message: "Unauthorized" });
  }
  return tusServer.handle(req, res);
}

Additional hardening: - Validate that the authenticated user belongs to the team specified in the upload metadata - Implement per-user upload rate limiting - Add content-type validation / file type allowlisting

Timeline

  1. Vulnerability discovered by OpenHack
  2. Vendor pushed fix to GitHub
  3. Advisory published

References

Credit

Ananay Arora at OpenHack Security Research