Files
stupid-simple-network-inven…/SECURITY_AUDIT_BASE.md
T
olivier 88cf6458d0 Initial commit — Stupid Simple Network Inventory
Application web d'inventaire réseau manuel avec FastAPI, Vue 3 et Docker.
Inclut l'authentification JWT, la découverte ICMP, et la topologie en cards CSS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 09:19:19 +02:00

337 lines
19 KiB
Markdown

# Audit de securite de base
Projet audite: Stupid Simple Network Inventory
Date: 2026-05-05
Referentiels: OWASP ASVS niveau 1, OWASP Top 10 2021
Mode: revue statique locale, sans attaque reelle, sans exploitation, sans correction applicative.
## Synthese
L'application est une SPA Vue exposee par Nginx, avec API FastAPI protegee par JWT pour les routeurs metier. Le modele d'exploitation precise est local, derriere un reverse-proxy. Dans ce modele, certains controles comme TLS, filtrage d'exposition et en-tetes HTTP peuvent legitimement etre portes par le reverse-proxy, mais doivent etre documentes et verifiables.
Risque global estime: moyen en usage local derriere reverse-proxy correctement configure; eleve si le service est expose directement a un LAN non fiable ou a Internet. Le risque principal vient du bootstrap avec identifiants publics `admin/admin` si le changement n'est pas force avant exposition, de l'absence de limitation de tentatives, du stockage du JWT en `localStorage`, de la surface de scan reseau authentifiee et d'un manque de documentation du contrat de securite attendu cote reverse-proxy.
## Perimetre lu
- Documents: `CLAUDE.md`, `AGENTS.md`, `docs/*.md`
- README: `README.md` absent
- Config: `docker-compose.yml`, `backend/Dockerfile`, `frontend/Dockerfile`, `frontend/nginx.conf`, `frontend/vite.config.js`, `backend/requirements.txt`, `frontend/package.json`, `frontend/package-lock.json`
- Backend: `backend/main.py`, `backend/database.py`, `backend/models.py`, `backend/routers/*.py`
- Frontend: `frontend/src/**/*.js`, `frontend/src/**/*.vue`, `frontend/index.html`
- `.env.example`: aucun fichier trouve
## Points positifs
- Les routeurs metier `vlans`, `devices`, `links`, `discovery` sont proteges par `Depends(get_current_user)` dans `backend/main.py:105-108`.
- Les requetes SQL passent par SQLAlchemy ou par `text()` parametre pour les migrations; pas de concatenation SQL dangereuse observee dans les routes CRUD.
- Le ping utilise `subprocess.run([...])` sans `shell=True` dans `backend/routers/discovery.py:42-46`, ce qui limite fortement l'injection de commande shell.
- Les mots de passe sont haches avec bcrypt via passlib dans `backend/routers/auth.py:37`.
- La cle JWT est generee aleatoirement au premier demarrage si `SECRET_KEY` est absent dans `backend/routers/auth.py:20-30`.
- Les liens externes visibles utilisent `rel="noopener"` dans les composants principaux, ce qui reduit le risque de controle de la fenetre ouvrante.
## Constats principaux
### SEC-01 - Bootstrap administrateur `admin/admin` sans changement force
Priorite: P0
OWASP: Top 10 A07 Identification and Authentication Failures, A04 Insecure Design
ASVS L1: authentification, gestion des secrets initiaux
Fichiers:
- `backend/main.py:63-86`
- `CLAUDE.md` / `AGENTS.md` section Authentication
- `docs/backend.md`, `docs/extending.md`
La migration `_migrate_users()` cree automatiquement un compte `admin` avec mot de passe `admin` quand la table `users` est vide. Les documents indiquent explicitement ces identifiants comme mot de passe de depart a changer. Ce comportement est donc intentionnel, mais reste un ecart ASVS si l'application peut etre atteinte avant changement manuel: toute instance nouvellement demarree est accessible avec des identifiants publics jusqu'a cette action.
Impact: compromission complete des donnees d'inventaire, utilisation de la fonctionnalite de scan reseau, modification/suppression d'equipements.
Recommandation: conserver ce bootstrap seulement si l'application force le changement au premier login ou si la documentation exige explicitement une premiere connexion depuis localhost/reseau de confiance avant toute exposition. Variante plus robuste: exiger un mot de passe initial via variable d'environnement, fichier secret Docker, ou assistant de premier demarrage.
### SEC-02 - Pas de limitation de tentatives de connexion
Priorite: P1
OWASP: A07
ASVS L1: protections anti-automatisation de l'authentification
Fichiers:
- `backend/routers/auth.py:72-77`
- `frontend/nginx.conf:1-16`
`POST /api/auth/login` ne limite pas les tentatives par IP, par nom utilisateur ou par fenetre temporelle. Nginx ne definit pas non plus de `limit_req`. Une attaque par devinette de mot de passe devient praticable, surtout pendant la phase de bootstrap avant changement du mot de passe initial.
Recommandation: ajouter une limitation cote API ou Nginx, journaliser les echecs, retourner des erreurs generiques et prevoir un delai progressif ou verrouillage temporaire.
### SEC-03 - Politique de mot de passe insuffisante
Priorite: P1
OWASP: A07
ASVS L1: qualite des secrets d'authentification
Fichiers:
- `backend/routers/auth.py:66-69`
- `backend/routers/auth.py:80-95`
- `frontend/src/components/AccountModal.vue`
`new_password` est accepte s'il est non vide. Aucun minimum de longueur, aucune verification de mot de passe courant compromis/faible, et aucune validation de `new_username` ne sont appliquees cote serveur.
Recommandation: definir une politique minimale cote serveur, par exemple longueur minimale robuste, refus des mots de passe evidents, normalisation/validation du nom utilisateur, messages d'erreur non enumerants.
### SEC-04 - JWT persiste dans `localStorage`
Priorite: P1
OWASP: A02 Cryptographic Failures, A07, A05 Security Misconfiguration
ASVS L1: protection des tokens de session
Fichiers:
- `frontend/src/auth.js:3-13`
- `frontend/src/api.js:6-9`
- `backend/routers/auth.py:41-43`
Le token JWT est stocke dans `localStorage`. En cas de XSS ou d'extension navigateur compromise, le token est directement lisible. Le token dure 7 jours et ne contient pas de version de session permettant d'invalider les anciens tokens apres changement de mot de passe.
Recommandation: preferer un cookie `HttpOnly`, `Secure`, `SameSite`, et ajouter une strategie d'expiration courte + renouvellement ou une version de session cote base. Si le mode bearer est conserve, reduire la duree de vie et ajouter une invalidation apres changement de mot de passe.
### SEC-05 - CORS ouvert a toutes origines dans l'API interne
Priorite: P2
OWASP: A05
ASVS L1: configuration HTTP securisee
Fichiers:
- `backend/main.py:97-102`
- `docs/backend.md` indique que ce choix est intentionnel pour un outil LAN
`allow_origins=["*"]`, `allow_methods=["*"]`, `allow_headers=["*"]` ouvrent l'API a toute origine. Dans le deploiement Compose actuel, le backend n'est pas publie directement et le navigateur passe par le Nginx frontend/reverse-proxy en meme origine, ce qui reduit fortement le risque pratique. Cela reste une configuration permissive a encadrer si un reverse-proxy externe expose `/api/` ou si l'auth evolue vers des cookies.
Recommandation: soit supprimer CORS si l'API reste strictement interne et servie en meme origine par le reverse-proxy, soit configurer une liste d'origines explicites via environnement. Documenter le contrat attendu: backend non expose directement, `/api/` publie uniquement via le reverse-proxy.
### SEC-06 - Surface de scan reseau et SSRF authentifiee
Priorite: P1
OWASP: A10 SSRF, A04 Insecure Design
ASVS L1: validation des cibles reseau et limitation d'abus
Fichiers:
- `backend/routers/discovery.py:22-24`
- `backend/routers/discovery.py:52-60`
- `backend/routers/discovery.py:79-86`
- `backend/routers/discovery.py:89-129`
- `docker-compose.yml:4-5`
Les endpoints `/api/discovery/scan` et `/api/discovery/ping` permettent a tout utilisateur authentifie de provoquer des connexions ICMP et DNS depuis le conteneur backend vers des cibles fournies par le client. `scan` limite chaque reseau a 1024 hotes, mais ne limite pas le nombre total de targets. `ping` n'a pas de limite de taille de liste ni de validation IP. Le serveur DNS est librement fourni.
Impact: cartographie de reseaux accessibles depuis le conteneur, charge CPU/process, requetes DNS vers des serveurs arbitraires, usage comme outil de reconnaissance interne apres compromission d'un compte.
Recommandation: valider toutes les IP/CIDR, limiter le nombre total d'adresses par requete, limiter la frequence, autoriser uniquement des plages configurees dans l'inventaire, restreindre les DNS autorises ou utiliser le resolv.conf du conteneur.
### SEC-07 - Validation d'entree insuffisante sur les modeles metier
Priorite: P1
OWASP: A03 Injection, A04, A05
ASVS L1: validation cote serveur
Fichiers:
- `backend/routers/devices.py:11-33`
- `backend/routers/vlans.py:11-15`
- `backend/routers/links.py:11-15`
- `frontend/src/components/DeviceManager.vue:137`
- `frontend/src/components/TopologyGraph.vue:35,56,104,142`
Les schemas Pydantic acceptent des chaines libres sans bornes de longueur ni enums serveur. Exemples: `Device.type`, `virt_type`, `url`, `Vlan.cidr`, `Vlan.color`, `Link.link_type`, noms/descriptions/interfaces. Vue echappe correctement les interpolations texte, mais les URL sont reinjectees en `href` et l'API peut recevoir des valeurs qui ne passent pas par le formulaire HTML `type="url"`.
Recommandation: ajouter des contraintes Pydantic strictes: longueurs maximales, enums pour types, validation IP/CIDR, validation couleur hex, URL seulement `http`/`https`, normalisation des chaines. Ajouter `rel="noreferrer noopener"` sur les liens externes.
### SEC-08 - En-tetes de securite HTTP absents de la configuration fournie
Priorite: P2
OWASP: A05
ASVS L1: durcissement navigateur
Fichiers:
- `frontend/nginx.conf:1-16`
- `frontend/index.html`
Le Nginx embarque ne configure pas d'en-tetes de securite: CSP, `X-Frame-Options` ou `frame-ancestors`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`. Si un reverse-proxy frontal ajoute deja ces en-tetes, le risque est traite au niveau infrastructure; sinon, la configuration fournie reste incomplete.
Recommandation: fournir une configuration de reference pour le reverse-proxy frontal, ou ajouter ces en-tetes dans `frontend/nginx.conf`. La decision doit eviter les doublons contradictoires et etre documentee.
### SEC-09 - TLS delegue au reverse-proxy, contrat non documente
Priorite: P2
OWASP: A02, A05
ASVS L1: protection des identifiants en transit
Fichiers:
- `docker-compose.yml:14-15`
- `frontend/nginx.conf:1-2`
- `docs/architecture.md`
La configuration applicative expose HTTP sur `localhost:8080`. C'est coherent avec un service local derriere reverse-proxy, a condition que le reverse-proxy frontal termine TLS pour les acces non strictement locaux et que le backend/frontend Compose ne soient pas exposes directement sur un reseau non fiable.
Recommandation: documenter clairement que TLS est une responsabilite du reverse-proxy frontal, fournir un exemple de configuration attendue et recommander un bind local (`127.0.0.1:8080:80`) lorsque l'application est consommee uniquement par le proxy local.
### SEC-10 - Secret JWT persiste dans un bind mount projet, permissions faibles
Priorite: P1
OWASP: A02, A05
ASVS L1: gestion des secrets
Fichiers:
- `backend/routers/auth.py:17-30`
- `docker-compose.yml:6-7`
- `docs/architecture.md`
- `db_data/secret_key.txt` present localement, permissions observees `0644`
La cle JWT est stockee dans `./db_data/secret_key.txt`, dans l'arborescence projet, avec permissions lisibles par d'autres utilisateurs locaux. Elle n'a pas ete lue pendant l'audit. Si cette cle fuit, des JWT valides peuvent etre forges jusqu'a rotation.
Recommandation: utiliser `SECRET_KEY` depuis un secret Docker ou un fichier hors depot, fixer des permissions strictes, ajouter `db_data/` et fichiers secrets a l'exclusion VCS si necessaire, documenter la rotation.
### SEC-11 - Conteneurs peu durcis
Priorite: P2
OWASP: A05
ASVS L1: configuration de plateforme
Fichiers:
- `backend/Dockerfile:1-8`
- `frontend/Dockerfile:8-11`
- `docker-compose.yml:1-26`
Les images ne definissent pas d'utilisateur non-root explicite. Compose n'ajoute pas `read_only`, `security_opt: no-new-privileges:true`, `cap_drop`, limites de ressources, healthchecks ou contraintes de filesystem. Le backend ajoute `NET_RAW`, necessaire au ping, mais sans reduction des autres capacites.
Recommandation: executer les services avec utilisateur non-root, ajouter `cap_drop: [ALL]` puis `cap_add: [NET_RAW]` seulement pour backend, activer `no-new-privileges`, limiter ressources et rendre les FS readonly avec volumes temporaires explicites.
### SEC-12 - Pas de journalisation securite/audit
Priorite: P2
OWASP: A09 Security Logging and Monitoring Failures
ASVS L1: evenements de securite
Fichiers:
- `backend/routers/auth.py`
- `backend/routers/devices.py`
- `backend/routers/vlans.py`
- `backend/routers/discovery.py`
Les evenements sensibles ne sont pas journalises de maniere structuree: login reussi/echec, changement de compte, scans reseau, imports, suppressions. En cas d'incident, l'attribution et l'analyse seront limitees.
Recommandation: ajouter des logs structures sans secrets, incluant utilisateur, endpoint, action, resultat, compteurs et IP client issue du proxy de confiance.
### SEC-13 - Integrite relationnelle SQLite non enforcee
Priorite: P2
OWASP: A04, A08
ASVS L1: integrite des donnees
Fichiers:
- `backend/database.py:6-9`
- `backend/models.py`
- `docs/data-model.md` signale explicitement que les FK SQLite ne sont pas enforcees
Les `ForeignKey` SQLAlchemy ne suffisent pas sous SQLite sans `PRAGMA foreign_keys=ON` par connexion. L'application compense partiellement dans les routeurs, mais des incoherences restent possibles, par exemple via evolutions futures ou migrations.
Recommandation: activer le pragma via event SQLAlchemy, verifier les comportements de suppression, ajouter des tests d'integrite.
### SEC-14 - Gestion des dependances et supply chain incomplete
Priorite: P2
OWASP: A06 Vulnerable and Outdated Components, A08 Software and Data Integrity Failures
ASVS L1: composants connus et maintenus
Fichiers:
- `backend/requirements.txt`
- `frontend/package.json:9-19`
- `frontend/package-lock.json`
- `frontend/Dockerfile:3-6`
Les dependances sont epinglees cote Python, mais il n'y a pas de workflow SCA documente. Cote frontend, `package.json` utilise des ranges `^`, et le Dockerfile lance `npm install` au lieu de `npm ci`, ce qui reduit la reproductibilite. `cytoscape` reste declare alors que les notes disent que la topologie n'utilise plus Cytoscape.
Recommandation: utiliser `npm ci`, supprimer les dependances inutilisees, ajouter `pip-audit`/`npm audit` ou equivalent CI, documenter la procedure d'upgrade passlib/bcrypt.
### SEC-15 - Import JSON sans schema ni limite de taille
Priorite: P2
OWASP: A04, A05
ASVS L1: validation des donnees importees
Fichiers:
- `frontend/src/App.vue:191-208`
- routes CRUD backend appelees ensuite
L'import JSON parse tout le fichier cote navigateur, puis cree des VLANs/devices en boucle sans validation de schema dedie ni limite de taille/quantite. Les erreurs de creation sont ignorees avec `.catch(() => {})`, ce qui peut masquer des imports partiels ou incoherents.
Recommandation: definir un schema d'import, limiter taille et nombre d'elements, afficher un bilan d'erreurs, et s'appuyer sur les validations serveur renforcees.
### SEC-16 - `v-html` inutile dans la navigation
Priorite: P3
OWASP: A03 XSS, defense in depth
ASVS L1: sortie encodee
Fichiers:
- `frontend/src/App.vue:24`
- `frontend/src/App.vue` tableau `tabs`
`v-html` est utilise pour afficher des icones de navigation depuis des constantes locales. Le risque actuel est faible car les valeurs ne viennent pas de l'utilisateur, mais l'usage de HTML injecte est inutile et peut devenir dangereux lors d'une future evolution.
Recommandation: remplacer par du texte, des composants d'icones ou une interpolation normale.
### SEC-17 - Endpoint health public minimal
Priorite: P3
OWASP: A05
ASVS L1: exposition minimale
Fichiers:
- `backend/main.py:111-113`
- `docs/backend.md`
`GET /api/health` est public. Dans un modele local derriere reverse-proxy, c'est acceptable pour un healthcheck minimal. Il ne divulgue que `{"status":"ok"}`; risque faible. Il doit simplement rester minimal et ne pas exposer version, configuration ou details d'erreur.
Recommandation: conserver public si necessaire au healthcheck, documenter cette decision, et s'assurer que le reverse-proxy ne l'expose pas avec plus d'information que necessaire.
### SEC-18 - Documentation de securite et configuration d'environnement incompletes
Priorite: P2
OWASP: A05, A04
ASVS L1: configuration securisee documentee
Fichiers:
- `README.md` absent
- aucun `.env.example`
- `CLAUDE.md`, `AGENTS.md`, `docs/*.md`
Les fichiers de notes de developpement sont riches, mais il manque un README utilisateur et un `.env.example` pour documenter `SECRET_KEY`, origines CORS si conservees, URL publique du reverse-proxy, modele TLS delegue, creation du compte initial et durcissement production/local.
Recommandation: ajouter une documentation securite minimale et un exemple d'environnement sans secrets reels.
## Ecarts avec CLAUDE.md / AGENTS.md
- `README.md` est absent alors que le perimetre demande sa revue. Les informations equivalentes sont dans `CLAUDE.md`, `AGENTS.md` et `docs/`.
- Aucun `.env.example` n'est present, alors que `SECRET_KEY` est supporte par le code.
- `CLAUDE.md` et `AGENTS.md` documentent le compte de bootstrap `admin/admin`; ce n'est pas un ecart fonctionnel, mais cela doit etre accompagne d'un changement force ou d'une consigne de non-exposition avant changement pour satisfaire une posture ASVS L1.
- `docs/backend.md` indique que CORS ouvert est intentionnel pour un outil LAN. Avec un backend interne derriere reverse-proxy, le risque est reduit; il faut surtout documenter que le backend ne doit pas etre publie directement.
- `AGENTS.md` indique que les routeurs sont proteges; c'est vrai pour les routeurs metier. `GET /api/health` reste public et doit rester explicitement assume.
- Les notes disent "no Cytoscape", mais `frontend/package.json` garde `cytoscape` dans les dependances.
## Couverture OWASP Top 10
| Categorie | Evaluation |
|---|---|
| A01 Broken Access Control | Pas de multi-utilisateur/role. Routeurs metier proteges. Risque residuel faible a moyen via compte compromis. |
| A02 Cryptographic Failures | JWT en localStorage, TLS delegue au reverse-proxy a documenter, secret en bind mount lisible localement. |
| A03 Injection | SQL/commande correctement limites; validation URL/CSS/strings a renforcer; `v-html` inutile. |
| A04 Insecure Design | Bootstrap `admin/admin` a encadrer, scan reseau authentifie, absence de politique session/password. |
| A05 Security Misconfiguration | CORS permissif pour API interne, headers HTTP a porter par le proxy ou Nginx embarque, conteneurs peu durcis, health public minimal. |
| A06 Vulnerable and Outdated Components | Pas de SCA ni politique d'upgrade; `npm install`; dependance inutilisee. |
| A07 Identification and Authentication Failures | Bootstrap `admin/admin` sans changement force, pas de rate limit, politique password faible. |
| A08 Software and Data Integrity Failures | Build frontend non reproductible strictement; FK SQLite non enforcees; imports JSON peu controles. |
| A09 Logging and Monitoring Failures | Logs securite absents. |
| A10 SSRF | Discovery permet ping/DNS vers cibles fournies par utilisateur authentifie. |
## References
- OWASP ASVS: https://owasp.org/www-project-application-security-verification-standard/
- OWASP Top 10 2021: https://owasp.org/Top10/2021/