Hacktricks-skills lua-sandbox-security

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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/generic-methodologies-and-resources/lua/bypass-lua-sandboxes/bypass-lua-sandboxes/SKILL.MD
source content

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:

-- Basic _G dumper
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:

-- Example: Using game UI as output
local function create_output_channel()
  -- Some engines require initialization
  H.PlaySound(0, "r[1]")
  return function(msg)
    H.Say(1, msg)
  end
end

-- Usage
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()
  -- Payload executes when script loads
  io.popen("whoami")
end

-- Other common callbacks: OnLoad, OnEnter, OnStart

Dangerous Primitives Checklist

During enumeration, specifically hunt for:

PrimitiveRiskExample
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

-- Check if io.popen is reachable
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

-- Check loadstring
if loadstring then
  local f = loadstring("return 42")
  print("loadstring works: " .. f())
end

-- Check package.loadlib
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

-- Create and load bytecode
local function create_bytecode()
  local bc = string.dump(function() return 0x1337 end)
  local f = loadstring(bc)
  return f()
end

print(create_bytecode()) -- 4919

Note: Bytecode exploitation is engine/version-specific and requires reverse engineering. See references for detailed primitives.

Hardening Recommendations

Server-Side Controls

  1. Reject or rewrite user scripts - Don't execute untrusted code directly
  2. Allowlist safe APIs - Only expose necessary functions
  3. Strip dangerous globals:
    _ENV = {}
    _ENV.print = print  -- Only safe functions
    -- Remove: io, os, load, loadstring, loadfile, dofile
    -- Remove: package.loadlib, debug, ffi
    

Client-Side Controls

  1. Minimal _ENV - Start with empty environment, add only what's needed
  2. Forbid bytecode loading - Reject
    loadstring
    with precompiled chunks
  3. Reintroduce bytecode verifier - Or implement signature checks
  4. 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

  1. Enumerate - Dump
    _G
    and identify reachable primitives
  2. Test - Verify dangerous functions actually work
  3. Exploit - If primitives are exposed, demonstrate impact
  4. Report - Document findings with reproduction steps
  5. 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