
Un utente Docmost con privilegi bassi potrebbe fornire un attachmentId vittima all'endpoint di upload generico e sovrascrivere l'allegato memorizzato di un'altra pagina all'interno dello stesso spazio di lavoro.
Un utente Docmost con privilegi ridotti potrebbe fornire un attachmentId vittima all'endpoint di upload generico e sovrascrivere un allegato memorizzato di un'altra pagina nello stesso spazio di lavoro.
Ho identificato, divulgato in modo responsabile e riprodotto un difetto di autorizzazione di Alta gravità in Docmost, la piattaforma collaborativa open-source per la documentazione.
Il sito ufficiale di Docmost la presenta come un wiki aziendale on-premise con oltre 3 milioni di download, e afferma che è utilizzata da team di organizzazioni tra cui Vilnius City, Bechtle, il Governo Australiano, la Croce Rossa e ETS Quebec.
Il bug risiedeva nel percorso generico di upload dei file che Docmost utilizza anche per i flussi di salvataggio/aggiornamento dei diagrammi.
Stavo esaminando quel codice con una domanda molto specifica in mente:
Cosa succede se l'endpoint di upload verifica l'accesso in modifica su una pagina, ma il target di sovrascrittura viene selezionato con un ID allegato controllato dall'utente separato?
In questo caso, quella domanda ha portato direttamente a un reale fallimento del binding degli oggetti.
Docmost permetteva a un chiamante di inviare:
pageId per una pagina a cui era consentito modificare, eattachmentId appartenente a una pagina diversa nello stesso spazio di lavoroIl server eseguiva un controllo di consistenza della sovrascrittura, ma la guardia utilizzava la logica booleana sbagliata.
Ciò significava che la richiesta poteva superare l'autorizzazione e sovrascrivere comunque l'allegato vittima.
Questo problema è diventato CVE-2026-34213.
Docmost: docmost/docmost
Avviso: GHSA-89fp-2hch-j9gp
CVE: CVE-2026-34213
Corretto in: v0.71.0
---
pageId controllato dall'attaccante con accesso in modifica -> attachmentId vittima controllato dall'attaccante -> guardia di sovrascrittura difettosa considera valida la sovrascrittura tra pagine -> percorso di archiviazione ricostruito dall'attaccanteId vittima -> byte dell'attaccante sostituiscono il file vittima -> la pagina vittima continua a servire l'allegato modificato
Docmost memorizza gli allegati delle pagine caricati come record di database più file di backup nell'archiviazione.
Per i caricamenti normali, il server crea un nuovo ID allegato e scrive un nuovo file.
Per i flussi di salvataggio/aggiornamento dei diagrammi, invece, il client riutilizza intenzionalmente un attachmentId esistente in modo che lo stesso file di diagramma possa essere aggiornato sul posto invece di generare un nuovo record di allegato ogni volta.
Questo comportamento è legittimo di per sé.
Il problema è che crea un percorso ad alto rischio:
Ogni volta che un endpoint combina queste due responsabilità, l'implementazione deve vincolarle esattamente tra loro.
Docmost non lo ha fatto.
Gli endpoint misti di creazione/aggiornamento sono luoghi comuni per i bug di autorizzazione.
Il motivo è semplice:
Questo è esattamente lo schema qui.
POST /api/files/upload convalidava che il chiamante potesse modificare la pagina denominata da pageId.
Ma se veniva fornito anche attachmentId, il server passava a un percorso di sovrascrittura e selezionava un record di allegato esistente separatamente.
Questo rendeva la domanda di sicurezza critica:
il percorso di sovrascrittura dimostra che l'allegato selezionato appartiene effettivamente alla pagina autorizzata?
La risposta nelle versioni vulnerabili era no.
La causa principale era un bypass dell'autorizzazione tramite una chiave controllata dall'utente, combinato con un bug di logica booleana nella guardia di sovrascrittura.
Il flusso vulnerabile appariva così:
AttachmentController.uploadFile() leggeva pageId dai dati del modulo multipart.validateCanEdit(page, user).attachmentId opzionale dalla stessa richiesta.AttachmentService.uploadFile() caricava l'allegato esistente tramite quell'ID fornito dall'attaccante.&& invece di rifiutare in caso di mancata corrispondenza.La guardia vulnerabile era:
if (
existingAttachment.pageId !== pageId &&
existingAttachment.fileExt !== preparedFile.fileExtension &&
existingAttachment.workspaceId !== workspaceId
) {
throw new BadRequestException("File attachment does not match");
}
Quella condizione rifiutava la richiesta solo se:
tutti contemporaneamente.
Questo è l'opposto di ciò che una guardia di sovrascrittura dovrebbe fare.
Nel caso reale dell'attacco, l'attaccante rimaneva intenzionalmente all'interno dello stesso spazio di lavoro.
Quindi:
existingAttachment.workspaceId !== workspaceId era falseUna volta che quell'operando diventava falso, l'intera condizione && veniva valutata come falsa, anche se l'allegato apparteneva a una pagina diversa.
Quindi il server trattava una sovrascrittura tra pagine come valida.
Questa era la prima metà del bug.
La seconda metà è ciò che ha reso reale l'impatto.
Dopo il controllo, il servizio ricostruiva il percorso di destinazione dell'archiviazione usando l'attachmentId e il nome file forniti dall'attaccante:
const filePath =
`${getAttachmentFolderPath(AttachmentType.File, workspaceId)}/` +
`${attachmentId}/${preparedFile.fileName}`;
Poi, sul percorso di aggiornamento, Docmost aggiornava solo i metadati mutabili come:
fileSizeupdatedAtNon riassociava la proprietà alla pagina dell'attaccante.
Quindi la pagina vittima continuava a puntare allo stesso record di allegato e allo stesso ID allegato. Solo i byte del file sottostante cambiavano.
Ecco perché non si trattava di una mancata corrispondenza innocua.
Era una primitiva di sovrascrittura non autorizzata persistente.
Non era un bug estetico né un problema di collisione di nomi file.
L'attaccante non aveva bisogno di una race condition. L'attaccante non doveva indovinare un percorso casuale. L'attaccante non aveva bisogno di accesso in scrittura alla pagina vittima.
Aveva bisogno solo di:
Da lì, poteva sostituire i byte del file memorizzato per l'allegato di un'altra pagina mentre la pagina vittima continuava a fare riferimento e servire quell'allegato come se nulla fosse cambiato.
Questo è un fallimento diretto dell'integrità.
In termini pratici, l'attaccante poteva: