
Exemplo do Seal Security — aplicativo npm vulnerável (EJS CVE-2022-29078) corrigido para versões seladas; integração com GitHub Actions + Jenkins
Uma aplicação Node.js/Express mínima e intencionalmente vulnerável usada para demonstrar, de ponta a ponta, como a Seal Security corrige um CVE conhecido substituindo uma dependência vulnerável por uma versão selada (backport, drop-in) — sem nenhuma alteração nas suas faixas de versão declaradas ou no seu código.
Foi projetado como um teste de fumaça de ponta a ponta para o CLI do Seal em CI/CD: execute o app, dispare um exploit real, execute o Seal e veja o mesmo exploit ser bloqueado.
| Ecossistema | JavaScript / npm |
| Pacote vulnerável | [email protected] (resolve para 2.7.4) |
| CVE | CVE‑2022‑29078 — Injeção de template no lado do servidor EJS → Execução Remota de Código (CVSS 9.8) |
| Versão selada (corrigida) | ejs 2.7.4-sp1 do registro npm do Seal |
| Integração | CLI do Seal como uma etapa de build — mostrado tanto para GitHub Actions quanto para Jenkins |
O app também inclui outras dependências vulneráveis conhecidas (lodash 4.17.5, json5 0.5.1, got 6.7.1), cada uma das quais o Seal também corrige para uma build selada.
O app espalha toda a string de consulta da URL diretamente na chamada de renderização do EJS:
const data = { name: 'World', ...req.query };
res.render('page', data, ...)
O EJS aceita um objeto settings['view options'] cujo valor outputFunctionName é escrito — não sanitizado — no corpo da função de template compilada. Um atacante pode, portanto, injetar JavaScript arbitrário que é executado no servidor com os privilégios do processo Node.js.
Requisição normal
/?name=alice
renderiza Hello alice!.
Requisição de exploit
/?name=Hacker&settings[view%20options][outputFunctionName]=x;setTimeout(function()%7Bprocess.exit(1)%7D,3000);s
O servidor executa setTimeout(function(){ process.exit(1) }, 3000). A página carrega primeiro e afirma claramente que o RCE teve sucesso; recarregue alguns segundos depois e você obterá ERR_CONNECTION_REFUSED — o código injetado matou o servidor, provando que código arbitrário foi executado.
O atraso de 3 segundos é intencional: permite que a resposta chegue ao navegador antes que o processo termine, para que você veja a página de "exploit bem-sucedido" e depois uma falha limpa, em vez de uma aba 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
O Seal é SaaS, hospedado pelo Seal — nada é instalado dentro do seu ambiente, e todo o tráfego é HTTPS de saída na porta TCP 443 apenas. Para executar a correção, você precisa:
| Segredo/credencial |
|---|
Configure estes em Settings → Secrets and variables → Actions (GitHub) ou Manage Jenkins → Credentials (Jenkins). Nunca faça commit de tokens no repositório.
Coloque na lista de permissões estes hosts do Seal para saída na porta 443: app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io e — para pacotes npm selados — npm.sealsecurity.io. O binário do CLI é baixado de github.com / objects.githubusercontent.com.
npm install
npm start # → http://localhost:3001
Abra http://localhost:3001/?name=alice (funciona), depois a URL de exploit acima (derruba o servidor).
O CLI do Seal executa como uma etapa extra, após npm install e antes do empacotamento. Ele escaneia as dependências resolvidas e reescreve as vulneráveis para suas versões seladas, usando o modo de correção remoto (a política é gerenciada centralmente na UI do Seal).
Usa 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
Execute via Actions → “Seal Security Remediation” → Run workflow. Veja .github/workflows/seal-security.yml.
Um único estágio adicionado, após a instalação e antes do empacotamento. Veja 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 vem da credencial seal-token do Jenkins; defina SEAL_PROJECT para o ID do seu Projeto Seal.
Após seal fix, as dependências vulneráveis resolvem para builds seladas do registro do Seal — suas faixas de versão no package.json permanecem as mesmas:
Uma versão selada é o mesmo pacote com a correção de segurança backportada, portanto é uma substituição drop-in — sem alterações de código, sem upgrade de versão principal.
Execute novamente a URL de exploit contra o app corrigido. A injeção não é mais executada: o ejs selado rejeita o outputFunctionName malicioso e o app responde com “Invalid parameter” em vez de executar o payload. O servidor permanece ativo.
seal fix para o arquivo de manifesto/lock específico — package-lock.json para npm. Para um repositório com múltiplos manifestos, execute um seal fix por manifesto.Essa é toda a integração — um estágio, apenas saída, sem alterações no código da aplicação.
| Usado para |
|---|
| Onde vai |
|---|
| Seal token | Autenticando o CLI do Seal | Segredo do GitHub Actions SEAL_TOKEN / credencial "Secret text" do Jenkins seal-token |
| token ngrok (opcional) | Expondo o app em execução a um navegador para testes | Segredo do GitHub Actions NGROK_TOKEN |
| Dependência | Antes | Depois (selado) |
|---|
| 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 |