Exploitation du serveur

Atteindre l'exécution de code sur un serveur Mattermost : failles d'autorisation de l'API et IDOR qui lisent les messages et fichiers d'autres équipes par identifiant, injection SQL dans les paramètres de filtre et de tri de l'API, traversée de chemin dans les endpoints de fichiers et d'archive/import, et l'upload de plugin réservé au system-admin sur /api/v4/plugins qui charge un plugin Go d'attaquant dans le processus du serveur. Exemple concret de curl d'upload de plugin avec un bundle malveillant, les requêtes IDOR et SQLi, et leur interprétation, aboutissant à un shell en tant qu'utilisateur mattermost.

5 min de lecturemis à jour le 12 oct. 2026florianamette1 contributeur
Demander à votre assistant (MCP)Modifier sur GitHub

C'est ici qu'un jeton se transforme en accès au serveur. Trois classes comptent : l'autorisation défaillante (IDOR) qui lit des données que le jeton ne devrait pas voir, l'injection dans la couche de requête, et la machinerie de plugins réservée aux administrateurs, conçue pour exécuter du code d'opérateur et qui exécute donc du code d'attaquant dès que vous détenez un jeton system-admin. Le chemin des plugins est la voie fiable vers un shell ; l'IDOR et l'injection sont les voies vers les données et les identifiants qui vous procurent le jeton admin en premier lieu.

Autorisation défaillante et IDOR entre équipes#

Mattermost identifie les messages, fichiers, canaux et équipes par des identifiants opaques, et plusieurs endpoints de lecture /api/v4 vérifiaient historiquement que vous étiez authentifié mais pas que vous étiez membre de l'équipe de la ressource. Avec n'importe quel jeton à faibles privilèges, demander une ressource par identifiant renvoie alors du contenu provenant d'équipes et de canaux privés que vous n'avez jamais rejoints :

http
GET /api/v4/channels/{foreign_channel_id}/posts HTTP/1.1
Host: target:8065
Authorization: Bearer <low-priv-token>
text
{"order":["p1","p2"],"posts":{"p1":{"message":"vpn creds: ...","channel_id":"..."}}}

Un 200 renvoyant des messages d'un canal dont votre compte n'est pas membre est le signe que l'IDOR fonctionne. Récoltez les identifiants de canaux et d'équipes depuis l'énumération, puis balayez les endpoints de messages, de fichiers et de membres de canaux par identifiant. Le même motif appliqué à GET /api/v4/files/{file_id} et GET /api/v4/files/{file_id}/link récupère les pièces jointes (sauvegardes, matériel de clés) entre équipes. Lire le canal de messages privés d'un admin de cette manière est une voie directe vers les identifiants ou le jeton de réinitialisation qui vous font passer à system_admin.

Injection SQL dans les paramètres de filtre#

Les endpoints qui acceptent des arguments de tri/filtre atteignant la couche base de données sous forme de fragments de requête (plutôt que de paramètres liés) sont injectables sur les builds affectés. Les filtres de type rapport et analytique, et les paramètres de style sort/in_team/in_channel sur certains endpoints de recherche et de liste d'administration, sont les points de sortie habituels :

http
GET /api/v4/users?in_team=xxx&sort=create_at)%20AND%20(SELECT%201%20FROM%20pg_sleep(5))-- - HTTP/1.1
Host: target:8065
Authorization: Bearer <token>

Interprétez par le temps : une réponse qui revient après ~5 s là où la requête non modifiée est immédiate confirme une injection booléenne/temporelle dans ce paramètre. Escaladez vers l'extraction de Sessions.Token et Users.Password (bcrypt) depuis la base de données, et en particulier les secrets stockés. Confirmez d'abord le backend à partir du texte d'erreur (pg_sleep pour Postgres, SLEEP() pour MySQL) vu dans une charge utile délibérément cassée.

Traversée de chemin dans les endpoints de fichiers et d'import#

Les routes de téléchargement, d'export et d'import/archive en masse composent un chemin du système de fichiers à partir de l'entrée de la requête. Là où le chemin n'est pas confiné, ../ s'échappe du répertoire de données :

http
GET /api/v4/files/../../../../etc/passwd HTTP/1.1
Host: target:8065
Authorization: Bearer <token>

Un corps contenant root:x:0:0: confirme la lecture arbitraire de fichiers en tant qu'utilisateur du service ; pointez-le vers le config.json de Mattermost pour récupérer le SqlSettings.DataSource (DSN complet de la BD) et les secrets de signature ServerSettings. Les endpoints d'import/archive (/api/v4/imports, la gestion des archives de commandes slash et de plugins) qui décompressent un .zip/.tar.gz avec des noms d'entrée contrôlés par l'attaquant sont la variante côté écriture : une entrée d'archive traversante écrit un fichier en dehors du répertoire prévu, ce qui met en place le RCE par plugin ci-dessous si l'upload de plugin est verrouillé.

Upload de plugin vers l'exécution de code#

Un plugin Mattermost est un bundle contenant un exécutable Go compilé que le serveur lance et avec lequel il communique par RPC au sein de son propre espace de processus. En uploader un en tant que system-admin est donc, par conception, de l'exécution de code arbitraire en tant que compte de service mattermost. Construisez un plugin minimal dont le main côté serveur exécute votre commande, empaquetez-le selon la disposition .tar.gz attendue (un manifeste plugin.json plus le binaire compilé sous server/), puis uploadez-le et activez-le :

http
POST /api/v4/plugins HTTP/1.1
Host: target:8065
Authorization: Bearer <system-admin-token>
Content-Type: multipart/form-data; boundary=x

--x
Content-Disposition: form-data; name="plugin"; filename="x.tar.gz"
Content-Type: application/gzip

<binary of the malicious plugin bundle>
--x--
text
{"id":"com.evil.x","version":"1.0.0",...}

Un 200 avec le manifeste signifie qu'il est installé. Activez-le pour que le serveur lance le binaire :

http
POST /api/v4/plugins/com.evil.x/enable HTTP/1.1
Host: target:8065
Authorization: Bearer <system-admin-token>

Là où le déploiement impose des signatures de plugins (RequirePluginSignature), soit désactivez ce paramètre via l'API de configuration d'administration avec le même jeton (PUT /api/v4/config), soit signez le bundle avec une clé que vous avez implantée via cette même écriture de configuration ; le contrôle de signature est une politique contrôlée par l'administrateur, pas une barrière cryptographique une fois que vous êtes admin. Le hook ServeHTTP ou d'activation du plugin exécute votre charge utile : un reverse shell, une lecture de config.json depuis le disque, ou un dump des tables Postgres Users/Sessions via le DSN que le serveur détient déjà.

Suite#

Le plugin s'exécute en tant qu'utilisateur mattermost, vous avez donc un shell sur l'hôte avec le système de fichiers et la portée réseau du compte de service, et config.json sur le disque vous remet le DSN de la base de données et tous les secrets d'intégration. De là, pivotez vers l'hôte Postgres/MySQL, générez des jetons d'accès personnels pour la persistance (voir authentification), et lisez l'historique de chaque équipe directement depuis le store plutôt qu'une IDOR à la fois.

Outils#

Références#