
Una condivisione pubblica sembrava pulita nell'albero delle pagine, ma l'endpoint di ricerca raccontava una storia diversa. In Docmost, le pagine figlie limitate nascoste agli spettatori della condivisione pubblica potevano comunque trapelare attraverso i risultati di ricerca della condivisione pubblica.
Una condivisione pubblica sembrava pulita nell'albero delle pagine, ma l'endpoint di ricerca raccontava un'altra storia. In Docmost, le pagine figlie con restrizioni nascoste ai visitatori della condivisione pubblica potevano comunque trapelare attraverso i risultati di ricerca della condivisione pubblica.
Ho trovato questo problema durante la revisione di Docmost, una piattaforma wiki e documentazione collaborativa open-source, partendo da una domanda molto semplice:
Se una pagina è intenzionalmente nascosta a un visitatore della condivisione pubblica, ogni funzionalità pubblica rispetta lo stesso confine di restrizione?
In questo caso, la risposta è stata no.
Una pagina figlia con restrizione poteva rimanere nascosta nell'albero della condivisione pubblica mentre trapelava comunque attraverso l'endpoint di ricerca della condivisione pubblica.
Il problema è stato accettato e gli è stato assegnato CVE-2026-33146.
Docmost: Docmost su GitHub
CVE: CVE-2026-33146
Il sito ufficiale di Docmost lo presenta come un wiki on-premise pronto per le aziende con 3M+ download, e afferma che è utilizzato da team in organizzazioni tra cui Vilnius City, Bechtle, il Governo Australiano, la Croce Rossa e ETS Quebec.
condivisione pubblica genitore con sottopagine abilitate → discendente con restrizione omesso dall'albero pubblico → attore malintenzionato interroga la ricerca della condivisione pubblica → titolo e snippet del figlio con restrizione trapelano
Docmost è una piattaforma wiki e documentazione collaborativa.
Fornisce:
Ciò significa che il suo modello di condivisione pubblica è un vero confine di sicurezza.
La domanda importante qui non era se Docmost può condividere pagine pubblicamente.
La vera domanda era:
Quando Docmost decide che una pagina discendente è limitata e non deve apparire a un visitatore della condivisione pubblica, quella restrizione è valida ovunque nel flusso di condivisione pubblica?
In questo caso, non lo era.
Molte revisioni di sicurezza si fermano troppo presto quando vedono una pagina nascosta nell'interfaccia utente.
Questo non basta.
La domanda più forte è questa:
Ogni percorso backend impone la stessa decisione di visibilità?
Questo è importante perché i confini di sicurezza non sono definiti dall'aspetto dell'interfaccia.
Sono definiti da ciò che il server restituisce effettivamente.
Qui, l'endpoint dell'albero pubblico si è comportato in modo sicuro:
Ma il percorso di ricerca della condivisione pubblica si è comportato diversamente:
Questo ha reso il problema una reale violazione dell'autorizzazione e della divulgazione di informazioni, non solo un'incoerenza di presentazione.
Non ho affrontato Docmost facendo fuzzing casuale degli endpoint sperando che apparisse qualcosa di interessante.
La strada più solida era scegliere prima un confine di fiducia.
Per le applicazioni che supportano:
una delle migliori domande da porsi è:
Il livello di ricerca impone esattamente lo stesso confine di autorizzazione del livello di navigazione?
Questa domanda diventa particolarmente preziosa quando:
È esattamente qui che si è manifestato questo problema.
Il bug non era che Docmost non riusciva a nascondere la pagina con restrizione nell'albero pubblico normale.
Il bug era che la ricerca pubblica non onorava la stessa logica di restrizione.
Dalla revisione del codice sorgente, il flusso dell'albero pubblico utilizzava la traversata dei discendenti consapevole delle restrizioni.
Area rilevante:
apps/server/src/core/share/share.service.tsQuel percorso escludeva intenzionalmente i discendenti con restrizione utilizzando:
getPageAndDescendantsExcludingRestricted(...)Ma il flusso di ricerca della condivisione pubblica seguiva un percorso diverso.
Aree rilevanti:
apps/server/src/core/search/search.controller.tsapps/server/src/core/search/search.service.tsLì, il codice raccoglieva i discendenti utilizzando:
getPageAndDescendants(...)Ciò significa che i discendenti con restrizione rimanevano nel campo d'azione per la ricerca.
Nel contesto della condivisione pubblica, questo è molto importante perché il ramo di ricerca viene eseguito senza un contesto di autorizzazione utente autenticato normale. Quindi, una volta che i discendenti con restrizione erano inclusi nel set di pagine ricercabili, i loro metadati potevano trapelare attraverso la risposta.
Perché l'attaccante non ha bisogno di un account autenticato.
Ha solo bisogno di:
Una volta che questa condizione esiste, un visitatore pubblico può interrogare l'endpoint di ricerca della condivisione e recuperare:
Questo è sufficiente per creare una fuga di informazioni, anche se l'intero corpo della pagina non viene restituito.
La distinzione importante è che l'applicazione segnala già chiaramente il modello di sicurezza previsto.
L'endpoint dell'albero pubblico nasconde i discendenti con restrizione.
Quindi la vera domanda non è:
"La ricerca restituisce per caso un insieme di risultati più ampio?"
La vera domanda è:
"La ricerca sta violando una decisione di autorizzazione già applicata altrove per lo stesso confine di condivisione pubblica?"
In Docmost, sì.
Questo trasforma il problema da:
a:
Ecco perché è una vulnerabilità reale.
Ho validato il problema confrontando i due endpoint pubblici rilevanti fianco a fianco.
Prima, ho testato l'endpoint normale dell'albero pubblico utilizzando la chiave di condivisione pubblica.
Richiesta di esempio:
POST /api/shares/tree HTTP/1.1
Host: 127.0.0.1:6752
Content-Type: application/json
{
"shareId": "public-share-key"
}
La risposta restituiva solo la pagina figlia pubblica nell'albero delle pagine.
Risultato rappresentativo:
{
"pageTree": [
{
"id": "public-child",
"title": "Public roadmap"
}
]
}
Questo ha stabilito il comportamento atteso del prodotto:
Ho quindi interrogato l'endpoint di ricerca della condivisione pubblica utilizzando un termine che appariva all'interno del discendente con restrizione.
Richiesta di esempio: