Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-34213 — Un utilisateur Docmost à faibles privilèges pourrait fournir un `attachmentId` de victime au point de terminaison de téléchargement générique et écraser la pièce jointe stockée d'une autre page dans le même espace de travail. | Kitploit
Outils/GitHubGitHub/0xmrma/cve-2026-34213
Analyse des VulnérabilitésExploitation d'Applications WebTests d'IntrusionArticles et RechercheApprentissage et Éducation
GitHub0xmrma/cve-2026-34213

CVE-2026-34213

Un utilisateur Docmost à faibles privilèges pourrait fournir un `attachmentId` de victime au point de terminaison de téléchargement générique et écraser la pièce jointe stockée d'une autre page dans le même espace de travail.

Voir le dépôt
44il y a 2 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-34213

Un utilisateur Docmost à faibles privilèges pourrait fournir un attachmentId victime au point de terminaison de téléversement générique et écraser la pièce jointe stockée d'une autre page dans le même espace de travail.

Introduction

J'ai identifié, divulgué de manière responsable et reproduit un défaut d'autorisation de Haute gravité dans Docmost, la plateforme collaborative de documentation open‑source.

Le site officiel de Docmost la présente comme un wiki sur site prêt pour l'entreprise avec 3M+ téléchargements, et affirme qu'elle est utilisée par des équipes d'organisations dont Vilnius City, Bechtle, le Gouvernement australien, la Croix‑Rouge et ETS Québec.

Le bogue vivait dans le chemin générique de téléversement de fichiers que Docmost utilise également pour les flux de sauvegarde/mise à jour de diagrammes.

J'examinais ce code avec une question très spécifique en tête :

Que se passe‑t‑il si le point de terminaison de téléversement prouve l'accès en modification sur une page, mais que la cible d'écrasement est sélectionnée avec un identifiant de pièce jointe distinct contrôlé par l'utilisateur ?

Dans ce cas, cette question a directement conduit à une véritable défaillance de liaison d'objet.

Docmost permettait à un appelant d'envoyer :

  • un pageId pour une page qu'il était autorisé à modifier, et
  • un attachmentId appartenant à une page différente dans le même espace de travail
---

Le serveur effectuait bien un contrôle de cohérence d'écrasement, mais la garde utilisait la mauvaise logique booléenne.

Cela signifiait que la requête pouvait passer l'autorisation et écraser la pièce jointe victime quand même.

Ce problème est devenu CVE-2026-34213.

Docmost : docmost/docmost
Avis : GHSA-89fp-2hch-j9gp
CVE : CVE-2026-34213
Corrigé dans : v0.71.0

photo0

Chaîne d'attaque

pageId contrôlé par l'attaquant avec accès en modification -> victim attachmentId contrôlé par l'attaquant -> garde d'écrasement défaillante traite l'écrasement inter‑pages comme valide -> chemin de stockage reconstruit à partir du victim attachmentId -> les octets de l'attaquant remplacent le fichier victime -> la page victime continue de servir la pièce jointe modifiée


Ce que fait cette partie de Docmost

Docmost stocke les pièces jointes téléversées sous forme d'enregistrements en base de données plus des fichiers de sauvegarde dans le stockage.

Pour les téléversements normaux, le serveur crée un nouvel identifiant de pièce jointe et écrit un nouveau fichier.

Pour les flux de sauvegarde/mise à jour de diagrammes, cependant, le client réutilise intentionnellement un attachmentId existant afin que le même fichier de diagramme puisse être mis à jour sur place au lieu de générer un tout nouvel enregistrement de pièce jointe à chaque fois.

Ce comportement est légitime en soi.

Le problème est qu'il crée un chemin à haut risque :

  • une entrée identifie la page en cours d'autorisation
  • une autre entrée identifie la pièce jointe en cours d'écrasement

Chaque fois qu'un point de terminaison mélange ces deux responsabilités, l'implémentation doit les lier exactement.

Docmost ne l'a pas fait.


Pourquoi cette surface méritait d'être examinée

Les points de terminaison mixtes création/mise à jour sont des endroits courants pour les bogues d'autorisation.

La raison est simple :

  • les flux de création sont généralement autorisés par rapport à l'objet conteneur
  • les flux de mise à jour sont généralement autorisés par rapport à l'enregistrement existant
  • si un point de terminaison essaie de faire les deux, il est facile de valider la mauvaise chose en premier et de traiter le deuxième identifiant comme « simple métadonnée »

C'est exactement le schéma ici.

POST /api/files/upload validait que l'appelant pouvait modifier la page nommée par pageId.

Mais si attachmentId était également fourni, le serveur basculait vers un chemin d'écrasement et sélectionnait un enregistrement de pièce jointe existant séparément.

Cela posait la question de sécurité critique :

le chemin d'écrasement prouve‑t‑il que la pièce jointe sélectionnée appartient bien à la page autorisée ?

La réponse dans les versions vulnérables était non.


Cause racine

La cause racine était un contournement d'autorisation via une clé contrôlée par l'utilisateur, combiné à un bogue de logique booléenne dans la garde d'écrasement.

Le flux vulnérable ressemblait à ceci :

  1. AttachmentController.uploadFile() lisait pageId à partir des données de formulaire multipart.
  2. Il chargeait cette page et appelait validateCanEdit(page, user).
  3. Il acceptait séparément un attachmentId optionnel depuis la même requête.
  4. AttachmentService.uploadFile() chargeait la pièce jointe existante via cet identifiant fourni par l'attaquant.
  5. La garde d'écrasement tentait de vérifier que la pièce jointe existante correspondait à la page autorisée.
  6. La garde utilisait && au lieu de rejeter sur toute non‑correspondance.

La garde vulnérable était :

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

Cette condition ne rejetait la requête que si :

  • l'identifiant de page ne correspondait pas, et
  • l'extension de fichier ne correspondait pas, et
  • l'identifiant d'espace de travail ne correspondait pas

tous en même temps.

C'est l'inverse de ce qu'une garde d'écrasement devrait faire.

Dans le cas d'attaque réel, l'attaquant restait intentionnellement dans le même espace de travail.

Donc :

  • existingAttachment.workspaceId !== workspaceId était false

Une fois que cet opérande devenait false, toute la condition && s'évaluait à false, même si la pièce jointe appartenait à une page différente.

Ainsi, le serveur traitait un écrasement inter‑pages comme valide.

C'était la première moitié du bogue.

La seconde moitié est ce qui rend l'impact réel.

Après la vérification, le service reconstruisait le chemin de stockage de destination en utilisant le attachmentId et le nom de fichier fournis par l'attaquant :

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

Ensuite, sur le chemin de mise à jour, Docmost ne mettait à jour que les métadonnées mutables telles que :

  • fileSize
  • updatedAt

Il ne reliait pas la propriété à la page de l'attaquant.

Ainsi, la page victime continuait de pointer vers le même enregistrement de pièce jointe et le même identifiant de pièce jointe. Seuls les octets du fichier sous‑jacent changeaient.

C'est pourquoi ce n'était pas une simple non‑correspondance inoffensive.

C'était une primitive d'écrasement persistant non autorisé.


Pourquoi c'est un problème de sécurité, pas seulement une erreur de logique

Ce n'était pas un bogue cosmétique ni un problème de collision de noms de fichiers.

L'attaquant n'avait pas besoin d'une condition de course. L'attaquant n'avait pas besoin de deviner un chemin aléatoire. L'attaquant n'avait pas besoin d'accès en écriture à la page victime.

Il avait seulement besoin :

  • d'un accès en lecture pour apprendre une référence de pièce jointe victime, et
  • d'un accès en écriture à n'importe quelle autre page dans le même espace de travail

À partir de là, il pouvait remplacer les octets du fichier stocké pour la pièce jointe d'une autre page tandis que la page victime continuait de référencer et de servir cette pièce jointe comme si de rien n'était.

C'est une défaillance directe d'intégrité.

En termes pratiques, l'attaquant pouvait :

  • falsifier des diagrammes
  • remplacer des pièces jointes par un contenu trompeur
  • corrompre des fichiers référencés
  • créer des pistes d'audit confuses car la pièce jointe semblait toujours appartenir à la page victime

Le point important est le suivant :

le serveur acceptait une cible d'écrasement choisie par l'attaquant, sans la lier à la page dont la permission d'édition avait effectivement été vérifiée.

Il s'agit d'une défaillance de contrôle d'accès, pas seulement d'une mauvaise hygiène booléenne.


Pourquoi l'exploitation était pratique

L'exploit était particulièrement pratique pour les pièces jointes de diagrammes.

Le client de Docmost réutilise intentionnellement attachmentId pour les sauvegardes de diagrammes et utilise des noms de fichiers déterministes :

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

C'est important car cela réduit les prérequis de l'attaquant.

Pour les pièces jointes génériques, l'attaquant a besoin à la fois :

  • de l'identifiant de la pièce jointe victime
  • du nom de fichier victime

Pour les diagrammes, le nom de fichier est déjà prévisible.

Donc si l'attaquant peut lire le contenu de la page victime, il peut souvent récupérer la seule pièce manquante dont il a besoin :

  • le attachmentId victime

Dans ma configuration de validation, j'ai utilisé exactement ce chemin :

  • l'attaquant avait seulement un accès lecteur à l'espace victime
  • l'attaquant avait un accès écrivain à un autre espace contrôlé par l'attaquant
  • les deux espaces appartenaient au même espace de travail

C'était suffisant.

L'exploit traversait les limites de pages et les limites d'espaces à l'intérieur du même espace de travail, tout en satisfaisant la vérification erronée d'espace de travail.


Preuve de concept

J'ai validé le problème en direct sur Docmost v0.70.3 en utilisant un laboratoire jetable construit à partir de docmost/docmost:0.70.3, Postgres et Redis.

Le flux du PoC était :

  1. Créer un compte propriétaire.
  2. Créer un espace victime et un espace contrôlé par l'attaquant dans le même espace de travail.
  3. Inviter un second utilisateur comme attaquant.
  4. Accorder à l'attaquant :
    • un accès lecteur à l'espace victime
    • un accès écrivain à l'espace contrôlé par l'attaquant
  5. Dans l'espace victime, téléverser une pièce jointe de diagramme sur une page victime.
  6. En tant qu'attaquant, récupérer les informations de la page victime et noter le attachmentId victime.
  7. Envoyer POST /api/files/upload avec :
    • pageId = ID de la page attaquante
    • attachmentId = ID de la pièce jointe victime
    • file = fichier de remplacement contrôlé par l'attaquant utilisant le nom de fichier victime
  8. Télécharger la pièce jointe victime avant et après l'écrasement et comparer les hachages.

La forme minimale de la requête était :

root@kitploit:~
POST /api/files/upload
Content-Type: multipart/form-data

pageId=<attackerPageId>
attachmentId=<victimAttachmentId>
[email protected];filename=diagram.excalidraw.svg

Le résultat observé en direct était :

  • ID de pièce jointe victime appris par l'attaquant : 019d18ae-b176-751c-8525-b5f3cede131d
  • ID de page attaquante utilisé pour la requête d'écrasement : 019d18ae-b15b-70e9-ac67-64948e87cc5e
  • ID de page propriétaire victime resté : 019d18ae-b12f-75ec-8c1c-5aff3ba6be9c
  • Réponse du serveur à la requête d'écrasement : 200 OK
  • SHA-256 du fichier victime avant écrasement :
root@kitploit:~
686a0a0ede90ece1cbb975bb29304a6c3a90373a9c3ab2496345cf7ca59cc8fa
  • SHA-256 du fichier victime après écrasement :
root@kitploit:~
e0168298846cdaf75c4d880f4b721d7c0ef0ef310f75617bf2b833af34cdbeba
  • SHA-256 de la charge utile de l'attaquant :
root@kitploit:~
e0168298846cdaf75c4d880f4b721d7c0ef0ef310f75617bf2b833af34cdbeba
  • Le stockage monté a confirmé que le chemin victime contenait maintenant :
root@kitploit:~
Remplacement de l'attaquant depuis une autre page

C'est une preuve complète d'écrasement de bout en bout, pas seulement une revue théorique du code source.


Pourquoi le PoC a été choisi ainsi

J'ai utilisé deux styles de preuve pendant le triage :

  • un harnais autonome restreint qui reflétait la logique d'écrasement vulnérable, et
  • un exploit HTTP complet en direct contre une instance Docmost jetable

Le harnais autonome était utile pour isoler l'échec de logique booléenne.

Le PoC HTTP en direct était l'artefact le plus fort car il prouvait l'histoire complète de sécurité :

  • l'autorisation de page réussissait sur la page attaquante
  • le attachmentId victime était accepté
  • la requête d'écrasement retournait un succès
  • la page victime restait le propriétaire logique
  • les octets stockés sur le disque changeaient en contenu contrôlé par l'attaquant

Cette distinction compte dans les bogues de contrôle d'accès.

« la condition est fausse » ne suffit pas en soi.

« la condition est fausse, et l'application peut être pilotée de bout en bout vers un écrasement persistant non autorisé » est le cas complet.


Analyse du correctif

Le correctif livré dans v0.71.0 a changé la garde d'écrasement de && à || :

root@kitploit:~
if (
  existingAttachment.pageId !== pageId ||
  existingAttachment.fileExt !== preparedFile.fileExtension ||
  existingAttachment.workspaceId !== workspaceId
) {
  throw new BadRequestException("File attachment does not match");
}

Ce correctif est minimal, direct et correct pour le bogue qui a été signalé.

Il rétablit la bonne règle :

l'écrasement est autorisé uniquement lorsque la pièce jointe existante correspond exactement aux hypothèses de page/espace de travail/type autorisées.

Une fois que la garde rejette sur toute non‑correspondance :

  • les écrasements inter‑pages échouent
  • les écrasements inter‑espaces de travail échouent
  • les non‑correspondances de type/extension échouent

C'était le bon type de correctif :

  • pas de reconception
  • pas de logique de compatibilité vague
  • pas de tentative de récupération « au mieux »

Juste un lien strict entre la page autorisée et la cible d'écrasement.

Il y a toujours une leçon d'ingénierie plus large ici :

les points de terminaison de téléversement génériques qui servent également des flux de mise à jour sur place devraient être traités comme des surfaces API à haut risque.

Même lorsque le bogue immédiat est corrigé, des conceptions plus solides à long terme sont :

  • des points de terminaison de mise à jour dédiés pour les flux de sauvegarde de diagrammes
  • des vérifications de liaison immuable sur le nom de fichier ainsi que sur l'identifiant de pièce jointe
  • une couverture de régression qui modélise explicitement les tentatives d'écrasement inter‑pages

Mais pour la vulnérabilité elle‑même, le correctif publié a fermé le problème central proprement.


Cas de régression qui comptent

Que le projet ait ajouté ou non ses propres tests privés autour du correctif, voici les cas qui comptent pour une couverture à long terme :

  • l'écrasement avec le même pageId et le même attachmentId doit réussir
  • l'écrasement avec un pageId différent et le même espace de travail doit échouer
  • l'écrasement avec un espace de travail différent doit échouer
  • l'écrasement avec une extension de fichier non correspondante doit échouer
  • l'écrasement utilisant un nom de fichier de diagramme connu mais un attachmentId étranger doit échouer
  • l'écrasement ne doit jamais conserver silencieusement la propriété victime après que des octets contrôlés par l'attaquant ont été écrits

Le but de ces tests n'est pas seulement l'exactitude.

C'est de verrouiller la liaison d'autorisation afin que de futures refontes « utiles » du téléversement ne rouvrent pas la même classe de bogue.


Gravité et classification

L'avis publié a classé ce problème comme :

  • CWE-639 : Contournement d'autorisation via une clé contrôlée par l'utilisateur
  • CVSS v3.1 :
root@kitploit:~
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L

Cela atterrit à 7.1 / Haute, ce qui est la bonne conclusion.

La métrique importante ici est l'Intégrité.

Ce n'était pas un bogue de métadonnées de bas niveau. L'attaquant contrôlait entièrement les octets de remplacement écrits dans le chemin de la pièce jointe d'une autre page, et la page victime continuait de servir l'objet modifié par la suite.

C'est exactement le type de falsification inter‑enregistrements stockée qui mérite une Intégrité Haute.

La disponibilité restant Faible a également du sens car corrompre un diagramme ou un document joint peut rendre le contenu victime inutilisable, mais l'impact principal reste une modification non autorisée plutôt qu'une interruption complète du service.


Divulgation

J'ai signalé le problème de manière privée via GitHub Security Advisories avec :

  • une analyse de cause racine
  • un PoC HTTP en direct
  • des preuves de requête/réponse
  • des hachages de fichiers avant/après
  • une configuration de laboratoire jetable épinglée

Le problème a été accepté par le mainteneur, attribué à CVE-2026-34213, et publié le 14 avril 2026.

L'avis public liste :

  • versions affectées : >= v0.3.0
  • version corrigée : v0.71.0

Cette histoire correspondait également à ma revue locale du code source : la logique d'écrasement vulnérable était présente dans la première version étiquetée que j'ai vérifiée dans la ligne vulnérable.


Ce que ce bogue enseigne réellement

La leçon intéressante ici n'est pas simplement « utilisez || au lieu de && ».

C'est le symptôme.

La leçon plus profonde est :

si un champ contrôlé par l'utilisateur prouve l'autorisation et qu'un autre champ contrôlé par l'utilisateur sélectionne l'objet en cours de mise à jour, ces deux champs doivent être liés explicitement et exactement.

Cette règle apparaît partout :

  • pièces jointes de documents
  • médias de profil
  • références d'objets cloud
  • modifications de problèmes/commentaires
  • retraitement de tâches en arrière‑plan

Dès qu'un système dit :

  • « vous pouvez modifier la page X »
  • « veuillez également me dire quel enregistrement existant mettre à jour »

il a créé une frontière de sécurité qui doit être appliquée avec des invariants de correspondance exacte.

Tout ce qui est plus souple que cela se transforme tôt ou tard en un bogue de clé contrôlée par l'utilisateur.

Ce problème renforce également un deuxième point facile à sous‑estimer :

les petites erreurs booléennes dans le code de garde peuvent avoir des conséquences de sécurité de premier ordre.

Une condition à trois clauses qui « semble raisonnable » à première vue a suffi à inverser le modèle de protection pour le chemin d'écrasement.

C'est pourquoi ces surfaces méritent une revue délibérée plutôt qu'une confiance décontractée.


Points clés

  • Docmost utilisait un seul point de terminaison à la fois pour les nouveaux téléversements et les mises à jour sur place de pièces jointes.
  • L'autorisation était vérifiée par rapport au pageId fourni par l'appelant, mais la sélection de la cible d'écrasement utilisait un attachmentId distinct fourni par l'appelant.
  • La garde d'écrasement ne rejetait que lorsque toutes les conditions de non‑correspondance étaient vraies en même temps.
  • Dans le cas d'attaque dans le même espace de travail, cette vérification échouait en mode ouvert.
  • Le service reconstruisait le chemin de stockage à partir de l'identifiant de pièce jointe victime et y écrivait des octets contrôlés par l'attaquant.
  • L'enregistrement de la pièce jointe restait lié à la page victime après l'écrasement.
  • Les noms de fichier de diagrammes déterministes rendaient l'exploitation particulièrement pratique.
  • Le correctif dans v0.71.0 changeait correctement la garde pour rejeter sur toute non‑correspondance.

Derniers mots

Cette vulnérabilité ne concernait pas un comportement de stockage exotique.

Elle concernait un chemin de mise à jour qui faisait trop confiance à un identifiant d'objet sélectionné par l'attaquant.

Docmost prouvait l'accès en modification sur une page, acceptait un identifiant de pièce jointe existant depuis une autre page, puis laissait une vérification d'écrasement erronée transformer cette non‑correspondance en un remplacement réussi de fichier inter‑pages.

C'est pourquoi elle est devenue CVE-2026-34213.

Le correctif dans v0.71.0 a corrigé le problème immédiat proprement, mais la leçon plus large reste précieuse :

lorsque l'autorisation et la sélection d'objet sont réparties sur des champs séparés contrôlés par l'utilisateur, la liaison exacte est la propriété de sécurité.

Télécharger l’outil