Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-34213 — A low-privileged Docmost user could supply a victim attachmentId to the generic upload endpoint and overwrite another page's stored attachment inside the same workspace. | Kitploit
Tools/GitHubGitHub/0xmrma/cve-2026-34213
Vulnerability AnalysisWeb Application ExploitationPenetration TestingPapers & ResearchLearning & Education
GitHub0xmrma/cve-2026-34213

CVE-2026-34213

A low-privileged Docmost user could supply a victim attachmentId to the generic upload endpoint and overwrite another page's stored attachment inside the same workspace.

View Repository
533 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-34213

A low-privileged Docmost user could supply a victim attachmentId to the generic upload endpoint and overwrite another page's stored attachment inside the same workspace.

Intro

I identified, responsibly disclosed, and reproduced a High-severity authorization flaw in Docmost, the open-source collaborative documentation platform.

Docmost’s official site presents it as an enterprise-ready on-premises wiki with 3M+ downloads, and says it is trusted by teams at organizations including Vilnius City, Bechtle, the Australian Government, the Red Cross, and ETS Quebec.

The bug lived in the generic file-upload path that Docmost also uses for diagram save/update flows.

I was reviewing that code with a very specific question in mind:

What happens if the upload endpoint proves edit access on one page, but the overwrite target is selected with a separate user-controlled attachment ID?

In this case, that question led directly to a real object-binding failure.

Docmost allowed a caller to send:

  • a pageId for a page they were allowed to edit, and
  • an attachmentId belonging to a different page in the same workspace

The server did perform an overwrite consistency check, but the guard used the wrong boolean logic.

That meant the request could pass authorization and overwrite the victim attachment anyway.

This issue became CVE-2026-34213.

Docmost: docmost/docmost
Advisory: GHSA-89fp-2hch-j9gp
CVE: CVE-2026-34213
Patched in: v0.71.0

photo0 ---

Attack Chain

attacker-controlled pageId with edit access -> attacker-controlled victim attachmentId -> flawed overwrite guard treats cross-page overwrite as valid -> storage path rebuilt from victim attachmentId -> attacker bytes replace victim file -> victim page continues serving modified attachment


What This Part of Docmost Does

Docmost stores uploaded page attachments as database records plus backing files in storage.

For normal uploads, the server creates a fresh attachment ID and writes a new file.

For diagram save/update flows, however, the client intentionally reuses an existing attachmentId so the same diagram file can be updated in place instead of generating a brand-new attachment record every time.

That behavior is legitimate on its own.

The problem is that it creates a high-risk path:

  • one input identifies the page being authorized
  • another input identifies the attachment being overwritten

Whenever an endpoint mixes those two responsibilities, the implementation must bind them together exactly.

Docmost did not.


Why This Surface Was Worth Looking At

Mixed create/update endpoints are common places for authorization bugs.

The reason is simple:

  • create flows are usually authorized against the container object
  • update flows are usually authorized against the existing record
  • if one endpoint tries to do both, it is easy to validate the wrong thing first and treat the second identifier as "just metadata"

That is exactly the pattern here.

POST /api/files/upload validated that the caller could edit the page named by pageId.

But if attachmentId was also supplied, the server switched into an overwrite path and selected an existing attachment record separately.

That made the critical security question:

does the overwrite path prove that the selected attachment actually belongs to the authorized page?

The answer in vulnerable versions was no.


Root Cause

The root cause was an authorization bypass through a user-controlled key, combined with a boolean logic bug in the overwrite guard.

The vulnerable flow looked like this:

  1. AttachmentController.uploadFile() read pageId from multipart form data.
  2. It loaded that page and called validateCanEdit(page, user).
  3. It separately accepted an optional attachmentId from the same request.
  4. AttachmentService.uploadFile() loaded the existing attachment by that attacker-supplied ID.
  5. The overwrite guard attempted to verify the existing attachment matched the authorized page.
  6. The guard used && instead of rejecting on any mismatch.

The vulnerable guard was:

if (
  existingAttachment.pageId !== pageId &&
  existingAttachment.fileExt !== preparedFile.fileExtension &&
  existingAttachment.workspaceId !== workspaceId
) {
  throw new BadRequestException("File attachment does not match");
}

That condition only rejected the request if:

  • the page ID mismatched, and
  • the file extension mismatched, and
  • the workspace ID mismatched

all at the same time.

That is the opposite of what an overwrite guard should do.

For the real attack case, the attacker intentionally stayed inside the same workspace.

So:

  • existingAttachment.workspaceId !== workspaceId was false

Once that operand became false, the whole && condition evaluated to false, even if the attachment belonged to a different page.

So the server treated a cross-page overwrite as valid.

That was the first half of the bug.

The second half is what made the impact real.

After the check, the service rebuilt the destination storage path using the attacker-supplied attachmentId and filename:

const filePath =
  `${getAttachmentFolderPath(AttachmentType.File, workspaceId)}/` +
  `${attachmentId}/${preparedFile.fileName}`;

Then, on the update path, Docmost only updated mutable metadata such as:

  • fileSize
  • updatedAt

It did not rebind ownership to the attacker page.

So the victim page kept pointing at the same attachment record and the same attachment ID. Only the underlying file bytes changed.

That is why this was not a harmless mismatch.

It was a persistent unauthorized overwrite primitive.


Why This Is a Security Issue, Not Just a Logic Mistake

This was not a cosmetic bug and not a filename collision issue.

The attacker did not need a race. The attacker did not need to guess a random path. The attacker did not need write access to the victim page.

They only needed:

  • read access to learn a victim attachment reference, and
  • write access to any other page in the same workspace

From there, they could replace the stored file bytes for another page's attachment while the victim page continued to reference and serve that attachment as if nothing had changed.

That is a direct integrity failure.

In practical terms, the attacker could:

  • tamper with diagrams
  • replace attachments with misleading content
  • corrupt referenced files
  • create confusing audit trails because the attachment still appeared to belong to the victim page

The important point is this:

the server accepted an overwrite target chosen by the attacker, without binding it to the page whose edit permission had actually been checked.

That is an access-control failure, not just bad boolean hygiene.


Why Exploitation Was Practical

The exploit was especially practical for diagram attachments.

Docmost's client intentionally reuses attachmentId for diagram saves and uses deterministic filenames:

  • diagram.excalidraw.svg
  • diagram.drawio.svg

That matters because it lowers the attacker's requirements.

For generic attachments, the attacker needs both:

  • the victim attachment ID
  • the victim filename

For diagrams, the filename is already predictable.

So if the attacker can read the victim page content, they can often recover the only missing piece they need:

  • the victim attachmentId

In my validation setup, I used exactly that path:

  • the attacker had only reader access to the victim space
  • the attacker had writer access to a different attacker-controlled space
  • both spaces belonged to the same workspace
Download Tool