tile38 icon indicating copy to clipboard operation
tile38 copied to clipboard

Lua Sanitization

Open program-- opened this issue 3 years ago • 2 comments

Describe the bug Tile38's EVAL command does not sanitize Lua code. This allows an attacker to use arbitrary code to potentially access information on the machine and opens a door for lateral movement.

To Reproduce Send arbitrary code using EVAL to a Tile38 server, such as:

https://tile38.fakedomain.com/eval+return{io.popen("printenv"):read('*a')}+0

Logs The above command returns the following JSON:

{
  "ok": true,
  "result": [
    "HOSTNAME=tile38host\nSHLVL=1\nHOME=/root\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nPWD=/\n"
  ],
  "elapsed": "1.03398ms"
}

Additional context I'm not sure if this is out of scope for Tile38, and should be resolved at the administrator's level, but if so, feel free to close this then.

program-- avatar Dec 14 '21 20:12 program--

But I can see how this could pose a problem for folks that expose their tile38 server directly to the internet.

I'm not sure how much of a risk this really is, because tile38 is intended as a middleware component.

That being said, perhaps we just omit those riskier function altogether.

Tile38 uses yuin/gopher-lua, which runs Lua 5.1.

The Lua 5.1 reference has a complete list of operations. There are various functions that touch the underlying os (filesystem, env vars, etc.). We could, maybe, just omit those functions, or find a way to sandbox them.

🤔

tidwall avatar Dec 15 '21 15:12 tidwall

I'm not super familiar with Golang or Lua, but in gopher-lua's README, there seems to be an example of loading only a subset of modules into a new Lua state. I imagine this would only need to be applied here (from my limited knowledge of tile38's codebase).

Here's the example code from the README:

func main() {
    L := lua.NewState(lua.Options{SkipOpenLibs: true})
    defer L.Close()
    for _, pair := range []struct {
        n string
        f lua.LGFunction
    }{
        {lua.LoadLibName, lua.OpenPackage}, // Must be first
        {lua.BaseLibName, lua.OpenBase},
        {lua.TabLibName, lua.OpenTable},
    } {
        if err := L.CallByParam(lua.P{
            Fn:      L.NewFunction(pair.f),
            NRet:    0,
            Protect: true,
        }, lua.LString(pair.n)); err != nil {
            panic(err)
        }
    }
    if err := L.DoFile("main.lua"); err != nil {
        panic(err)
    }
}

But, I also agree that the risk here is not really known -- and, there are ways to handle this outside of tile38. :smile:

program-- avatar Dec 15 '21 16:12 program--