
Esempio di Seal Security — app npm vulnerabile (EJS CVE-2022-29078) bonificata a versioni sigillate; integrazione con GitHub Actions + Jenkins
Un'applicazione Node.js/Express minimale e volutamente vulnerabile utilizzata per dimostrare, dall'inizio alla fine, come Seal Security risana una CVE nota sostituendo una dipendenza vulnerabile con una versione sigillata (backportata, plug‑in) — senza modificare le versioni dichiarate o il tuo codice.
È progettata come un test di fumo end‑to‑end per la CLI di Seal in CI/CD: esegui l'app, attiva un exploit reale, esegui Seal e guarda lo stesso exploit venire bloccato.
| Ecosystem | JavaScript / npm |
| Pacchetto vulnerabile | [email protected] (risolto a 2.7.4) |
| CVE | CVE‑2022‑29078 — Iniezione di template lato server EJS → Esecuzione di codice remoto (CVSS 9.8) |
| Versione sigillata (corretta) | ejs 2.7.4-sp1 dal registro npm di Seal |
| Integrazione | CLI Seal come unico step di build — mostrato per GitHub Actions e Jenkins |
L'app include anche altre dipendenze vulnerabili note (lodash 4.17.5, json5 0.5.1, got 6.7.1), che Seal risana anch'esse con build sigillate.
L'app inserisce l'intera stringa di query URL direttamente nella chiamata di rendering EJS:
const data = { name: 'World', ...req.query };
res.render('page', data, ...)
EJS accetta un oggetto settings['view options'] il cui valore outputFunctionName viene scritto — non sanificato — nel corpo della funzione template compilata. Un attaccante può quindi iniettare JavaScript arbitrario che viene eseguito sul server con i privilegi del processo Node.js.
Richiesta normale
/?name=alice
restituisce Hello alice!.
Richiesta exploit
/?name=Hacker&settings[view%20options][outputFunctionName]=x;setTimeout(function()%7Bprocess.exit(1)%7D,3000);s
Il server esegue setTimeout(function(){ process.exit(1) }, 3000). La pagina viene caricata e indica chiaramente che l'RCE è riuscita; ricarica dopo qualche secondo e otterrai ERR_CONNECTION_REFUSED — il codice iniettato ha ucciso il server, dimostrando che codice arbitrario è stato eseguito.
Il ritardo di 3 secondi è intenzionale: consente alla risposta di raggiungere il browser prima che il processo esca, in modo da vedere la pagina "exploit riuscito" e poi un crash pulito anziché una scheda bloccata.
.
├── index.js # l'app Express vulnerabile
├── views/ # template EJS
├── package.json / package-lock.json
├── Jenkinsfile # pipeline Jenkins (Groovy) di esempio con lo stage Seal
└── .github/workflows/
├── build-and-run.yml # build + espone l'app per test browser
└── seal-security.yml # esegue la remediation Seal, poi avvia l'app
Seal è SaaS, ospitato da Seal — nulla viene installato nel tuo ambiente, e tutto il traffico è HTTPS in uscita solo su TCP 443. Per eseguire la remediation hai bisogno di:
| Secret / credenziale | Utilizzo | Dove inserirlo |
|---|---|---|
| Token Seal | Autenticazione della CLI Seal | Segreto GitHub Actions SEAL_TOKEN / credenziale Jenkins "Secret text" seal-token |
| Token ngrok (opzionale) | Esposizione dell'app in esecuzione a un browser per test | Segreto GitHub Actions NGROK_TOKEN |
Configurali in Settings → Secrets and variables → Actions (GitHub) o Manage Jenkins → Credentials (Jenkins). Non committare mai i token nel repository.
Metti in whitelist questi host Seal per l'uscita sulla porta 443:
app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io, e — per i pacchetti npm sigillati — npm.sealsecurity.io. Il binario CLI viene scaricato da github.com / objects.githubusercontent.com.
npm install
npm start # → http://localhost:3001
Apri http://localhost:3001/?name=alice (funziona), poi l'URL dell'exploit sopra (causa il crash del server).
La CLI di Seal viene eseguita come un passo extra, dopo npm install e prima del packaging. Scansiona le dipendenze risolte e riscrive quelle vulnerabili nelle loro versioni sigillate, utilizzando la modalità di correzione remota (la politica è gestita centralmente nell'interfaccia di Seal).
Utilizza seal-community/cli-action:
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: package-lock.json # il file lock per questo ecosistema
Eseguilo tramite Actions → “Seal Security Remediation” → Run workflow. Vedi .github/workflows/seal-security.yml.
Un singolo stage aggiunto, dopo l'installazione e prima del packaging. Vedi 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 proviene dalla credenziale Jenkins seal-token; imposta SEAL_PROJECT al tuo ID progetto Seal.
Dopo seal fix, le dipendenze vulnerabili vengono risolte con build sigillate dal registro di Seal — i range di versione nel tuo package.json rimangono invariati:
| Dipendenza | Prima | Dopo (sigillata) |
|---|---|---|
| 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 |
Una versione sigillata è lo stesso pacchetto con il fix di sicurezza backportato, quindi è una sostituzione plug‑in — nessuna modifica al codice, nessun upgrade di versione maggiore.
Riesegui l'URL dell'exploit sull'app risanata. L'iniezione non viene più eseguita: il ejs sigillato rifiuta il outputFunctionName malizioso e l'app risponde con “Invalid parameter” invece di eseguire il payload. Il server rimane attivo.
seal fix al file manifest/lock specifico — package-lock.json per npm. Per un repository con più manifest, esegui un seal fix per ogni manifest.Questa è l'intera integrazione — uno stage, solo uscita, nessuna modifica al codice applicativo.