| name | lua-sandbox-security |
| description | Security research and penetration testing for Lua sandbox environments. Use this skill when analyzing Lua VM security in game clients, embedded applications, or scripting engines. Trigger when users mention Lua sandboxes, embedded Lua, game client security, bytecode exploitation, sandbox escape, or need to enumerate Lua environments for security assessments. Also use when hardening Lua environments or reviewing Lua security configurations. |
Lua Sandbox Security Research
A skill for security researchers and developers to analyze, test, and harden Lua sandbox environments in embedded applications, game clients, and scripting engines.
When to Use This Skill
Use this skill when:
- You need to enumerate a Lua sandbox environment for security testing
- You're assessing Lua VM security in game clients or embedded applications
- You want to identify dangerous primitives exposed in a Lua environment
- You're testing for sandbox escape vulnerabilities
- You need to harden a Lua environment against exploitation
- You're reviewing Lua security configurations or bytecode loading policies
Core Concepts
The Lua Sandbox Model
Lua sandboxes typically restrict access to standard libraries (io, os, package, debug) while exposing a limited set of safe functions. However, misconfigurations often leave dangerous primitives reachable, enabling:
- Command execution via
io.popen() or os.execute()
- Arbitrary code execution via
load(), loadstring(), loadfile()
- Native library loading via
package.loadlib()
- Memory corruption via bytecode exploitation (Lua ≤5.1)
Attack Surface
The global environment _G is the primary attack surface. Enumeration reveals what's reachable:
- io/os: Direct command execution
- load/loadstring/loadfile: Source/bytecode execution
- package: Dynamic library loading
- debug: Privilege escalation within VM
- LuaJIT ffi: Native code calls
Enumeration Techniques
Dump the Global Environment
Start by inventorying all reachable globals. Use any available output primitive:
local function dump_globals(out)
out("=== DUMPING _G ===")
for k, v in pairs(_G) do
out(tostring(k) .. " = " .. tostring(v))
end
end
Alternative Output Channels
When print() is blocked, repurpose in-VM channels:
local function create_output_channel()
H.PlaySound(0, "r[1]")
return function(msg)
H.Say(1, msg)
end
end
local out = create_output_channel()
dump_globals(out)
Generalize this pattern: Any textbox, toast, logger, or UI callback accepting strings can act as stdout for reconnaissance.
Auto-Run Callbacks
If the host pushes scripts to clients with auto-run hooks, payloads execute on load:
function OnInit()
io.popen("whoami")
end
Dangerous Primitives Checklist
During enumeration, specifically hunt for:
| Primitive | Risk | Example |
|---|
io.popen | Command execution | io.popen("id") |
os.execute | Command execution | os.execute("/bin/id") |
load/loadstring | Code execution | load("os.execute('id')")() |
loadfile/dofile | File-based code | dofile("/tmp/payload.lua") |
package.loadlib | Native loading | package.loadlib("./lib.so", "luaopen_foo") |
debug | Privilege escalation | debug.getinfo, debug.sethook |
ffi (LuaJIT) | Native calls | ffi.C.system("id") |
Testing Primitive Availability
if io and io.popen then
local handle = io.popen("echo test")
local result = handle:read("*a")
handle:close()
print("io.popen works: " .. result)
end
if loadstring then
local f = loadstring("return 42")
print("loadstring works: " .. f())
end
if package and package.loadlib then
print("package.loadlib is exposed")
end
Bytecode Exploitation (Advanced)
When load/loadstring are exposed but io/os are restricted, crafted bytecode can enable memory corruption:
Key Facts
- Lua ≤5.1: Bytecode verifier has known bypasses
- Lua 5.2+: Verifier removed (applications should reject precompiled chunks)
- Attack flow: Leak pointers → craft type confusion → arbitrary read/write → code execution
Basic Bytecode Loading
local function create_bytecode()
local bc = string.dump(function() return 0x1337 end)
local f = loadstring(bc)
return f()
end
print(create_bytecode())
Note: Bytecode exploitation is engine/version-specific and requires reverse engineering. See references for detailed primitives.
Hardening Recommendations
Server-Side Controls
- Reject or rewrite user scripts - Don't execute untrusted code directly
- Allowlist safe APIs - Only expose necessary functions
- Strip dangerous globals:
_ENV = {}
_ENV.print = print
Client-Side Controls
- Minimal _ENV - Start with empty environment, add only what's needed
- Forbid bytecode loading - Reject
loadstring with precompiled chunks
- Reintroduce bytecode verifier - Or implement signature checks
- Block process creation - Prevent child process spawning from client
Telemetry & Detection
- Alert on gameclient → child process creation after script load
- Correlate UI/chat/script events with process spawning
- Monitor for unusual
io.popen or os.execute calls
Security Assessment Workflow
- Enumerate - Dump
_G and identify reachable primitives
- Test - Verify dangerous functions actually work
- Exploit - If primitives are exposed, demonstrate impact
- Report - Document findings with reproduction steps
- Remediate - Apply hardening recommendations
References
Important Notes
- Authorization: Only test Lua sandboxes you own or have explicit permission to assess
- Scope: Document what you're testing and get written authorization
- Safety: Some exploitation techniques can crash applications or corrupt data
- Defense: This skill is equally valuable for hardening Lua environments against attacks