
Esempio di Seal Security — app pip vulnerabile (PyYAML CVE-2020-14343) corretta con versioni sealed; integrazione GitHub Actions + Jenkins
Un'applicazione Flask minima e intenzionalmente vulnerabile utilizzata per dimostrare, end‑to‑end, come Seal Security ripara una CVE nota sostituendo una dipendenza vulnerabile con una versione sigillata (backport, drop‑in) — senza alcuna modifica ai requisiti dichiarati o al codice.
È progettata come uno smoke test completo per la CLI Seal in CI/CD: esegui l'app, innesca un exploit reale, esegui Seal e osserva lo stesso exploit venire bloccato.
| Ecosistema | Python / pip |
| Pacchetto vulnerabile | PyYAML==5.1 |
| CVE | CVE‑2020‑14343 — deserializzazione yaml.load / FullLoader → esecuzione di codice arbitrario |
| Versione sigillata (corretta) | pyyaml 5.1+sp1 dal registro PyPI di Seal |
| Integrazione | CLI Seal come passaggio di build — mostrata sia per GitHub Actions che per Jenkins |
La pagina di benvenuto riceve un name e lo analizza tramite il loader predefinito di PyYAML:
parsed = yaml.load(name) # PyYAML 5.1 → unsafe FullLoader (CVE-2020-14343)
Con PyYAML 5.1, yaml.load() senza un SafeLoader esplicito usa il FullLoader, che può costruire oggetti Python arbitrari da input non attendibili. Un attaccante invia un payload YAML che esegue codice Python arbitrario sul server.
Richiesta normale
/?name=alice → Welcome, alice!
Richiesta exploit — passa questo YAML come name (già URL‑encoded nei log del workflow):
!!python/object/apply:tuple [!!python/object/apply:map [!!python/name:eval , ["__import__('subprocess').check_output(['id']).decode()"]]]
Il PyYAML vulnerabile deserializza ed esegue il payload, l'app mostra una pagina “You've been pwned” e il server viene poi terminato (pochi secondi dopo, così la pagina viene prima servita). Ricarica e l'app non c'è più — via ngrok vedrai una pagina “endpoint offline”.
.
├── app.py # the vulnerable Flask app
├── requirements.txt # declares PyYAML==5.1
├── Jenkinsfile # example Jenkins (Groovy) pipeline with the Seal stage
└── .github/workflows/
├── build-and-run.yml # run + expose the app for browser testing
└── seal-security.yml # run Seal remediation, then start the app
Seal è SaaS, ospitato da Seal — non viene installato nulla nel tuo ambiente e tutto il traffico è HTTPS in uscita sulla sola porta TCP 443. Per eseguire la remediation ti serve:
| Segreto / credenziale |
|---|
Configurali in Settings → Secrets and variables → Actions (GitHub) o in Manage Jenkins → Credentials (Jenkins). Non committare mai i token nel repository.
Aggiungi alla allowlist questi host Seal per la porta 443 in uscita:
app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io e — per i pacchetti pip sigillati — pypi.sealsecurity.io. Il binario della CLI viene scaricato da github.com / objects.githubusercontent.com.
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python app.py # → http://localhost:5000
Apri http://localhost:5000/?name=alice (funziona), poi invia il payload dell'exploit qui sopra come name — l'app mostra "You've been pwned" e il server viene terminato pochi secondi dopo.
La CLI Seal viene eseguita come un passaggio aggiuntivo, dopo pip install e prima del packaging. Analizza le dipendenze risolte e riscrive quelle vulnerabili nelle loro versioni sigillate, usando la modalità di fix remote (la policy è gestita centralmente nella UI di Seal).
Usa seal-community/cli-action:
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: requirements.txt # the manifest for this ecosystem
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=requirements.txt
'''
}
}
SEAL_TOKEN proviene dalla credenziale Jenkins seal-token; imposta SEAL_PROJECT sul tuo ID progetto Seal.
Dopo seal fix, la dipendenza vulnerabile viene risolta in una build sigillata dal registro PyPI di Seal — stesso pacchetto, fix di sicurezza backportato:
| Dipendenza | Prima | Dopo (sigillata) |
|---|---|---|
| PyYAML | 5.1 | 5.1+sp1 |
Una versione sigillata è lo stesso pacchetto con il fix di sicurezza backportato — una sostituzione drop‑in, senza modifiche al codice e senza upgrade di versione maggiore.
Esegui di nuovo l'exploit contro l'app riparata. Il PyYAML sigillato rifiuta di costruire gli oggetti dannosi, quindi yaml.load solleva un'eccezione invece di eseguire il payload, e l'app risponde con “Invalid input — payload rejected.” I nomi normali continuano a funzionare.
seal fix il manifest specifico — requirements.txt per pip. Per un repository con più manifest/lock file, esegui un seal fix per ogni manifest.Questa è l'intera integrazione — uno stage, solo traffico in uscita, nessuna modifica al codice dell'applicazione.
| Utilizzato per |
|---|
| Dove va |
|---|
| Seal token | Autenticazione della CLI Seal | Segreto GitHub Actions SEAL_TOKEN / credenziale Jenkins "Secret text" seal-token |
| ngrok token (facoltativo) | Esporre l'app in esecuzione a un browser per i test | Segreto GitHub Actions NGROK_TOKEN |