
Exemple Seal Security — application npm vulnérable (EJS CVE-2022-29078) corrigée vers des versions scellées ; intégration GitHub Actions + Jenkins
Une application Node.js/Express minimale, intentionnellement vulnérable, utilisée pour démontrer, de bout en bout, comment Seal Security corrige un CVE connu en remplaçant une dépendance vulnérable par une version scellée (rétroportée, plug-and-play) — sans aucune modification de vos plages de versions déclarées ni de votre code.
Elle est conçue comme un test de fumée global pour le CLI Seal en CI/CD : exécutez l'application, déclenchez une véritable exploitation, exécutez Seal, et voyez la même exploitation être bloquée.
| Écosystème | JavaScript / npm |
| Paquet vulnérable | [email protected] (résout en 2.7.4) |
| CVE | CVE‑2022‑29078 — Injection de template côté serveur EJS → Exécution de code à distance (CVSS 9.8) |
| Version scellée (corrigée) | ejs 2.7.4-sp1 depuis le registre npm de Seal |
| Intégration | CLI Seal comme une étape de build — montrée pour GitHub Actions et Jenkins |
L'application inclut également d'autres dépendances vulnérables bien connues (lodash 4.17.5, json5 0.5.1, got 6.7.1), chacune étant également corrigée par Seal vers une version scellée.
L'application insère l'intégralité de la chaîne de requête URL directement dans l'appel de rendu EJS :
const data = { name: 'World', ...req.query };
res.render('page', data, ...)
EJS accepte un objet settings['view options'] dont la valeur outputFunctionName est écrite — non assainie — dans le corps de la fonction de modèle compilée. Un attaquant peut donc injecter du JavaScript arbitraire qui s'exécute sur le serveur avec les privilèges du processus Node.js.
Requête normale
/?name=alice
affiche Hello alice!.
Requête d'exploitation
/?name=Hacker&settings[view%20options][outputFunctionName]=x;setTimeout(function()%7Bprocess.exit(1)%7D,3000);s
Le serveur exécute setTimeout(function(){ process.exit(1) }, 3000). La page se charge d'abord et indique clairement que l'RCE a réussi ; rechargez quelques secondes plus tard et vous obtiendrez ERR_CONNECTION_REFUSED — le code injecté a tué le serveur, prouvant que du code arbitraire a été exécuté.
Le délai de 3 secondes est intentionnel : il permet à la réponse d'atteindre le navigateur avant que le processus ne se termine, afin que vous voyiez la page "exploitation réussie" puis un plantage propre plutôt qu'un onglet gelé.
.
├── index.js # l'application Express vulnérable
├── views/ # templates EJS
├── package.json / package-lock.json
├── Jenkinsfile # exemple de pipeline Jenkins (Groovy) avec l'étape Seal
└── .github/workflows/
├── build-and-run.yml # build + exposition de l'application pour les tests navigateur
└── seal-security.yml # exécuter la correction Seal, puis démarrer l'application
Seal est SaaS, hébergé par Seal — rien n'est installé dans votre environnement, et tout le trafic est HTTPS sortant sur TCP 443 uniquement. Pour exécuter la correction, vous avez besoin de :
Configurez-les dans Settings → Secrets and variables → Actions (GitHub) ou Manage Jenkins → Credentials (Jenkins). Ne commettez jamais les jetons dans le dépôt.
Liste blanche de ces hôtes Seal pour le trafic sortant 443 : app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io, et — pour les paquets npm scellés — npm.sealsecurity.io. Le binaire CLI est téléchargé depuis github.com / objects.githubusercontent.com.
npm install
npm start # → http://localhost:3001
Ouvrez http://localhost:3001/?name=alice (fonctionne), puis l'URL d'exploitation ci-dessus (plante le serveur).
Le CLI Seal s'exécute comme une étape supplémentaire, après npm install et avant l'empaquetage. Il analyse les dépendances résolues et réécrit les dépendances vulnérables vers leurs versions scellées, en utilisant le mode de correction à distance (la politique est gérée centralement dans l'interface Seal).
Utilise seal-community/cli-action :
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: package-lock.json # le fichier lock pour cet écosystème
Exécutez-le via Actions → “Seal Security Remediation” → Run workflow. Voir .github/workflows/seal-security.yml.
Une seule étape ajoutée, après l'installation et avant l'empaquetage. Voir Jenkinsfile :
stage('Seal') {
steps {
sh '''
curl -fsSL https://github.com/seal-community/cli/releases/download/latest/seal-linux-amd64-latest -o seal
chmod +x seal
./seal fix --mode remote "$SEAL_MANIFEST" # SEAL_MANIFEST=package-lock.json
'''
}
}
SEAL_TOKEN provient du credential Jenkins seal-token ; définissez SEAL_PROJECT sur votre ID de projet Seal.
Après seal fix, les dépendances vulnérables résolvent vers des versions scellées du registre Seal — vos plages de versions package.json restent les mêmes :
Une version scellée est le même paquet avec le correctif de sécurité rétroporté, donc c'est un remplacement plug-and-play — aucune modification de code, aucune mise à jour de version majeure.
Réexécutez l'URL d'exploitation contre l'application corrigée. L'injection ne s'exécute plus : le ejs scellé rejette le outputFunctionName malveillant et l'application répond avec “Invalid parameter” au lieu d'exécuter la charge utile. Le serveur reste actif.
seal fix vers le fichier manifeste/verrou spécifique — package-lock.json pour npm. Pour un dépôt avec plusieurs manifestes, exécutez un seal fix par manifeste.Voilà toute l'intégration — une étape, sortant uniquement, aucune modification du code de l'application.
| Secret / identifiant |
|---|
| Utilisé pour |
|---|
| Où le placer |
|---|
| Seal token | Authentification du CLI Seal | Secret GitHub Actions SEAL_TOKEN / credential Jenkins "Secret text" seal-token |
| ngrok token (optionnel) | Exposer l'application en cours d'exécution à un navigateur pour les tests | Secret GitHub Actions NGROK_TOKEN |
| Dépendance | Avant | Après (scellé) |
|---|
| ejs | 2.7.4 | 2.7.4‑sp1 |
| lodash | 4.17.5 | 4.17.5‑sp1 |
| json5 | 0.5.1 | 0.5.1‑sp1 |
| got | 6.7.1 | 6.7.1‑sp1 |