
Ejemplo de Seal Security — aplicación npm vulnerable (EJS CVE-2022-29078) corregida a versiones selladas; integración con GitHub Actions + Jenkins
Una aplicación Node.js/Express mínima e intencionadamente vulnerable, utilizada para demostrar, de extremo a extremo, cómo Seal Security remedia un CVE conocido reemplazando una dependencia vulnerable por una versión sellada (con backport, de sustitución directa) — sin ningún cambio en tus rangos de versiones declarados ni en tu código.
Está diseñada como una prueba de humo de principio a fin para el Seal CLI en CI/CD: ejecuta la aplicación, lanza un exploit real, ejecuta Seal y observa cómo el mismo exploit queda bloqueado.
| Ecosistema | JavaScript / npm |
| Paquete vulnerable | [email protected] (se resuelve a 2.7.4) |
| CVE | CVE‑2022‑29078 — inyección de plantillas en el lado del servidor de EJS → Ejecución remota de código (CVSS 9.8) |
| Versión sellada (corregida) | ejs 2.7.4-sp1 del registro npm de Seal |
| Integración | Seal CLI como un paso de compilación más — mostrado tanto para GitHub Actions como para Jenkins |
La aplicación también incluye otras dependencias vulnerables conocidas (lodash 4.17.5, json5 0.5.1,
got 6.7.1), cada una de las cuales Seal también remedia a una compilación sellada.
La aplicación extiende toda la cadena de consulta de la URL directamente en la llamada de renderizado de EJS:
const data = { name: 'World', ...req.query };
res.render('page', data, ...)
EJS acepta un objeto settings['view options'] cuyo valor de outputFunctionName se escribe —
sin sanear — en el cuerpo de la función de plantilla compilada. Un atacante puede, por tanto,
inyectar JavaScript arbitrario que se ejecuta en el servidor con los privilegios del proceso Node.js.
Petición normal
/?name=alice
renderiza Hello alice!.
Petición de exploit
/?name=Hacker&settings[view%20options][outputFunctionName]=x;setTimeout(function()%7Bprocess.exit(1)%7D,3000);s
El servidor ejecuta setTimeout(function(){ process.exit(1) }, 3000). La página se carga primero y
declara claramente que el RCE ha tenido éxito; recarga unos segundos más tarde y obtendrás
ERR_CONNECTION_REFUSED — el código inyectado ha matado al servidor, lo que demuestra que se ejecutó código arbitrario.
El retraso de 3 segundos es intencional: permite que la respuesta llegue al navegador antes de que el proceso finalice, así ves la página de "exploit con éxito" y luego un cierre limpio en lugar de una pestaña congelada.
.
├── index.js # the vulnerable Express app
├── views/ # EJS templates
├── package.json / package-lock.json
├── Jenkinsfile # example Jenkins (Groovy) pipeline with the Seal stage
└── .github/workflows/
├── build-and-run.yml # build + expose the app for browser testing
└── seal-security.yml # run Seal remediation, then start the app
Seal es SaaS, alojado por Seal — no se instala nada en tu entorno y todo el tráfico es HTTPS saliente únicamente por TCP 443. Para ejecutar la remediación necesitas:
Configúralos en Settings → Secrets and variables → Actions (GitHub) o Manage Jenkins → Credentials (Jenkins). Nunca hagas commit de tokens en el repositorio.
Añade a la lista blanca estos hosts de Seal para el puerto 443 saliente:
app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io y — para
paquetes npm sellados — npm.sealsecurity.io. El binario del CLI se descarga de
github.com / objects.githubusercontent.com.
npm install
npm start # → http://localhost:3001
Abre http://localhost:3001/?name=alice (funciona) y luego la URL del exploit de arriba (hace fallar el servidor).
El Seal CLI se ejecuta como un paso adicional, después de npm install y antes del empaquetado.
Escanea las dependencias resueltas y reescribe las vulnerables a sus versiones selladas, usando
el modo de corrección remoto (la política se gestiona de forma centralizada en la interfaz de Seal).
Utiliza seal-community/cli-action:
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: package-lock.json # the lock file for this ecosystem
Ejecútalo mediante Actions → “Seal Security Remediation” → Run workflow. Consulta
.github/workflows/seal-security.yml.
Una única etapa añadida, después de la instalación y antes del empaquetado. Consulta 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 de la credencial seal-token de Jenkins; define SEAL_PROJECT con el ID de tu
proyecto de Seal.
Después de seal fix, las dependencias vulnerables se resuelven a compilaciones selladas del registro de Seal —
tus rangos de versiones de package.json permanecen iguales:
Una versión sellada es el mismo paquete con el backport de la corrección de seguridad, por lo que es un sustituto directo — sin cambios de código ni actualización de versión principal.
Vuelve a ejecutar la URL del exploit contra la aplicación remediada. La inyección ya no se ejecuta: el ejs
sellado rechaza el outputFunctionName malicioso y la aplicación responde con “Invalid parameter”
en lugar de ejecutar la carga útil. El servidor permanece activo.
seal fix al archivo de manifiesto/bloqueo específico — package-lock.json para npm. Para un repositorio
con varios manifiestos, ejecuta un seal fix por manifiesto.Esa es toda la integración — una etapa, solo saliente, sin cambios en el código de la aplicación.
| Secreto / credencial |
|---|
| Se usa para |
|---|
| Dónde se guarda |
|---|
| Token de Seal | Autenticar el Seal CLI | Secreto de GitHub Actions SEAL_TOKEN / credencial "Secret text" de Jenkins seal-token |
| Token de ngrok (opcional) | Exponer la aplicación en ejecución a un navegador para probarla | Secreto de GitHub Actions NGROK_TOKEN |
| Dependencia | Antes | Después (sellado) |
|---|
| 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 |