3.4 Lua环境下的后门技术与防御

近年来OpenResty/Lua技术的应用也比较广泛,本节将介绍OpenResty/Lua的基础知识,以及双击者常用的一些方法,如Lua环境下实现Webshell、挂马、嗅探器与代码隐藏的相关技术与防御方法。

3.4.1 OpenResty与Tengine介绍

Nginx是一个用C语言开发的高性能Web服务器及反向代理服务器,可直接使用C/C++进行二次开发,但对于很多新用户来说是有一定门槛的,且C/C++的开发效率也低于Python、JS、Lua等语言,Python、JS、Lua三者中,Lua是解析器最小,性能最高的语言,LuaJIT比Lua又快数十倍。目前将Nginx和Lua结合在一起的有OpenResty和Tengine。

最先将Nginx与Lua组合到一起的是OpenResty,OpenResty提供了一个lua-nginx-module模块,可以将Lua嵌入Nginx中,OpenResty是Nginx的Bundle,与官方的最新版本几乎是同步的。

Tengine是基于Nginx的一个分支发开的,也包含了lua-nginx-module模块,阿里巴巴集团(以下简称阿里)根据自己的业务情况对Nginx进行了一些定制开发。

OpenResty中的Lua命令会在OpenResty的不同阶段执行,OpenResty的阶段分为Initialization阶段、Rewrite/Access阶段、Content阶段与Log阶段,Lua命令的详细执行阶段如图3-5所示。

●图3-5 OpenResty的Lua命令执行阶段

Lua命令执行阶段的含义如表3-1所示。

表3-1 Lua命令执行阶段的含义

(续)

3.4.2 Lua版Webshell的原理与防御

OpenResty默认不会加载Lua脚本,需要在OpenResty的配置文件nginx.conf中显式指定Lua文件的路径,然后再用init_by_lua_file加载Lua文件。如以下配置示例表示启用Lua执行的功能及指定入口文件:

conf/lua_src/Init.lua中的内容如下:

cmd=require("t")表示加载了t.lua中的模块,并命名为cmd,以后在Nginx的所有执行阶段通过cmd变量即可调用。

t.lua实现了一个简单的命令执行功能,如下所示:

最后在Nginx的配置文件中添加location指定在OpenResty的content阶段执行刚才定义的后门模块,以下的例子为把content_by_lua放到server段的/test/下,配置如下所示:

接下来就可以请求/test/来执行Webshell了,执行的效果如图3-6所示。

●图3-6 Lua版的Webshell测试

如果将content_by_lua改为access_by_lua(Content阶段不允许放在HTTP节,关于OpenResty的执行阶段会在第6章进行详细介绍)放到HTTP节表示为一个全局的后门,任意一个URL,只要传入特定的参数,Nginx就会响应,即便是404的页面也可以响应,如图3-7所示。

●图3-7 Lua全局Webshell测试

Lua环境下Webshell的检测方法是检查Nginx、OpenResty与Tengine的配置文件,确认是否有可疑的Lua被加载、正常的Lua业务代码是否被篡改,以及是否被插入了有Webshell功能的代码。

3.4.3 Lua环境下的挂马技术与防御

在网页中插入恶意代码的技术业界俗称挂马,用Lua挂马的原理是在OpenResty的body filter_by_lua∗阶段中插入挂马代码,替换原始的HTTP响应。

ngx.arg在body filter阶段用来读取、更新应答数据。其中ngx.arg[1]是待发送的body,ngx.arg[2]指示后续是否还有待发送数据。所以在挂马时需要将ngx.arg[1]的值取出加入挂马代码后再赋值给ngx.arg[1],以下示例为在HTML的head标签中插入一句JavaScript:

然后在body_filter_by_lua∗阶段执行这个函数,Nginx的配置如下所示:

Nginx配置完成后,再次访问目标网站即可看到插入的JavaScript已经执行了,如图3-8所示。

●图3-8 Lua挂马测试

Lua环境下挂马的检测方法与检测Webshell的方法类似,也是通过检查Nginx、OpenResty与Tengine的配置文件,确认有没有可疑的Lua被加载,以及确认正常的Lua业务代码是否被篡改。

3.4.4 Lua环境下的数据监听原理与防御

OpenResty中实现数据监听可以在后端程序接收到请求之前,对数据进行预处理,比如只在access_by_lua∗阶段用ngx.req.read_body()和local post_args=ngx.req.get_post_args()就可以获取到用户POST请求的数据。然后可以再利用lua-resty-http模块将数据以POST的方式提交到攻击者指定的地方,以下代码实现了一个数据监听的函数:

如果将监听的代码放入Nginx的HTTP节中,表示全局监听并窃取POST数据,这样攻击者就会收到所有的POST数据请求,有些可能是不关心的数据,通常的做法是放入目标站点关键的location中,如/login、/admin等。

由于lua-resty-http是基于cosocket实现的,所以不能放在以下几个阶段:set_by_lua∗、log_by_lua∗、header_filter_by_lua∗和body_filter_by_lua∗

如果只想记录正确的密码,过滤掉错误的密码,需要在header_filter_by_lua∗或body_filter_by_lua∗阶段通过服务器返回的值来判断用户POST提交的密码是否正确,这时如果想提交到服务器中,就不能使用lua-resty-http了,因为lua-resty-http的底层是cososocket,不支持在这个阶段执行。可以通过ngx.timer.at以异步的方式提交。

另外也可以使用第三方的模块lua-requests在header_filter_by_lua∗或body_filter_by_lua∗阶段提交数据,此模块不受Nginx执行阶段的限制,可以在任何阶段调用

这里用Tornado编写一个接受POST参数的Web程序,它会接收OpenResty监听到并发送过来的账户信息,测试代码及效果如图3-9所示。

●图3-9 Lua数据窃取测试

Lua环境下数据监听的恶意代码检测与检测Webshell的方法类似,也是通过检查Nginx、OpenResty与Tengine的配置文件,确认有没有可疑的Lua被加载进去,以及确认正常的Lua业务代码是否被篡改。

3.4.5 Lua代码的隐藏与加密

在nginx.conf中加入了执行Lua的代码后非常容易被发现,有些攻击者可能用include命令将代码放置得隐蔽一些。例如,可以将以下代码中的lua_package_path与init_by_lua_file命令移到mime.types中,效果是一样的,但更加隐蔽。

有些攻击者会把Lua加载的配置代码放在隐蔽的地方,还会把明文的Lua代码进行加密,以增大被检测出来的难度,原理如下。

OpenResty使用的Lua引擎是LuaJIT,LuaJIT提供了一个luajit-b参数,可以将代码编译为字节码,这样就不容易被看到明文代码了。使用方式如图3-10所示(OpenResty的LuaJIT的默认路径为/usr/local/openresty/luajit/bin/luajit),用编译后的Lua字节码替换明文的文件即可。

●图3-10 Lua代码加密测试

了解了攻击者的方法,防御起来就比较简单了。在检测是否被插入恶意代码时,也要检测include命令包含的文件的内容,发现有被加密的文件时,如果正常业务的Lua文件没有加密,那被加密的文件很有可能是攻击者留下的,需要进一步进行排查。