## https://sploitus.com/exploit?id=DB196B4C-A537-5744-8870-B94831744BBC
# Seal Security — Python (pip) Example
A minimal, intentionally vulnerable Flask application used to demonstrate, end‑to‑end, how
[Seal Security](https://www.sealsecurity.io/) remediates a known CVE by replacing a vulnerable
dependency with a **sealed** (backported, drop‑in) version — with **no change to your declared
requirements or your code**.
It is designed as a front‑to‑back smoke test for the Seal CLI in CI/CD: run the app, trigger a
real exploit, run Seal, and watch the same exploit get blocked.
---
## What this example demonstrates
| | |
|---|---|
| **Ecosystem** | Python / pip |
| **Vulnerable package** | `PyYAML==5.1` |
| **CVE** | [CVE‑2020‑14343](https://nvd.nist.gov/vuln/detail/CVE-2020-14343) — `yaml.load` / `FullLoader` deserialization → arbitrary code execution |
| **Sealed (fixed) version** | `pyyaml 5.1+sp1` from Seal's PyPI registry |
| **Integration** | Seal CLI as one build step — shown for both **GitHub Actions** and **Jenkins** |
---
## How the exploit works
The welcome page takes a `name` and parses it through PyYAML's default loader:
```python
parsed = yaml.load(name) # PyYAML 5.1 → unsafe FullLoader (CVE-2020-14343)
```
On PyYAML 5.1, `yaml.load()` without an explicit `SafeLoader` uses the **FullLoader**, which can
construct arbitrary Python objects from untrusted input. An attacker submits a YAML payload that
evaluates arbitrary Python — here, running a shell command and returning its output.
**Normal request:** enter `alice` → renders `Welcome, alice!`
**Exploit payload** (paste into the name field):
```
!!python/object/apply:tuple [!!python/object/apply:map [!!python/name:eval , ["__import__('subprocess').check_output(['id']).decode()"]]]
```
The page renders the output of the server‑side `id` command — proof that arbitrary code ran on
the server, driven purely by untrusted input.
---
## Repository layout
```
.
├── 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
```
---
## Prerequisites
Seal is **SaaS, Seal‑hosted** — nothing is installed inside your environment, and all traffic
is **outbound HTTPS on TCP 443** only. To run the remediation you need:
| Secret / credential | Used for | Where it goes |
|---|---|---|
| **Seal token** | Authenticating the Seal CLI | GitHub Actions secret `SEAL_TOKEN` / Jenkins "Secret text" credential `seal-token` |
| **ngrok token** *(optional)* | Exposing the running app to a browser for testing | GitHub Actions secret `NGROK_TOKEN` |
> Configure these in **Settings → Secrets and variables → Actions** (GitHub) or
> **Manage Jenkins → Credentials** (Jenkins). Never commit tokens to the repo.
Allowlist these Seal hosts for outbound 443:
`app.sealsecurity.io`, `authorization.sealsecurity.io`, `cli.sealsecurity.io`, and — for
sealed pip packages — **`pypi.sealsecurity.io`**. The CLI binary is downloaded from
`github.com` / `objects.githubusercontent.com`.
---
## Run it locally
```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python app.py # → http://localhost:5000
```
Open `http://localhost:5000/`, enter a normal name (works), then paste the exploit payload.
---
## Remediate with Seal
The Seal CLI runs as **one extra step**, after `pip install` and before packaging. It scans the
resolved dependencies and rewrites the vulnerable ones to their sealed versions, using **remote**
fix mode (policy is managed centrally in the Seal UI).
### Option A — GitHub Actions
Uses [`seal-community/cli-action`](https://github.com/seal-community/cli-action):
```yaml
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: requirements.txt # the manifest for this ecosystem
```
Run it via **Actions → “Seal Security Remediation” → Run workflow**. See
[`.github/workflows/seal-security.yml`](.github/workflows/seal-security.yml).
### Option B — Jenkins (Groovy pipeline)
A single added stage, after install and before packaging. See [`Jenkinsfile`](Jenkinsfile):
```groovy
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` comes from the `seal-token` Jenkins credential; set `SEAL_PROJECT` to your Seal
Project ID.
---
## What Seal changes
After `seal fix`, the vulnerable dependency resolves to a sealed build from Seal's PyPI
registry — same package, security fix backported:
| Dependency | Before | After (sealed) |
|---|---|---|
| PyYAML | 5.1 | **5.1+sp1** |
A sealed version is the *same* package with the security fix backported — a drop‑in
replacement, no code changes and no major‑version upgrade.
## Verify the fix
Re‑run the exploit against the remediated app. The sealed `PyYAML` refuses to construct the
malicious objects, so `yaml.load` raises instead of executing the payload, and the app responds
with **“Invalid input — payload rejected.”** Normal names still work.
---
## How to add Seal to your own project
1. Add **one step** to your pipeline, **after** dependencies are installed and **before**
packaging.
2. Point `seal fix` at the specific manifest — `requirements.txt` for pip. For a repo with
multiple manifests/lock files, run one `seal fix` per manifest.
3. Use **remote** fix mode so your security team manages remediation policy centrally in the
Seal UI — nothing is committed to the repo.
4. Provide the Seal token via your CI secret store (GitHub secret / Jenkins credential).
That's the whole integration — one stage, outbound‑only, no changes to application code.