Pengantar
Redis adalah penyimpanan data dalam memori sumber terbuka yang banyak digunakan sebagai cache, perantara pesan, dan basis data NoSQL berkinerja tinggi. Redis menawarkan struktur data yang kaya seperti string, hash, daftar, set, set terurut, bitmap, HyperLogLog, dan aliran, yang didukung oleh operasi atomik dan latensi yang sangat rendah. Persistensi tersedia melalui snapshot RDB dan AOF, dan ketersediaan tinggi disediakan melalui replikasi, Sentinel, dan Cluster. Redis juga mendukung skrip sisi server dengan Lua untuk mengeksekusi operasi kompleks secara atomik. Kecepatan, fleksibilitas, dan ekosistemnya yang matang telah menjadikannya sebagai fondasi inti bagi sistem modern yang sensitif terhadap latensi.
Pada Oktober 2025, Redis merilis versi 8.2.2 , sebuah rilis keamanan yang memperbaiki empat kerentanan pada mesin Lua yang tertanam. Yang paling kritis adalah kerentanan use-after-free pada Lua yang memungkinkan penyerang untuk keluar dari sandbox skrip dan mengeksekusi kode pada host ketika pengguna yang tidak tepercaya dapat menjalankan Lua secara sembarangan. Rilis ini juga mengatasi integer overflow pada unpack, kelemahan eksekusi skrip lintas pengguna, dan pembacaan di luar batas pada lexer Lua. Dalam postingan ini, kami akan menguraikan setiap CVE, membahas patch-patch yang ada , dan menguraikan langkah-langkah praktis untuk memperkuat penerapan Redis Anda.
Ikhtisar Kerentanan
Sebelum menyelami kode, berikut peta cepat tentang apa yang diperbaiki 8.2.2 pada mesin Lua.
CVE-2025-46817: Luapan integer saat pembongkaran (CVSS 9.8, Kritis)
Argumen ekstrem untuk unpack(tbl, i, j) dapat melebihi jumlah internal nilai yang akan dikembalikan. Jumlah yang salah tersebut dapat melewati pemeriksaan tumpukan dan menyebabkan kerusakan atau kerusakan memori, menjadikannya primitif yang berguna untuk eksploitasi.
CVE-2025-46818: Eksekusi skrip lintas pengguna melalui status runtime bersama (CVSS 7.3, Tinggi)
Kontrol yang longgar di sekitar metatabel dan API terkait lingkungan memungkinkan satu skrip memengaruhi cara skrip lainnya berjalan. Dalam beberapa pengaturan, hal ini berarti pengguna dengan hak istimewa yang lebih rendah dapat memengaruhi eksekusi dalam konteks pengguna lain, sehingga melemahkan isolasi.
CVE-2025-46819: Lua membaca di luar batas saat penguraian string panjang (CVSS 7.1, Tinggi)
Penanganan pembatas string panjang/komentar panjang yang rapuh ([=[ … ]=], dll.) pada lexer Lua dapat mendorongnya melewati batas buffer-nya. Hal ini dapat menyebabkan Redis crash dan, dalam kasus-kasus ekstrem, mengekspos memori di dekatnya.
CVE-2025-49844: Penggunaan Lua setelah bebas di parser (CVSS 10.0, Kritis)
Skrip Lua yang dibuat dapat memanipulasi pengumpulan dan penguraian sampah dengan cara yang mencapai batas penggunaan-setelah-bebas pada mesin Lua yang tertanam.
Selanjutnya, kita akan melihat masing-masing secara lebih rinci, dengan potongan patch yang relevan
Di dalam perbaikan: Penyelaman mendalam kerentanan
CVE-2025-46817: Luapan bilangan bulat saat membongkar
Masalah ini memengaruhi unpack(tbl, i, j) Lua 5.1 yang tertanam di Redis. Dengan nilai i/j yang ekstrem, perhitungan "berapa banyak hasil yang akan dikembalikan" dapat meluap, menghasilkan hitungan yang salah, dan lolos dari pemeriksaan tumpukan. Hal ini dapat menyebabkan crash atau kerusakan memori dan dapat digunakan sebagai fondasi menuju RCE dalam skenario yang tidak bersahabat.
Patch untuk CVE ini dapat diakses di sini: fc9abc7
Analisis patch
Perbaikan ini membuat penanganan jangkauan unpack menjadi eksplisit dan aman.
- Perhitungan hitungan dialihkan ke matematika tak bertanda yang aman
Sebelum patch :
statis int luaB_unpack (lua_State *L) {
int saya, e, n;
luaL_checktype(L, 1, LUA_TTABLE);
i = luaL_optint(L, 2, 1);
e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1));
jika (i > e) kembalikan 0; /* rentang kosong */
n = e – i + 1; /* mungkin meluap (bertanda) */
jika (n <= 0 || !lua_checkstack(L, n))
kembalikan luaL_error(L, “terlalu banyak hasil untuk dibongkar”);
/* dorong n hasil… */
}
Di sini, e – i + 1 dilakukan dalam bilangan bulat bertanda. Dengan nilai ekstrem (misalnya 0 dan 2147483647), ini dapat meluap, dan n <= 0 bukanlah penjaga yang andal.
Setelah pembaruan :
statis int luaB_unpack (lua_State *L) {
int saya, e;
unsigned int n;
luaL_checktype(L, 1, LUA_TTABLE);
i = luaL_optint(L, 2, 1);
e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1));
jika (i > e) kembalikan 0; /* rentang kosong */
n = (unsigned int)e – (unsigned int)i; /* elemen dikurangi 1 */
jika (n >= INT_MAX || !lua_checkstack(L, ++n))
kembalikan luaL_error(L, “terlalu banyak hasil untuk dibongkar”);
lua_rawgeti(L, 1, i); /* dorong arg[i] */
sementara (i++ < e) {
lua_rawgeti(L, 1, i);
}
kembali n;
}
Poin-poin penting:
- Menggunakan unsigned int untuk rentang tersebut guna menghindari nilai negatif/berlebihan.
- Memperlakukan n sebagai “jumlah – 1” dan bertambah satu kali sebelum pemeriksaan tumpukan.
- Menegakkan batas atas yang keras (n >= INT_MAX → kesalahan).
- Memastikan tumpukan memiliki ruang untuk semua hasil sebelum mendorong apa pun.
- Pengindeksan array dibuat eksplisit
Pengetatan terkait dalam kode tabel:
Setelah patch
jika (1 <= kunci dan kunci <= t->sizearray)
kembalikan &t->array[kunci-1];
Alih-alih mengandalkan trik pemeran yang tidak ditandatangani, ia menggunakan pemeriksaan batas yang jelas, yang menghindari perilaku ganjil untuk indeks yang besar atau negatif.
Secara keseluruhan, perubahan ini berarti rentang pembongkaran yang ekstrem tidak lagi menyebabkan jumlah yang terbungkus atau push yang tidak aman. Panggilan berukuran besar kini gagal dengan kesalahan "terlalu banyak hasil untuk dibongkar" yang jelas, alih-alih berisiko mengalami crash atau kerusakan memori.
CVE-2025-46819: Lua membaca di luar batas saat mengurai string panjang
Masalah ini terletak pada lexer Lua, khususnya pada cara mengurai string panjang dan komentar panjang seperti [=[ … ]=]. Pada versi yang rentan, pola pembatas yang salah bentuk atau ekstrem dapat mendorong lexer melewati batas buffer inputnya. Hal ini dapat menyebabkan Redis crash (DoS), dan dalam beberapa kasus ekstrem dapat mengekspos byte dari memori terdekat.
Patch untuk CVE ini dapat dilihat di sini: 3a1624d
Analisis patch
Perbaikan ini membuat penanganan tali panjang lebih ketat dan tidak mudah pecah.
Penguraian pembatas dibuat tidak ambigu
Pembantu yang mengurai urutan seperti [===[ dan ]===] diperbarui untuk menggunakan tipe yang lebih aman dan mengembalikan nilai yang jelas dan konsisten:
Setelah patch
ukuran statis_t lewati_sep(LexState *ls) {
ukuran_t hitung = 0;
int s = ls->current; /* '[' atau ']' */
lua_assert(s == '[' || s == ']');
simpan_dan_berikutnya(ls); /* konsumsi itu */
sementara (ls->current == '=') { /* hitung '=' */
simpan_dan_berikutnya(ls);
hitung++;
}
jika (ls->current == s) /* cocok dengan '[' atau ']' kedua */
kembalikan hitungan + 2; /* pembatas panjang yang valid */
jika tidak (jumlah == 0)
kembalikan 1; /* bukan string/komentar yang panjang */
lain
kembalikan 0; /* salah format */
}
- Pembatas string panjang yang valid sekarang mengembalikan panjang yang terdefinisi dengan baik (>= 2).
- Polos [jatuh kembali dengan bersih (1).
- Pola [==… yang salah bentuk akan mengembalikan 0 dan diperlakukan sebagai kesalahan.
Ini menutup celah di mana pola ganjil atau tidak lengkap dapat membingungkan lexer hingga membaca di luar buffer.
Offset untuk mengiris tubuh dikoreksi
Saat mengekstrak konten string sebenarnya, patch menyelaraskan matematika irisan dengan hasil skip_sep:
Setelah patch:
jika (seminfo) {
semiinfo->ts = luaX_newstring(
aku,
luaZ_buffer(ls->buff) + sep,
luaZ_bufflen(ls->buff) – 2 * sep
);
}
Sebelumnya, offset tidak sesuai dengan semantik pembatas, yang dapat menyebabkan perilaku menyimpang secara halus. Sekarang, nilai awal dan panjang diturunkan langsung dari nilai sep, sehingga lexer tetap berada dalam batas.
Tipe sesuai dengan apa yang sebenarnya dilakukan oleh kode tersebut
Penghitung dan indeks yang terlibat dalam logika ini sekarang menggunakan tipe unsigned/size yang sesuai, bukan signed int yang kelebihan beban, sehingga mengurangi risiko nilai negatif atau wraparound yang merayap ke dalam aritmatika pointer.
Semua ini berarti bahwa bahkan dengan sekuens [=…=[ … ]=…=] yang besar atau sengaja dirusak, Lua kini dapat mengurainya dengan benar atau menolaknya dengan kesalahan normal. Tidak ada lagi jalur di mana skrip yang tidak diinginkan dapat mendorong lexer untuk melihat melewati batas buffer.
CVE-2025-46818: Skrip Lua dapat dijalankan dalam konteks pengguna lain
Isu ini berkaitan dengan menjaga agar skrip Lua tetap berada dalam lingkup pengguna dan lingkungan tempat skrip tersebut berada . Pada versi yang rentan, elemen runtime bersama—seperti metatable pada tipe inti dan API lingkungan lama—cukup longgar sehingga satu skrip dapat memengaruhi cara skrip lain dijalankan. Dalam beberapa penerapan, hal itu dapat mengaburkan isolasi antar pengguna.
Patch untuk CVE ini dapat dilihat di sini: 45eac02
Analisis patch
Perbaikan ini melakukan dua hal utama: melindungi metatable inti dan membatasi akses ke API lama yang berisiko.
- Metatabel tipe inti dilindungi
Sebelumnya, tipe inti Lua yang diekspos ke skrip (string, angka, boolean, dll.) tidak memiliki penghalang ketat yang mencegah perubahan metatabel dari skrip pengguna. Artinya, modifikasi pada metatabel bersama dapat memengaruhi kode lain yang berjalan di mesin yang sama.
Kode yang ditambal secara eksplisit memperkuat metatabel tersebut selama penyiapan lingkungan Lua. Secara sederhana:
Kode yang ditambal: tandai metatabel primitif sebagai terlindungi
void statis luaProtectPrimitiveMetatables(lua_State *L) {
const int tipe[] = {LUA_TSTRING, LUA_TNUMBER, LUA_TBOOLEAN, LUA_TNIL};
untuk (ukuran_t i = 0; i < ukuran(tipe)/ukuran(tipe[0]); i++) {
luaL_getmetatable(L, nama_tipe_lua(L, jenis[i]));
jika (!lua_isnil(L, -1)) {
lua_pushliteral(L, “dilindungi”);
lua_setfield(L, -2, “__metatable”); /* kunci metatabel */
}
lua_pop(L, 1);
}
}
Perlindungan __metatable ini berarti skrip pengguna masih dapat melihat tipe-tipe ini tetapi tidak dapat mengganti atau menulis ulang metatable-nya secara global. Setiap upaya untuk melakukannya akan gagal dengan jelas, alih-alih mengubah perilaku secara diam-diam untuk semua orang.
- API lingkungan lama secara eksplisit bersifat opt-in
API Lua lama yang dapat memanipulasi lingkungan kini dinonaktifkan secara default dan hanya diekspos ketika operator secara eksplisit mengaktifkannya.
Secara konseptual, perubahannya tampak seperti ini:
Sebelum patch: API yang tidak digunakan lagi selalu tersedia di lingkungan skrip
konstanta statis luaL_Reg redis_compat_funcs[] = {
{"getfenv", luaB_getfenv},
{"setfenv", luaB_setfenv},
{“proxy baru”, luaB_proxy baru},
{NULL, NULL}
};
skripBuatEnv(…) {
luaL_register(L, “_G”, redis_compat_funcs);
}
Setelah patch: hanya mendaftar ketika lua-enable-deprecated-api diatur
skripBuatEnv(…) {
jika (server.lua_enable_deprecated_api) {
luaL_register(L, “_G”, redis_compat_funcs);
}
}
Jika lua-enable-deprecated-api tidak diaktifkan, fungsi-fungsi ini tidak akan ada dalam lingkungan skrip, sehingga menghilangkan serangkaian tuas yang ampuh untuk manipulasi lintas konteks.
Secara keseluruhan, perubahan-perubahan ini memastikan:
- Skrip tidak dapat secara diam-diam menambal perilaku mendasar untuk semua skrip lainnya dengan menulis ulang metatabel inti.
- Primitif manipulasi lingkungan adalah hanya tersedia jika operator sengaja menyalakannya.
Hal itu membuat runtime Lua lebih mudah diramalkan dan menjaga setiap skrip lebih dekat dengan hak istimewa dan konteks yang dimaksudkan—penting untuk setiap penerapan Redis bersama atau multi-penyewa.
CVE-2025-49844: Penggunaan Lua setelah bebas di parser
Bug ini terdapat pada cara Redis mengintegrasikan parser Lua. Dalam kondisi tertentu, skrip Lua yang dibuat dapat memicu pengumpulan sampah di waktu yang salah dan membuat Redis membaca pointer ke memori yang telah dibebaskan.
Patch untuk CVE ini dapat dilihat di sini: d5728cb5795
Analisis patch
Perubahan utamanya adalah Redis kini menyimpan referensi yang tepat ke objek Lua yang digunakan parser (seperti nama chunk) di tumpukan Lua saat melakukan parsing, sehingga garbage collector melihatnya sebagai aktif dan tidak dapat membebaskan atau memindahkannya lebih awal.

Sebelumnya, potongan “nama” dibuat secara inline dan diteruskan langsung ke lexer:
luaX_setinput(L, &lexstate, z, luaS_new(L, nama));
Karena nilai tersebut tidak terikat dengan jelas (misalnya, pada tumpukan Lua), campur tangan GC atau defrag yang tidak disengaja ditambah dengan skrip yang agresif, secara teori, dapat menyebabkan parser menyimpan penunjuk ke sesuatu yang telah dipindahkan atau dibebaskan.
Kode yang ditambal membuat kepemilikan itu eksplisit:
TString *tname = luaS_new(L, nama);
setsvalue2s(L, L->top, tname); /* pertahankan nama tersebut tetap aktif di tumpukan */
incr_top(L);
luaX_setinput(L, &lexstate, z, tname); /* menggunakannya dengan aman untuk lexing/parsing */
/* … mengurai potongan tersebut … */
–L->top; /* hilangkan referensi saat selesai */
Dengan mempertahankan status ini sebagai referensi untuk seluruh proses penguraian, GC dan defrag kini menganggapnya aktif dan tidak dapat mengambil kembali atau memindahkannya di tengah proses. Hal ini secara efektif menghilangkan jendela waktu sempit yang memungkinkan penggunaan setelah bebas.
Skenario penyebaran yang berisiko
Masalah-masalah ini khususnya relevan dalam lingkungan di mana:
- Aplikasi menggunakan skrip Lua di Redis.
- Klaster Redis yang sama dibagikan di seluruh tim atau penyewa.
- Redis dapat diakses, secara langsung atau melalui aplikasi Anda – dari masukan yang tidak tepercaya atau dikendalikan pengguna.
Tindakan yang disarankan
1. Patch terlebih dahulu
- Mutakhirkan ke versi Redis 8.2.2 yang telah ditambal.
- Perlakukan ini sebagai prioritas tinggi di mana pun kode yang tidak tepercaya atau semi-tepercaya dapat menjalankan Lua (misalnya melalui EVAL / SCRIPT LOAD) atau di mana kluster Redis dibagikan di seluruh tim/penyewa.
2. Kunci skrip Lua
- Gunakan ACL untuk membatasi skrip Lua ke layanan tepercaya saja.
- Simpan Redis di jaringan pribadi, jangan terpapar internet langsung.
- Biarkan lua-enable-deprecated-api dinonaktifkan kecuali Anda memiliki kebutuhan yang jelas dan telah diaudit.
3. Beban kerja yang sensitif terhadap segmen
- Jalankan Lua yang tidak tepercaya atau digerakkan oleh pelanggan pada instans Redis yang terpisah.
- Jangan mencampur skrip yang dikontrol penyewa dengan kluster yang menyimpan data penting atau sensitif.
4. Gunakan kontrol keamanan untuk menambah visibilitas (bukan sebagai pengganti patching)
- Konfigurasikan IPS/IDS/WAF untuk mendeteksi dan memblokir akses Redis langsung dan penggunaan protokol Redis yang mencurigakan.
- Gunakan perlindungan EDR/AV/runtime pada host Redis untuk menemukan perilaku seperti eksploitasi
Referensi:
Penulis:
Vinay Kumar
Adrip Mukherjee
Nandini Seth
Suvarnjeet Jagtap



