<span class="fix-status fix-pending"> par <span class="fix-status fix-done">Corrigé le JJ/MM/AAAA</span>
L’application présente plusieurs bonnes pratiques (limitation du login admin, régénération de session après
connexion, contrôle d’URL « intended » vers l’admin, purification HTML via directive Blade personnalisée,
webhook Stripe signé avec option de filtrage IP, uploads TinyMCE restreints aux images sans SVG).
Le point le plus préoccupant identifié était une redirection ouverte (H1) sur le
changement de langue de l’administration — corrigée le 04/05/2026. La politique CSP globale est volontairement
permissive (unsafe-inline / unsafe-eval), réf. M1, ce qui réduit la défense en profondeur
contre le XSS. Les réponses incluant $e->getMessage() côté client ont été supprimées pour TinyMCEController et SettingsController
(L1, corrigée le 06/05/2026 dans ce périmètre). Les clés d’édition du fichier
sfer.php sont désormais validées de façon uniforme (M2, corrigée le 06/05/2026).
La couche Stripe webhooks associe déjà signature HMAC et filtre IP configurable (L2), considérée comme corrigée (validation des mesures) le 06/05/2026,
sous réserve d’actualiser ponctuellement la liste officielle Stripe dans config/stripe_webhooks.php.
| Réf. | Gravité | Statut | Sujet | Fichier / zone |
|---|---|---|---|---|
| H1 | Haute | Corrigé le 04/05/2026 | Redirection ouverte (paramètre redirect + url()->previous()) — traitée |
app/Http/Controllers/Admin/AdminLocaleController.php |
| M1 | Moyenne | À corriger | CSP globale avec unsafe-inline et unsafe-eval |
app/Http/Middleware/SecurityHeaders.php |
| M2 | Moyenne | Corrigé le 06/05/2026 | Validation des clés config/sfer.php (update / add / delete) harmonisée — traitée |
app/Http/Controllers/Admin/SettingsController.php |
| L1 | Faible | Corrigé le 06/05/2026 | Messages d’erreur HTTP sans fuite de getMessage() — TinyMCEController, SettingsController (autres contrôleurs possibles) |
app/Http/Controllers/Admin/TinyMCEController.php, SettingsController.php |
| L2 | Faible | Corrigé le 06/05/2026 | config/stripe_webhooks.php, VerifyStripeWebhookSourceIp, StripeWebhookController |
|
| I1 | Informative | À traiter si besoin | Surface d’admin « notifications / dashboard » sans middleware de module métier | routes/admin.php |
| I2 | Informative | À traiter si besoin | Référence à une vue publique inexistante pages.show |
app/Http/Controllers/PageController.php |
| I3 | Informative | À traiter si besoin | Routes admin dynamiques issues des slugs « modules » en base | routes/admin.php |
| I4 | Informative | À traiter si besoin | Middleware CheckAdminModulePermission — permissions pour actions hors CRUD |
app/Http/Middleware/CheckAdminModulePermission.php |
Correctif : la redirection après changement de langue n’accepte plus qu’une cible sous le préfixe
/cp-admin, avec vérification d’hôte et de port lorsqu’une URL absolue est fournie ;
les URL externes et les chemins contenant .. sont rejetés. Comportement aligné avec
AdminLoginController::sendLoginResponse pour l’URL « intended ».
Ancienne exposition : usage de $request->query('redirect') et de url()->previous()
passés à redirect()->to() sans filtrage strict (phishing ou autre destination externe possible).
Route concernée : GET /cp-admin/set-language/{locale}
(admin.set-language). Remplacez monsite.example par votre hôte réel.
redirect vers un site tiers (clone de login) :https://monsite.example/cp-admin/set-language/fr?redirect=https://phishing-attaquant.example/faux-cp-admin
https://monsite.example/cp-admin/set-language/en?redirect=https%3A%2F%2Fevil.example%2Fcollecte
//), souvent acceptée comme « hors du domaine légitime » sans filtre :https://monsite.example/cp-admin/set-language/fr?redirect=//malveillant.example/page
Une autre variante passait par l’en-tête Referer : la victime arrive d’abord depuis un lien externe,
puis url()->previous() pouvait devenir cette origine ; après changement de langue, elle était encore redirigée vers le site hostile sans paramètre redirect dans l’URL affichée.
URL (Uniform Resource Locator) : adresse web (schéma, hôte, chemin, paramètres). Open redirect : la requête aboutit à une redirection vers une destination partiellement ou totalement contrôlée par l’attaquant. Phishing : hameçonnage — tromper l’utilisateur pour qu’il divulgue des identifiants ou exécute une action.
Recommandation : n’autoriser que les chemins relatifs commençant par
/cp-admin (sur le même principe que sendLoginResponse dans
AdminLoginController), ou valider explicitement host + path.
SecurityHeaders définit une CSP avec
script-src incluant 'unsafe-inline' et 'unsafe-eval'. C’est compréhensible pour TinyMCE, Tailwind CDN, etc., mais tout script inline reste autorisé : en cas de
XSS, la mitigation navigateur est fortement diminuée.
CSP : voir glossaire. unsafe-inline : autorise les blocs et gestionnaires d’événements inline dans le HTML.
unsafe-eval : autorise eval() et constructions équivalentes (ex. certains modèles de génération de code côté navigateur).
XSS : injection de script exécuté dans le contexte du site (vol de cookie de session si non protégé, actions au nom de l’utilisateur, etc.).
Recommandation : nonces ou hashes pour les scripts statiques ; routes CSP distinctes pour l’admin et le front ; éviter Tailwind CDN en production au profit d’un build.
Correctif : la validation de la clé utilise désormais les mêmes règles pour
updateConfigVariable, addConfigVariable et deleteConfigVariable,
via une méthode privée commune (regex:/^[a-zA-Z0-9_.]+$/, longueur max 255).
Ancienne exposition : updateConfigVariable n’exigeait qu’une chaîne max:255,
tandis que seul addConfigVariable appliquait la regex restrictive — risque de clés ou segments
atypiques lors d’une mise à jour.
Config applicative : fichier PHP lu au démarrage (ex. coordonnées société, paramètres métier). Ce n’est pas une CSRF en soi, mais la surface d’écriture fichier augmente l’impact d’un compte admin compromis.
sfer.php pour rediriger des e-mails, afficher du contenu malveillant, ou casser applicativement le site (disponibilité / intégrité).Recommandation : réutiliser la même contrainte de validation que pour
addConfigVariable et, idéalement, une liste blanche des clés modifiables en production.
Correctif (périmètre audit) : les blocs catch concernés appellent désormais report($e)
pour journaliser l’erreur réelle et renvoient un message générique traduit (admin/settings.messages.errors.unexpected,
path_not_allowed pour certains chemins invalide sous lang/, admin/tinymce.upload_failed pour l’upload).
Ancienne exposition : concaténation de $e->getMessage() dans des réponses JSON ou en session flash pouvait révéler chemins système ou détails d’infra.
JSON : format d’échange de données texte souvent utilisé par les API et les réponses AJAX. Information disclosure (fuite d’information) : exposer des détails internes utiles à un attaquant pour affiner une attaque (versions, chemins, requêtes SQL dans le message d’erreur, etc.).
Recommandation : journaliser en interne uniquement (report() /
Log::error) et réponse générique au client hors environnement debug.
POST /stripe/webhook vérifie d’abord l’origine (stripe.webhook.ip + fichier
config/stripe_webhooks.php), puis StripeWebhookController valide la charge utile avec
Webhook::constructEvent et le secret de webhook (services.stripe.webhook_secret).
Le risque signalé était opérationnel (liste d’IP officielle évolutive) : le suivi passe par une
resynchronisation périodique avec la documentation Stripe (ips_webhooks.json) et une surveillance des logs
[Stripe Webhook].
Webhook : appel HTTP serveur à serveur (ici Stripe → votre application) suite à un événement paiement. IP allowlist / liste blanche : n’accepte les requêtes que depuis des adresses IP connues comme appartenant au prestataire. DoS fonctionnel — ici indirect : paiements qui ne confirment plus si tout le trafic est rejeté.
Pour la suite : garder STRIPE_WEBHOOK_VERIFY_IP=true en production et reporter dans ce fichier de config ou en procédure interne tout changement venant des plages Stripe.
Ces routes sont protégées par admin.auth mais pas par le middleware
admin.module.permission contrairement au gros du back-office. Tout administrateur authentifié y a
donc accès, ce qui peut être voulu fonctionnellement mais élargit la surface par rapport aux modules métiers à
permission explicite.
Middleware (Laravel) : couche qui filtre la requête avant le contrôleur (ici authentification vs permissions fines). RBAC : voir glossaire — ici écart entre « tout admin » et « admin avec rôle module X ».
pages.show absente du dépôt
À traiter si besoin
PageController::show renvoie view('pages.show', …) alors qu’aucun fichier Blade
correspondant n’apparaît sous resources/views/pages/ : risque d’erreur 500 lors de l’affichage d’une page CMS par slug.
Blade : moteur de templates Laravel. HTTP 500 : erreur serveur interne.
Stack trace : détail technique parfois affiché si APP_DEBUG=true en production (fuite massive d’information).
Les ressources admin sont créées à partir du slug en base (Module::where('is_active', true)).
Une compromission de la base permettrait des changements de surface d’attaque ou un chargement non prévu si un
contrôleur existe.
ORM / Eloquent : accès base de données par objets (Laravel). Ici les slugs en base pilotent les segments d’URL.
modules.slug pour exposer de nouvelles routes (si un contrôleur existe) ou créer confusion.CheckAdminModulePermission
À traiter si besoin
Les méthodes de contrôleur nommées autrement que les actions REST classiques créent une permission du type
module.action littérale. Il faut s’assurer que les permissions correspondantes sont bien créées dans
Spatie ; sinon soit tout le monde est bloqué, soit des contournements par rôle peuvent être possibles selon votre
politique (super_admin passe par Gate::before). Revue métier conseillée.
Gate : façade Laravel pour l’autorisation. Spatie Laravel Permission : paquet tiers pour rôles/permissions persistés en base.
CRUD : opérations classiques mapping vers view, create, edit, delete.
super_admin contourne les permissions — à surveiller sur la gouvernance des comptes./cp-admin (évite redirection vers site tiers).@sanitized branchée sur HTMLPurifier pour le contenu riche.assertPathWithinLangDir limite les chemins sous lang/.default-src./debug/* commentées dans web.php (attention à les laisser absentes en production).AdminLocaleController::switchsfer.php (M2) · SettingsController.$e->getMessage() supprimée côté client pour SettingsController et TinyMCEController (L1) — report($e) côté serveur. Poursuivre la même règle sur les autres contrôleurs admin si besoin.resources/views/pages/show.blade.php (I2) référencée par PageController::show.composer audit) et configuration serveur (.env hors production).