security: audit mcp-nas — RCE root anonyme exposee Internet + correctifs P0
This commit is contained in:
parent
4c1c81a610
commit
2afc825923
|
|
@ -11,6 +11,7 @@
|
||||||
|---------|------|------|
|
|---------|------|------|
|
||||||
| [Acces Bifrost (passerelle LLM, vision/OCR)](common/bifrost-access.md) | 2026-06-14 | infra, bifrost, llm, vision, x-bf-vk |
|
| [Acces Bifrost (passerelle LLM, vision/OCR)](common/bifrost-access.md) | 2026-06-14 | infra, bifrost, llm, vision, x-bf-vk |
|
||||||
| [Audit securite reseau NAS (surface WAN, crowdsec, firewall, wg-easy)](common/audit-securite-reseau-nas-20260703.md) | 2026-07-03 | infra, securite, reseau, crowdsec, firewall, wg-easy, wan |
|
| [Audit securite reseau NAS (surface WAN, crowdsec, firewall, wg-easy)](common/audit-securite-reseau-nas-20260703.md) | 2026-07-03 | infra, securite, reseau, crowdsec, firewall, wg-easy, wan |
|
||||||
|
| [Audit securite mcp-nas — RCE root ANONYME exposee Internet (docker.sock, secrets clairs audit.log, correctifs P0 + rotation)](common/mcp-nas-security-audit.md) | 2026-07-08 | infra, securite, mcp-nas, rce, docker-socket, secrets, rotation |
|
||||||
| [Fragilites de dependances stack 43 containers (restart/persistance/couplages)](common/fragilites-dependances-stack-20260703.md) | 2026-07-03 | infra, stack, restart, couplage, dependances, rla-api, n8n |
|
| [Fragilites de dependances stack 43 containers (restart/persistance/couplages)](common/fragilites-dependances-stack-20260703.md) | 2026-07-03 | infra, stack, restart, couplage, dependances, rla-api, n8n |
|
||||||
| [Correctifs fragilites appliques (rla-api compose, n8n/trilium restart, bifrost-proxy healthcheck)](common/correctifs-fragilites-application-20260704.md) | 2026-07-04 | infra, correctifs, rla-api, n8n, bifrost-proxy, healthcheck |
|
| [Correctifs fragilites appliques (rla-api compose, n8n/trilium restart, bifrost-proxy healthcheck)](common/correctifs-fragilites-application-20260704.md) | 2026-07-04 | infra, correctifs, rla-api, n8n, bifrost-proxy, healthcheck |
|
||||||
| [nyora-doc-api mode template — cause racine non-restyle des runs (rPr > styles) + correctif restyle opt-in proposé](common/nyora-doc-api-template-restyle-cause-racine.md) | 2026-07-08 | infra, nyora-doc-api, docx, pptx, template, cause-racine |
|
| [nyora-doc-api mode template — cause racine non-restyle des runs (rPr > styles) + correctif restyle opt-in proposé](common/nyora-doc-api-template-restyle-cause-racine.md) | 2026-07-08 | infra, nyora-doc-api, docx, pptx, template, cause-racine |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Audit sécurité mcp-nas — RCE root anonyme exposée Internet (2026-07-08)
|
||||||
|
|
||||||
|
**Tags** : infra, securite, mcp-nas, rce, docker-socket, secrets, rotation
|
||||||
|
**Périmètre** : conteneur `linux-mcp-nas` (`mcp-nas.nd.i234.me`, port hôte 3042→8000, réseau Docker `n8n`).
|
||||||
|
**Complète** : `common/audit-securite-reseau-nas-20260703.md` (couche réseau/WAN). Ici = **couche applicative**.
|
||||||
|
**Statut** : audit seul, correctifs **NON appliqués** (décision Nabil requise — rotation + coupure exposition = disruptif).
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**Compromission totale possible par un attaquant anonyme sur Internet.** Chaîne vérifiée depuis un poste externe, sans aucun credential :
|
||||||
|
|
||||||
|
1. **Endpoint MCP sans authentification.** `POST https://mcp-nas.nd.i234.me/mcp` → `initialize` répond, `tools/list` renvoie `run_command`. Le bloc nginx (`deploy/nginx-reverse-proxy.conf`) fait `proxy_pass http://localhost:3042` **sans `auth_basic`, sans allowlist IP, sans vérif de token**. Les routes OAuth du serveur (`/register` → `client_id:"public"`, `/token`, `/authorize`, `/.well-known/...`) sont des **stubs décoratifs** : elles ne valident aucun bearer. FastMCP n'a aucun auth provider. → **shell arbitraire pour tout le monde**.
|
||||||
|
2. **Évasion vers root hôte via `docker.sock`.** Le conteneur tourne en **root** (uid=0) avec `/var/run/docker.sock` monté (RW). L'API Docker est joignable (`curl --unix-socket /var/run/docker.sock http://localhost/version` OK). → créer un conteneur privilégié montant `/` = **root sur le NAS**. Le binaire `docker` n'est pas installé mais le socket suffit (curl). Le serveur MCP **n'utilise jamais** ce socket (aucune référence dans le code) → mount inutile.
|
||||||
|
3. **Pivot latéral.** Clés SSH privées `/root/.ssh/claude_oversight_contabo{,_v2,_v3}` **dans le conteneur** → accès VPS Contabo.
|
||||||
|
4. **2e chemin root hôte.** Le conteneur ssh vers `Best0f@172.17.0.1:22222` (host-gateway) avec le mot de passe sudo passé en clair → sudo hôte.
|
||||||
|
|
||||||
|
## audit.log = coffre à secrets en clair
|
||||||
|
|
||||||
|
`/mnt/docker/linux-mcp-nas/audit.log` (volume hôte RW, **lisible par la RCE**) journalise chaque commande entière. Relevé au 2026-07-08 : mot de passe sudo NAS en clair **266×**, **102** URLs `http://bolbol:MDP@.../repo.git`, mots de passe n8n / Portainer, **127** clés Bifrost (`x-bf-vk`), **19** Bearer. Le mot de passe maître est **réutilisé** (même préfixe pour sudo / n8n / Portainer). → un attaquant `cat` le log et récupère tout le trousseau.
|
||||||
|
|
||||||
|
## Autres surfaces
|
||||||
|
|
||||||
|
- **Volumes RW** : `/volume1/docker` monté RW → tous les `.env` de l'infra (43 conteneurs) lisibles ET modifiables par la RCE. Doit être `:ro`.
|
||||||
|
- **Denylist** (`DENY_ANCHORED`/`DENY_GLOBAL` dans `linux_mcp_server.py`) **contournable trivialement** : segment après `&&`/`;`, `find -delete`, `python -c`, docker.sock, ssh+sudo. C'est de la défense en profondeur, **pas une frontière de sécurité** — ne jamais s'y fier.
|
||||||
|
- **Contournement Gitea-API-HTTP** (quand git absent du NAS) : utilise le **mot de passe du compte `bolbol` complet** dans l'URL de clone (→ audit.log + `argv` visible en `ps`), au lieu d'un token scopé. Sur-privilège (accès à tous les repos) + fuite.
|
||||||
|
- **Gemini : aucune fuite.** Le serveur ne référence ni Gemini ni Bifrost (grep code = 0 résultat) ; la sortie texte ne part que vers NyoraNotes. La frontière « exclusif Claude » tient au niveau code : aucune commande mcp-nas n'alimente un prompt Gemini (Gemini n'est câblé que pour la vision dans Bifrost, et mcp-nas ne produit pas d'image).
|
||||||
|
|
||||||
|
## Correctifs priorisés
|
||||||
|
|
||||||
|
**P0 — urgence (RCE root exposée) :**
|
||||||
|
1. **Couper l'exposition publique** : retirer la règle nginx `mcp-nas.nd.i234.me` OU la restreindre au réseau WireGuard uniquement (pas de `proxy_pass` depuis le WAN). Si un accès distant reste nécessaire : `auth_request` nginx ou un secret partagé obligatoire (header) + implémenter une vraie validation bearer côté serveur.
|
||||||
|
2. **Supprimer le mount `docker.sock`** du `docker-compose.yml` (jamais utilisé par le code).
|
||||||
|
3. **Run non-root** : `user: "1026:100"` + `cap_drop: [ALL]` + `security_opt: no-new-privileges`.
|
||||||
|
4. Ne plus embarquer le mot de passe sudo dans les commandes ; **sortir les clés SSH** du conteneur (ou les monter en lecture seule depuis un secret) ; monter `/mnt/docker` en **`:ro`** (garder RW uniquement le sous-chemin strictement nécessaire).
|
||||||
|
5. **Rédiger les secrets** avant écriture dans audit.log (regex password/token/bearer/x-bf-vk → `***`) + `chmod 600` + rotation du fichier.
|
||||||
|
6. **ROTATION complète** des credentials fuités : mot de passe sudo NAS `Best0f`, n8n, Portainer `bestof`, compte Gitea `bolbol`, **toutes** les clés virtuelles Bifrost, clés SSH Contabo.
|
||||||
|
|
||||||
|
**P1 :** token Gitea repo-scopé (au lieu du mot de passe bolbol) via credential helper, jamais dans l'URL ; rate-limiting/fail2ban sur l'endpoint.
|
||||||
|
|
||||||
|
**P2 :** healthcheck ne teste que `/health` pas `/mcp` (connu) ; dérive doc `MCP_MAX_OUTPUT_CHARS` (README dit 2000, défaut code 8000).
|
||||||
|
|
||||||
|
## Vérif rapide (depuis un poste externe, doit ÉCHOUER après P0)
|
||||||
|
```sh
|
||||||
|
curl -s https://mcp-nas.nd.i234.me/health # ne doit plus répondre sans auth
|
||||||
|
curl -s -X POST https://mcp-nas.nd.i234.me/mcp \
|
||||||
|
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
|
||||||
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"x","version":"0"}}}'
|
||||||
|
# tant que ça renvoie un result MCP sans token → RCE ouverte
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue