140 lines
6.6 KiB
Markdown
140 lines
6.6 KiB
Markdown
# PTA V7.8 — Correctifs de sécurité et descente des droits en base
|
|
|
|
Cette version ne change aucune règle métier. Elle corrige les failles relevées
|
|
lors de l'audit et déplace l'intégralité des règles d'accès dans la base.
|
|
|
|
## Failles corrigées
|
|
|
|
### 1. phpMyAdmin ouvert sans mot de passe (critique)
|
|
|
|
`docker-compose.yml` fournissait `PMA_USER` et `PMA_PASSWORD` au conteneur
|
|
phpMyAdmin. Ces deux variables font basculer phpMyAdmin en `auth_type=config` :
|
|
toute personne atteignant le port arrivait sur une session **déjà
|
|
authentifiée**, avec accès complet en lecture et en écriture à la base. Le
|
|
`.env` publiait de surcroît ce port sur `0.0.0.0`, donc sur tout le réseau.
|
|
|
|
Les deux variables ont été retirées et le port est revenu sur `127.0.0.1`.
|
|
phpMyAdmin réclame désormais un identifiant MariaDB à chaque connexion. Pour y
|
|
accéder depuis un poste distant : `ssh -L 8081:127.0.0.1:8081 utilisateur@serveur`.
|
|
|
|
### 2. XSS stocké via les attributs HTML
|
|
|
|
`PTA.utils.escapeHtml` s'appuyait sur `textContent` / `innerHTML`, qui
|
|
n'échappent ni `"` ni `'`. La fonction était pourtant utilisée dans une
|
|
vingtaine de contextes d'attribut (`value="…"`, `title="…"`, `aria-label="…"`).
|
|
Un libellé contenant un guillemet permettait de sortir de l'attribut et
|
|
d'injecter du script.
|
|
|
|
Les vecteurs réalistes étaient les libellés de vacances scolaires — importés
|
|
depuis une API externe — ainsi que les noms de lieux, les noms d'agents, les
|
|
libellés de diplôme et les commentaires PTA.
|
|
|
|
`escapeHtml` échappe désormais les cinq caractères et reste valable en contexte
|
|
texte comme en contexte attribut. L'implémentation dupliquée `escapeAttr` de
|
|
`annual-overview.js` a été supprimée au profit de la fonction commune.
|
|
|
|
### 3. Règles de refus nginx sans effet
|
|
|
|
Les blocs interdisant `config.php`, `db.php` et `page_bootstrap.php` étaient
|
|
placés **après** `location ~ \.php$`. nginx retenant la première expression
|
|
régulière correspondante, ces refus n'ont jamais été appliqués : les trois
|
|
fichiers d'amorçage étaient exécutables depuis le navigateur.
|
|
|
|
L'ordre des blocs a été corrigé et la couverture élargie (`/docker/`, fichiers
|
|
`.env`, `.sql`, `.ini`, `.log`, `.md`).
|
|
|
|
### 4. Divulgation d'informations techniques
|
|
|
|
`PTA_DEBUG` valait `1` par défaut : le message des exceptions — requêtes SQL,
|
|
noms de tables, chemins serveur — était renvoyé au navigateur. Les pages
|
|
d'erreur de `page_bootstrap.php` et de `role.php` affichaient de la même façon
|
|
`getMessage()`, et `db.php` y ajoutait l'hôte, l'utilisateur et le nom de la
|
|
base issus du message PDO.
|
|
|
|
Le mode debug est désormais désactivé par défaut. Tout détail technique est
|
|
journalisé côté serveur via `App\Core\Security::reportThrowable()` et n'atteint
|
|
le client que si `PTA_DEBUG` est explicitement activé.
|
|
|
|
### 5. Absence d'en-têtes de sécurité
|
|
|
|
Les réponses HTML portent maintenant une `Content-Security-Policy` **à nonce** :
|
|
`script-src 'self' 'nonce-…'`, sans `unsafe-inline`. PTA ne contient aucun
|
|
gestionnaire d'événement inline ni style inline, la politique peut donc rester
|
|
stricte. S'y ajoutent `X-Content-Type-Options`, `X-Frame-Options`,
|
|
`Referrer-Policy`, `Permissions-Policy`, `Cross-Origin-Opener-Policy` et, en
|
|
HTTPS, `Strict-Transport-Security`.
|
|
|
|
Tout nouveau `<script>` inline doit porter `nonce="<?= $cspNonce ?>"`, sinon le
|
|
navigateur refusera de l'exécuter.
|
|
|
|
### 6. Session et CSRF
|
|
|
|
`session_start()` était appelé à trois endroits sans paramètres de cookie.
|
|
`App\Core\Session::start()` centralise le démarrage et impose `HttpOnly`,
|
|
`SameSite=Lax`, `use_strict_mode` et `secure` en HTTPS — y compris hors du
|
|
conteneur Docker, où le `php.ini` fourni ne s'applique pas.
|
|
|
|
Le changement de rôle (`role.php?action=reset`), qui modifiait l'état de la
|
|
session via un simple GET, exige désormais le jeton CSRF. Le jeton est
|
|
régénéré à chaque changement de rôle.
|
|
|
|
### 7. Secrets versionnables
|
|
|
|
Le dépôt n'avait ni `.gitignore` ni `.dockerignore`. Le `.env` contenant les
|
|
mots de passe MariaDB était prêt à être commité, et `COPY . /var/www/html`
|
|
copiait `.env` et `.git` dans l'image publiée. Les deux fichiers ont été créés.
|
|
|
|
## Descente des droits en base
|
|
|
|
`app/Core/Access.php` ne contient plus aucune règle : la matrice des droits et
|
|
les périmètres de visibilité sont lus dans des vues SQL.
|
|
|
|
| Vue | Rôle |
|
|
| ------------------------------ | ----------------------------------------------------------- |
|
|
| `v_acces_role_page` | pages autorisées par rôle |
|
|
| `v_acces_role_permission` | permissions fonctionnelles par rôle |
|
|
| `v_acces_identite_agent` | identités d'agents actifs sélectionnables |
|
|
| `v_acces_identite_structure` | lieux d'affectation actifs sélectionnables |
|
|
| `v_acces_agent_par_structure` | quels agents un lieu peut consulter (rattachement ou planif.) |
|
|
| `v_acces_perimetre_creneau` | lieu et planning d'un créneau, avant modification |
|
|
| `v_acces_perimetre_planning` | lieu et agent d'un planning, avant validation |
|
|
|
|
Les deux premières s'appuient sur les tables `acces_role_page` et
|
|
`acces_role_permission` : **modifier un droit se fait désormais en SQL, sans
|
|
redéploiement**. Une permission absente de la base vaut « refusé ».
|
|
|
|
`SchemaModel::assertCurrent()` vérifie la présence des sept vues au démarrage.
|
|
Si la migration n'a pas été jouée, l'application refuse de démarrer plutôt que
|
|
de tourner sans contrôle de périmètre.
|
|
|
|
## Installation
|
|
|
|
La migration est **appliquée automatiquement au déploiement**. Le service jetable `db-migrate`
|
|
du compose joue `database/migration_v7_8_securite_acces.sql` dès que MariaDB est sain, et le
|
|
service `php` n'est démarré qu'après sa sortie en succès.
|
|
|
|
1. Sauvegarder la base.
|
|
2. Déployer :
|
|
|
|
```bash
|
|
docker compose up -d --build
|
|
docker compose logs db-migrate # doit afficher « Base à jour »
|
|
```
|
|
|
|
La migration est idempotente et suivie dans la table `schema_migration` : un second déploiement
|
|
l'ignore. Elle reste jouable à la main si besoin :
|
|
|
|
```sql
|
|
SOURCE database/migration_v7_8_securite_acces.sql;
|
|
```
|
|
|
|
## Reste à traiter
|
|
|
|
Le sélecteur de `role.php` n'est toujours **pas une authentification** :
|
|
n'importe quel visiteur peut choisir « Service Enfance » et obtenir le contrôle
|
|
complet de l'application. Tant que ce point n'est pas traité, aucune des
|
|
corrections ci-dessus ne protège une instance exposée sur Internet.
|
|
|
|
Le mot de passe MariaDB doit également être changé : le `.env` reprend encore la
|
|
valeur d'exemple.
|