0x00 Preface

---

The previous article "Zimbra SOAP API Development Guide" introduced the method of calling the Zimbra SOAP API, along with the open-source code Zimbra_SOAP_API_Manage.

This article will expand on that foundation by adding features that can be achieved using administrator privileges.

0x01 Introduction

---

This article will cover the following topics:

  • Obtaining the token of a specified mailbox user
  • Uploading files to the server via the clientUploader plugin
  • Log detection

0x02 Obtaining the token of a specified mailbox user

---

Documentation: https://files.zimbra.com/docs/soap_api/8.8.15/api-reference/zimbraAdmin/DelegateAuth.html

The corresponding namespace is zimbraAdmin

The requested address is: uri+":7071/service/admin/soap"

According to the SOAP format in the documentation, it can be implemented with the following Python code:

def gettoken_request(uri,token):
print("[*] Input the mailbox:")
mail = input("[>]: ")
request_body="""


{token}




{mail}



"""
try:
print("[*] Try to get the token")
r=requests.post(uri+":7071/service/admin/soap",data=request_body.format(token=token,mail=mail),verify=False,timeout=15)
if 'authToken' in r.text:
pattern_token = re.compile(r"(.*?)")
token = pattern_token.findall(r.text)
print("[+] authTOken:%s"%(token[0]))
except Exception as e:
print("[!] Error:%s"%(e))
exit(0)

The returned result is as follows:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX3600000

Extracting the authToken value can be used for mailbox login. The login method is as follows:

Navigate to the Zimbra mailbox login web page and add the following Cookie information:

Name: ZM_AUTH_TOKEN

Value: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

On the login page, enter the mailbox username (no password required) and click login.

Method to add Cookie in Chrome browser:

Press F12 in Chrome browser to open Developer Tools, select the Application tab.

Expand Storage -> Cookies sequentially.

0x03 Upload files to the server via the clientUploader plugin.

---

Note: The file upload operation requires setting Content-Type in the request headers to multipart/form-data; boundary=${boundary}. Example format:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1abcdefghijklmno

Complete packet format example:

POST /service/extension/clientUploader/upload HTTP/1.1
Host: mail.xx.com
Proxy-Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1abcdefghijklmno
Content-Length: 400
Cookie: ZM_ADMIN_AUTH_TOKEN=0_530bf417d0f3e55ed628e4671e44b1dea4652bab_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313535343835323934303131393b61646d696e3d313a313b
Upgrade-Insecure-Requests: 1

------WebKitFormBoundary1abcdefghijklmno
Content-Disposition: form-data; name="file"; filename="test.jsp"
Content-Type: image/jpeg

test12345
------WebKitFormBoundary1abcdefghijklmno--

Here, ------WebKitFormBoundary1abcdefghijklmno is the delimiter, and ------WebKitFormBoundary1abcdefghijklmno-- is the terminator.

If this attribute is not set, the file upload operation will fail, returning the following result:

The request does not upload a file

During the Python script development process, if the attribute is directly added in the Headers: Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1abcdefghijklmno, the sample code is as follows:

headers["Content-Type"]="multipart/form-data; boundary=----WebKitFormBoundary1abcdefghijklmno"

This will cause a bug and prevent successful execution.

The packet format at this point is as follows:

POST /service/extension/clientUploader/upload HTTP/1.1
Host: mail.xx.com
Proxy-Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1abcdefghijklmno
Content-Length: 400
Cookie: ZM_ADMIN_AUTH_TOKEN=0_530bf417d0f3e55ed628e4671e44b1dea4652bab_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313535343835323934303131393b61646d696e3d313a313b
Upgrade-Insecure-Requests: 1

------3c4bc2fbc2368a87e5def7b234fd126b
Content-Disposition: form-data; name="file"; filename="test.jsp"
Content-Type: image/jpeg

test12345
------3c4bc2fbc2368a87e5def7b234fd126b--

It was found that the delimiter and terminator are randomly regenerated values, not the ----WebKitFormBoundary1abcdefghijklmno we set in the request headers.

Therefore, the Python code needs to be modified. Here is one solution: use the requests_toolbelt library.

Code example:

fileContent = 0;
path = input("[*] Input the path of the file:")
with open(path,'r') as f:
fileContent = f.read()
filename = path
print("[*] filepath:"+path)
print("[*] filedata:"+fileContent)

headers = {
"Content-Type":"application/xml"
}
headers["Content-Type"]="multipart/form-data; boundary=----WebKitFormBoundary1abcdefghijklmno"
headers["Cookie"]="ZM_ADMIN_AUTH_TOKEN="+token+";"

m = MultipartEncoder(fields={
'filename1':(None,"test",None),
'clientFile':(filename,fileContent,"image/jpeg"),
'requestId':(None,"12345",None),
}, boundary = '----WebKitFormBoundary1abcdefghijklmno')

r = requests.post(uri+"/service/extension/clientUploader/upload",data=m,headers=headers,verify=False)
if 'window.parent._uploadManager.loaded(1,' in r.text:
print("[+] Upload Success!")
print("[+] URL:%s/downloads/%s"%(uri,filename))
else:
print("[!]")
print(r.text)
exit(0)

After successful upload, the path is in the downloads directory, accessible only to verified users

0x04 Open Source Code

---

The new code has been uploaded to GitHub, address as follows:

An open-source project

Added the following two features:

  • Gettoken
  • upload

Simultaneously added support for the CVE-2019-9621 SSRF vulnerability, enabling access to management resources via the SSRF exploit when the mail server's 7071 management port is closed.

0x05 Log Detection

---

The location of login logs is /opt/zimbra/log/mailbox.log

For other types of mail logs, refer to https://wiki.zimbra.com/wiki/Log_Files

0x06 Summary

---

This article expands the invocation methods of the Zimbra SOAP API, adding two practical functions: obtaining tokens for specified mailbox users and uploading files to the server via the clientUploader plugin, documenting the implementation details.