xpeditis2.0/audit_security/stripe-session-organisation/stripe-session-organisation.md

51 lines
5.4 KiB
Markdown

# SEC-17 — Stripe : session Checkout non liée à son organisation
**Gravité : Moyenne, avant correction.**
**État au 14 septembre 2026 :** Correctif local non commité au début de cette rédaction ; déploiement inconnu.
## Executive Summary
Un MANAGER connecté connaît l'identifiant d'une session Checkout créée pour une autre organisation. Il appelle `POST /api/v1/subscriptions/sync` avec ce sessionId. La session doit désigner un abonnement qui n'est pas déjà lié à une autre ligne locale protégée par l'unicité. La connaissance de cet identifiant est un prérequis ; aucune fuite générique n'est démontrée.
Le commit `c09b8be` contient encore le comportement vulnérable ; le correctif examiné est dans les modifications locales du 14 septembre. Aucun tag local ni version de production vérifiée ne permet d'annoncer une première release affectée ou une release déployée corrigée. La validation combine relecture du source et tests locaux documentés ; aucun incident réel n'est affirmé.
## Background
La frontière de sécurité est celle décrite par les prérequis ci-dessus. Le paramétrage fourni par le dépôt ne permet pas de connaître la topologie et les valeurs effectivement en ligne. Les preuves disponibles doivent donc être lues séparément des conditions de déploiement restant à vérifier.
## Vulnerability Details
Le contrôleur impose ADMIN/MANAGER et fournit l'organisation de la session authentifiée au service. `StripeAdapter.createCheckoutSession` écrit pourtant une métadonnée organizationId fiable, issue de l'appel serveur. L'ancien `SubscriptionService.syncFromStripe` récupérait la session Stripe, utilisait ses identifiants customer/subscription puis mettait à jour l'offre locale sans comparer cette métadonnée. Le succès de la récupération chez Stripe prouve l'existence de l'objet, pas son appartenance à l'appelant. La contrainte UNIQUE stripe_subscription_id interdit un rattachement déjà enregistré, mais ne protège pas avant livraison du webhook, après son échec ou pour un ancien identifiant libéré lors d'un changement d'offre.
Sources courantes, fonctions et tests concernés :
- [apps/backend/src/application/controllers/subscriptions.controller.ts](../../apps/backend/src/application/controllers/subscriptions.controller.ts)
- [apps/backend/src/application/services/subscription.service.ts](../../apps/backend/src/application/services/subscription.service.ts)
- [apps/backend/src/infrastructure/stripe/stripe.adapter.ts](../../apps/backend/src/infrastructure/stripe/stripe.adapter.ts)
- [apps/backend/src/application/services/subscription-sync.security.spec.ts](../../apps/backend/src/application/services/subscription-sync.security.spec.ts)
Pour comparer au snapshot vulnérable, consulter ces mêmes chemins dans la révision citée, sans supposer que les numéros de lignes actuels correspondent à l'ancienne version.
## Exploitability Analysis
Attribution locale d'une offre et d'identifiants de facturation d'un autre tenant à l'organisation appelante dans la fenêtre décrite. Aucun vol de carte bancaire, accès au compte Stripe global ou exploitation fiable de course en production n'est établi. La menace exige un identifiant Checkout étranger connu et l'absence de conflit d'unicité ; ces conditions expliquent la gravité moyenne retenue.
Le problème ne requiert pas de supprimer les contrôles métier ou cryptographiques voisins. Il exploite précisément la différence entre le contrôle attendu et celui effectivement exécuté. Les contre-exemples ci-dessous précisent ce que les tests isolent ; ils ne constituent pas un test de pénétration du site en ligne.
## Proof of Concept
Avant correction, deux tests malveillants échouaient parce que le service résolvait la promesse et persistait GOLD ; le contrôle même organisation réussissait. Après correction, six tests passent : métadonnées étrangères ou absentes, customer identique mais organisation étrangère, première synchronisation légitime, changement d'abonnement légitime et rafraîchissement sans session. Stripe est simulé ; la contrainte d'unicité n'est pas testée par une course contre une vraie base.
Les artefacts sont déjà dans les fichiers de test liés ci-dessus. Aucun faux journal d'exploitation ni nouvelle commande d'attaque de production n'est fourni. Voir [VALIDATION.md](../VALIDATION.md) pour le périmètre et les résultats consolidés.
## Remediation
La comparaison `checkoutSession.metadata?.organizationId !== organizationId` provoque désormais un refus avant de consommer les identifiants Stripe. Les métadonnées absentes sont également refusées. On n'impose pas une égalité stricte avec un ancien customerId local : plusieurs sessions concurrentes d'une même organisation peuvent légitimement exister. La synchronisation sans session conserve son comportement en utilisant l'abonnement local déjà lié. Ce problème est distinct d'une signature webhook invalide, déjà contrôlée ailleurs.
La présente passe documente ce changement antérieur ; elle n'ajoute aucun correctif applicatif et ne confirme pas son déploiement.
## Summary
Le mécanisme décrit est confirmé dans la révision vulnérable citée, et la portée du correctif local est bornée par les tests disponibles. Correctif local non commité au début de cette rédaction ; déploiement inconnu. Les limites de couverture générale sont détaillées dans [COUVERTURE.md](../COUVERTURE.md).