
CVE-2026-25243 — Redis RESTORE zipmap 二重解放 → リモートコード実行(ASLR 有効).
RESTORE zipmap double-free → リモートコード実行TL;DR.
RESTOREに渡された不正なDUMPペイロードは、Redis の レガシーハッシュ zipmap ローダーでヒープ double-free を引き起こします。 デフォルトの jemalloc では double-free は 無音(サーバーは稼働し続ける)であり、 制御可能な 型混乱(type-confusion) プリミティブになります。このリポジトリはそれを ASLR 有効でのリモートコード実行 に連鎖させます — Redis ワーカーがsystem("<攻撃者文字列>")を呼び、 サービスを提供し続けます。DoS ではありません。
# デフォルト Redis(DEBUG 無効)、ASLR 有効 — 最も自己完結したエクスプロイト(libc オフセット不要):
$ 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
ASLR を破るリークは DEBUG を一切使いません — redis.call の Lua C-closure アドレスを読み取ります
(EVAL 'return tostring(redis.call)')。これは以前の Redis エクスプロイトと同じ、自己完結型で DEBUG 不要の
テクニックです。blob_base/blob_robj および PIE ベース は、その後オーバーリードから実行時に導出されます
(固定オフセットなし)。PIE-ROP の仕上げは JOP スタックピボットを通じて execve@plt を呼ぶため、
libc アドレスを一切使いません — ビルド固有の定数は redis-server バイナリから読み取る PIE 相対
ガジェットオフセットだけであり、以前の HLL エクスプロイトのビルド別ガジェットテーブルとまったく同じです。
デフォルトの DEBUG 無効サーバー上で uid=0(root)、ASLR 有効、8/8 を確認済みです。
2 つのフィニッシュが用意されています。
poc_rce_aslr_pie_rop.py(上記)は最も自己完結型 — libc 不要、完全自己校正 — ただしexecveはワーカーを置き換えます(本番シェルにはリバースシェル--cmdを使用)。poc_rce_aslr_selfcal.pyはワーカーを生かしたままにします(system()が fork する)が、 その代わり libc バージョン別オフセットが 2 つ必要です。サーバーを存続させる必要があるかどうかで選択してください。
RESTORE key 0 <DUMP-payload> はシリアライズされたオブジェクトを逆シリアライズします。レガシーな
RDB_TYPE_HASH_ZIPMAP(0x09)タイプでは、バリデーターとコンバーターが長さフィールドのバイト数の解釈を
めぐって食い違います:
zipmapValidateIntegrity() は 実際の エンコード済みサイズ(過長な 0xFE プレフィックスでは 5)で歩みます。zipmapNext() は zipmap → listpack 変換中、デコードされた長さ < 254 に対して 1 バイトを使用します。過長な 5 バイト形式 で書かれた短い長さは検証を通過しますが、zipmapNext() を 4 バイト誤って
ストライドさせます。同じ誤ストライドから 2 つの結果が生じます: ヒープ オーバーリード(zipmap.c)と、
Redis のみで発生する、rdb.c のハッシュ zipmap ローダーでのヒープ double-free です:
sds field = sdstrynewlen(fstr, flen);
if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK || !lpSafeToAdd(lp, flen + vlen)) {
dictRelease(dupSearchDict); // (1) dictAdd が `field` の所有権を取得 -> ここで解放
sdsfree(field); // (2) 再度解放 -> double-free
Valkey はこれをガードしています(if (!field_added) sdsfree(field);)。上流の Redis はガードしていないため、
double-free は Redis のみで発生します。修正は過長エンコードされた短い長さを拒否し、ロード時のチェック順序を
並べ替えます。
silent double-free -> type-confusion overlap -> arbitrary pointer-forge
-> hashtable ハッシュの dict->type を偽の dictType に偽造
-> HGET hd "<field>" == dictFind -> type->hashFunction(field)
libc パス (selfcal): hashFunction = &system -> system("<cmd>") (ワーカー存続)
PIE パス (pie_rop): hashFunction = JOP-pivot g1、field = ROP チェーン
-> leave;ret が rsp を field 上にピボット
-> execve("/bin/sh","-c","<cmd>") via execve@plt (libc 不要、DEBUG 不要)
偽の dictType は 16 MB の文字列(SETRANGE)内の、アドレスの下位バイトが攻撃者文字列の sds ヘッダーと
一致するオフセットに植え付けられます。ASLR は完全に実行時に破られます:
system — 単一のヒープポインターリーク。推奨パスは DEBUG 不要 です: redis.call の Lua
C-closure のアドレス(EVAL 'return tostring(redis.call)')— 以前の Redis エクスプロイトと同じ
自己完結型リークです。jemalloc アリーナは libc から一定のオフセットにあるため、
system = leaked_robj + Δlibc + system_off となります。(DEBUG OBJECT は、スクリプト実行が無効なのに
DEBUG が有効という稀な構成でラボ用に使えるだけです。)SET の
dict を偽造してそのメンバーを 107 KB の SDS_TYPE_32 sds にし、SMEMBERS で隣接ヒープを
オーバーリードして、既知のオフセットで blob の robj.ptr を読み取ります。完全なプリミティブ単位の分析と、苦労して得た jemalloc / Redis-8.x の詳細(class-64 境界、dict エントリの
タグ付け、ハッシュフィールド mstr、キースペースの事前 grow)については WRITEUP.md を参照してください。
docker build -t cve-2026-25243 .
# 標準(jemalloc)デモ — DoS か否か? 型混乱を示します(ツール不要):
docker run --rm -p 6379:6379 cve-2026-25243
# フルチェーン — DEFAULT 構成(DEBUG 無効)、推奨エクスプロイト:
sysctl -w kernel.randomize_va_space=2 # ASLR ON
redis-server & # DEBUG はデフォルトでオフ
python3 exploits/poc_rce_aslr_selfcal.py --host 127.0.0.1 --port 6379 --cmd "id > /tmp/pwned 2>&1"
DEBUG 不要)ブートストラップのヒープポインターリークは以前の Redis エクスプロイトと同じアプローチです:
EVAL 'return tostring(redis.call)' で redis.call の Lua C-closure アドレスをリークします。
Lua スクリプトはデフォルトでオン。DEBUG はデフォルトで オフ(enable-debug-command no)—
したがって Lua リークが現実的な主要経路であり、DEBUG OBJECT(poc_rce_aslr.py)はラボでの便宜手段にすぎません。
poc_rce_aslr_selfcal.py はその後、オーバーリードから blob_base と blob_robj を実行時に導出 するため
(16 MB blob の robj シグネチャをスキャン)、DLUA/DFOBJ は およそ 合っていれば十分です。
system フィニッシュでは、スモールヒープのオーバーリードに libc ポインターが含まれない ため、libc を
自己導出できません — poc_rce_aslr_selfcal.py は libc バージョン別オフセット 2 つ(DLIBC、SYSTEM_OFF)を
保持します。poc_rce_aslr_pie_rop.py フィニッシュはその依存関係を完全に除去します: 同じオーバーリードには
PIE ポインターが確かに含まれ(共有の dictType が繰り返し出現する)、PIE ベースは
最頻-PIE 値 − DICTTYPE_OFF として自己校正され、チェーンは JOP スタックピボット
(mov rbp,rdi; call *0x8(rax) → leave;ret が rsp を攻撃者制御の HGET フィールド上にピボットし、それが
execve("/bin/sh","-c",<cmd>) ROP チェーンそのもの)を通じて execve@plt で終わります。ビルド固有の定数は
redis-server から読み取る PIE 相対ガジェットオフセットだけです — ターゲットごとに / で
抽出してください。これは以前の HLL エクスプロイトが ELF Build-ID ごとにガジェットテーブルをキーとするのと
まったく同じです。libc アドレスは一切使用しません。
screenshots/05-pie-rop-libc-free.png(NO libc 自己校正 + uid=0、最も自己完結した実行)、
01-rce-aslr-on.png(uid=0 の決定的ショット)、02-reliability.png(5/5)、03-exploit-chain.png(コード)、
04-debug-free-selfcal.png(デフォルト Redis 上での DEBUG 不要・自己校正 system 実行)。
RESTORE は通常のコマンドです — 認証なし/露出した Redis
(requirepass なし)では、接続済みの任意のクライアントが実行できます。認証ありのインスタンスでは、
ACL の -restore 拒否がない任意のユーザーが実行できます。他の Redis RCE で使われるデータ構造コマンドと
同じアクセスプロファイルです。< {6.2.22, 7.2.14, 7.4.9, 8.2.6, 8.4.3, 8.6.3} — すなわち 6.2.x から現在の 8.x まで。
Valkey は DoS/オーバーリードのみ(field_added ガードが double-free を阻止)。修正済みリリースにアップグレードしてください。できない場合: RESTORE を制限し(ACL … -restore)、
Redis を認証なしで露出させず、DEBUG を無効にしてください。
認可されたセキュリティ研究であり、防御側の認知向上のために公開されています。所有していない、または明示的な テスト許可がないシステムに対して実行しないでください。
| ファイル | デモ内容 |
|---|
★ exploits/poc_rce_aslr_pie_rop.py | 最も自己完結型 — デフォルト Redis(DEBUG オフ)で RCE、libc オフセット不要。blob_base/blob_robj/pie_base を自己校正。JOP ピボット経由で execve@plt(ワーカーは置き換え)。8/8。 |
★ exploits/poc_rce_aslr_selfcal.py | ワーカー存続型 — 同じチェーンだが hashFunction=&system(fork する)。libc バージョン別オフセット 2 つ(DLIBC/SYSTEM_OFF)が必要。Lua クロージャリーク、blob_base/blob_robj 自己校正。 |
exploits/poc_rce_aslr_nodebug.py | DEBUG 不要(Lua リーク)だが、固定オフセット使用(DEBUG 有効で校正済み) |
exploits/poc_rce_aslr.py | ラボ便利用バリアント: DEBUG OBJECT ブートストラップリーク(DEBUG 有効が必要) |
exploits/poc_rce_aslr_off.py | ASLR 無効で RCE(校正済みアドレス) |
exploits/poc_typeconfusion.py | double-free → 2 つのキーが 1 つのヒープバッファを共有(ツール不要) |
exploits/poc_doublefree.py | double-free(ASan: sdsfree で heap-use-after-free) |
exploits/poc_dos_overread.py | オーバーリードクラッシュ(ASan) |
ROPgadgetobjdump