
Ein Docmost-Benutzer mit niedrigen Berechtigungen könnte eine Opfer-attachmentId an den generischen Upload-Endpunkt übergeben und das gespeicherte Attachment einer anderen Seite innerhalb desselben Workspace überschreiben.
Ein niedrig privilegierter Docmost-Benutzer konnte eine Opfer-attachmentId an den generischen Upload-Endpunkt übergeben und ein gespeichertes Attachment einer anderen Seite innerhalb desselben Workspace überschreiben.
Ich habe einen kritischen Autorisierungsfehler in Docmost, der quelloffenen kollaborativen Dokumentationsplattform, identifiziert, verantwortungsvoll offengelegt und reproduziert.
Die offizielle Website von Docmost präsentiert es als unternehmensgerechtes On-Premises-Wiki mit über 3 Millionen Downloads und gibt an, dass es von Teams in Organisationen wie Vilnius City, Bechtle, der australischen Regierung, dem Roten Kreuz und ETS Quebec verwendet wird.
Der Fehler befand sich im generischen Datei-Upload-Pfad, den Docmost auch für Diagramm-Speicher-/Update-Abläufe verwendet.
Ich habe diesen Code mit einer ganz bestimmten Frage überprüft:
Was passiert, wenn der Upload-Endpunkt die Bearbeitungsberechtigung für eine Seite nachweist, das Überschreibungsziel jedoch mit einer separat vom Benutzer gesteuerten Attachment-ID ausgewählt wird?
In diesem Fall führte diese Frage direkt zu einem echten Object-Binding-Fehler.
Docmost erlaubte einem Aufrufer, Folgendes zu senden:
pageId für eine Seite, die er bearbeiten durfte, undattachmentId, die zu einer anderen Seite im selben Workspace gehörteDer Server führte zwar eine Konsistenzprüfung für das Überschreiben durch, aber die Absicherung verwendete die falsche boolesche Logik.
Das bedeutete, dass die Anfrage die Autorisierung bestehen und trotzdem das Opfer-Attachment überschreiben konnte.
Dieses Problem wurde als CVE-2026-34213 eingestuft.
Docmost: docmost/docmost
Advisory: GHSA-89fp-2hch-j9gp
CVE: CVE-2026-34213
Behoben in: v0.71.0
---
vom Angreifer kontrollierte pageId mit Bearbeitungszugriff -> vom Angreifer kontrollierte Opfer-attachmentId -> fehlerhafte Überschreibungsabsicherung behandelt seitenübergreifendes Überschreiben als gültig -> Speicherpfad aus Opfer-attachmentId neu aufgebaut -> Angreifer-Bytes ersetzen Opfer-Datei -> Opferseite stellt weiterhin modifiziertes Attachment bereit
Docmost speichert hochgeladene Seiten-Attachments als Datenbankeinträge sowie als zugehörige Dateien im Speicher.
Bei normalen Uploads erstellt der Server eine neue Attachment-ID und schreibt eine neue Datei.
Bei Diagramm-Speicher-/Update-Abläufen hingegen verwendet der Client absichtlich eine vorhandene attachmentId wieder, sodass dieselbe Diagrammdatei direkt aktualisiert werden kann, anstatt jedes Mal einen neuen Attachment-Datensatz zu erzeugen.
Dieses Verhalten ist an sich legitim.
Das Problem besteht darin, dass es einen risikoreichen Pfad schafft:
Immer wenn ein Endpunkt diese beiden Verantwortlichkeiten vermischt, muss die Implementierung sie exakt miteinander verknüpfen.
Docmost tat das nicht.
Gemischte Erstellungs-/Update-Endpunkte sind häufige Stellen für Autorisierungsfehler.
Der Grund ist einfach:
Genau dieses Muster liegt hier vor.
POST /api/files/upload validierte, ob der Aufrufer die durch pageId benannte Seite bearbeiten durfte.
Wenn jedoch auch attachmentId angegeben wurde, wechselte der Server in einen Überschreibungspfad und wählte einen bestehenden Attachment-Datensatz separat aus.
Dadurch ergab sich die kritische Sicherheitsfrage:
Beweist der Überschreibungspfad, dass das ausgewählte Attachment tatsächlich zur autorisierten Seite gehört?
Die Antwort in verletzlichen Versionen war nein.
Die Grundursache war ein Autorisierungsumgehung durch einen benutzergesteuerten Schlüssel in Kombination mit einem booleschen Logikfehler in der Überschreibungsabsicherung.
Der verletzliche Ablauf sah wie folgt aus:
AttachmentController.uploadFile() las pageId aus den Multipart-Formulardaten.validateCanEdit(page, user) auf.attachmentId aus derselben Anfrage.AttachmentService.uploadFile() lud das vorhandene Attachment anhand dieser vom Angreifer bereitgestellten ID.&& anstatt bei jeder Nichtübereinstimmung abzulehnen.Die verletzliche Absicherung war:
if (
existingAttachment.pageId !== pageId &&
existingAttachment.fileExt !== preparedFile.fileExtension &&
existingAttachment.workspaceId !== workspaceId
) {
throw new BadRequestException("File attachment does not match");
}
Diese Bedingung lehnte die Anfrage nur ab, wenn:
alles gleichzeitig.
Das ist das Gegenteil dessen, was eine Überschreibungsabsicherung tun sollte.
Im tatsächlichen Angriffsfall blieb der Angreifer absichtlich im selben Workspace.
Also:
existingAttachment.workspaceId !== workspaceId war falseSobald dieser Operand falsch wurde, ergab die gesamte &&-Bedingung false, selbst wenn das Attachment zu einer anderen Seite gehörte.
Der Server behandelte also ein seitenübergreifendes Überschreiben als gültig.
Das war die erste Hälfte des Fehlers.
Die zweite Hälfte machte die Auswirkung real.
Nach der Prüfung baute der Dienst den Zielspeicherpfad unter Verwendung der vom Angreifer bereitgestellten attachmentId und des Dateinamens neu auf:
const filePath =
`${getAttachmentFolderPath(AttachmentType.File, workspaceId)}/` +
`${attachmentId}/${preparedFile.fileName}`;
Dann aktualisierte Docmost im Update-Pfad nur änderbare Metadaten wie:
fileSizeupdatedAtEs verknüpfte das Eigentum nicht neu mit der Angreiferseite.
Die Opferseite verwies also weiterhin auf denselben Attachment-Datensatz und dieselbe Attachment-ID. Lediglich die zugrunde liegenden Datei-Bytes änderten sich.
Deshalb handelte es sich nicht um eine harmlose Nichtübereinstimmung.
Es war ein persistenter, unbefugter Überschreibungsprimitiv.
Dies war kein kosmetischer Fehler und kein Problem einer Dateinamenskollision.
Der Angreifer benötigte kein Wettrennen. Der Angreifer musste keinen zufälligen Pfad erraten. Der Angreifer benötigte keinen Schreibzugriff auf die Opferseite.
Er benötigte nur:
Von dort aus konnte er die gespeicherten Datei-Bytes für ein Attachment einer anderen Seite ersetzen, während die Opferseite weiterhin auf dieses Attachment verwies und es bereitstellte, als ob sich nichts geändert hätte.
Das ist ein direkter Integritätsfehler.
Praktisch könnte der Angreifer: