Share
## https://sploitus.com/exploit?id=631F51D0-6C44-5F72-989F-53FC65E75426
# CVE-2026-32710

**Heap buffer overflow in MariaDB `JSON_SCHEMA_VALID()` β†’ persistent privilege escalation**

| | |
|---|---|
| **Affected** | MariaDB 11.4.x (confirmed on 11.4.9) |
| **Bug** | OOB write in `json_get_normalized_string()` β€” `strncpy` into 128-byte `DYNAMIC_STRING` with no bounds check |
| **Impact** | `SELECT`-only user β†’ `ALL PRIVILEGES WITH GRANT OPTION`, persistent across server restart |
| **Source** | `sql/json_schema_helper.cc:91` |

> **Lab-assisted.**  The script uses Docker / root introspection to read
> `/proc/1/mem` and discover heap layout and `Security_context` addresses.
> A weaponized exploit would need an info-leak primitive to replace this step.

## Before

`lowpriv` can only `SELECT` on the `test` database. System tables are denied.

![before](screenshots/03-before.png)

## Exploit

A single Python script performs heap grooming, a two-hop arbitrary write
through user-variable metadata, and persists the escalation via `GRANT ALL`.

```
python3 exploit.py
```

![exploit](screenshots/04-chain.png)

## After

`lowpriv` now has `ALL PRIVILEGES WITH GRANT OPTION`, can read system tables,
read/write arbitrary files, and the grant survives server restart.

![after](screenshots/05-after.png)


Full exploit output

![full run](screenshots/01-exploit-run.png)



## How it works

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  SELECT  json_schema_valid(overflow),                           β”‚
β”‚          @ccc...c := hop1,                                      β”‚
β”‚          @aaa...a := hop2                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                    β”‚                   β”‚
         β–Ό                    β–Ό                   β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ 192-byte  β”‚     β”‚ Write to @c  β”‚    β”‚ Write to @a  β”‚
   β”‚ overflow   β”‚     β”‚ through the  β”‚    β”‚ through the  β”‚
   β”‚ corrupts   β”‚     β”‚ corrupted    β”‚    β”‚ redirected   β”‚
   β”‚ entry_c's  β”‚     β”‚ pointer:     β”‚    β”‚ pointer:     β”‚
   β”‚ value ptr  β”‚     β”‚              β”‚    β”‚              β”‚
   β”‚ (2-byte    β”‚     β”‚ entry_a β†’    β”‚    β”‚ master_accessβ”‚
   β”‚  partial   β”‚     β”‚   .value =   β”‚    β”‚   = 0xFFFF.. β”‚
   β”‚  overwrite)β”‚     β”‚   &master_   β”‚    β”‚ (ALL PRIVS)  β”‚
   β”‚            β”‚     β”‚   access     β”‚    β”‚              β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚   .length= 9 β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

1. **Heap groom** β€” 100+ user variables exhaust tcache, forcing
   `Entry_a β†’ Value_a β†’ Entry_b β†’ Value_b β†’ Entry_c β†’ Value_c` to be
   allocated consecutively.

2. **Overflow** β€” `JSON_SCHEMA_VALID` triggers a 192-byte `strncpy` past
   the 128-byte buffer, corrupting `entry_c→value` (2-byte partial pointer
   overwrite within the same 64 KB page) to point at `entry_a + 32`.

3. **Hop 1** β€” Assigning to `@c` writes 126 bytes through the corrupted
   pointer into `entry_a`'s metadata, setting:
   - `entry_a→value  = &Security_context::master_access`
   - `entry_a→length = 9`

4. **Hop 2** β€” Assigning to `@a` writes 8 bytes of `0xFF` through the
   redirected `entry_a→value` pointer → `master_access = ALL PRIVILEGES`.

5. **Persist** β€” The session now holds all privileges. `GRANT ALL` commits
   the escalation to the Aria-backed `mysql.global_priv` table. The session
   eventually crashes during cleanup (residual heap corruption), but the
   GRANT is already checkpointed and survives the restart.

### Key constraints solved

| Constraint | Solution |
|---|---|
| `STRING_RESULT` does `length++` before realloc check | Payload is Nβˆ’1 bytes so Nβˆ’1+1 = N matches stored length β†’ no realloc on corrupted pointer |
| 126-byte hop2 corrupts THD fields past `Security_context` | Set `entry_a→length = 9` in hop1 so hop2 writes only 8 bytes (master_access) + 1 NUL |
| `master_access` offset in `Security_context` | 1712 bytes from struct base (`priv_user[384]` + `proxy_user[645]` + `priv_host[256]` + `priv_role[384]` + padding + pointers) |
| Aria crash recovery rolls back uncommitted writes | `GRANT ALL` + 10 s `SLEEP` allows Aria checkpoint before session cleanup crash |

## Lab setup

```bash
# 1. Build and start the container
./setup.sh

# 2. Disable ASLR on the Docker host
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'

# 3. Run the exploit
python3 exploit.py
```

### Requirements

- Docker
- Python 3
- ASLR disabled on the Docker **host** (`/proc/sys/kernel/randomize_va_space = 0`)
- Container runs with `--cap-add SYS_PTRACE` (for `/proc/1/mem` access)

### What the lab assistance provides

The exploit script shells into the container as root to read `/proc/1/mem`.
This is used for two things:

1. **Heap layout discovery** β€” locating the three sentinel `user_var_entry`
   structs and verifying they are adjacent.
2. **`Security_context` address** β€” finding the `master_access` field to
   target with the two-hop write.

In a real-world scenario, an attacker would need a separate info-leak
vulnerability (or a side-channel) to obtain these addresses. The heap
overflow and write primitive are fully reachable from a low-privilege
SQL session without any special access.

## Files

| File | Description |
|---|---|
| `exploit.py` | Two-hop chain exploit (Python 3) |
| `Dockerfile` | Lab container image |
| `init.sql` | Creates the `lowpriv` user |
| `setup.sh` | Builds and starts the lab |
| `screenshots/` | `freeze`-generated terminal screenshots |