0x00 Preface

---

This article will introduce multiple methods for detecting Zimbra versions, implement automation through Python, document development details, and provide open-source code.

0x01 Introduction

---

This article will cover the following:

  • Implementation Approach
  • Implementation Details
  • Open-Source Code

0x02 Implementation Approach

---

There are many methods to check the Zimbra version, each with its own advantages and disadvantages. The specific methods are as follows:

1. Via the Web Management Page

Access the 7071 management page through a browser, where the current Zimbra version is displayed on the main page.

For example, my test environment displays as:

Zimbra Version: 9.0.0_GA_4273.NETWORK

The version obtained through this method is the accurate version

2. By executing the commands

su zimbra
/opt/zimbra/bin/zmcontrol -v

For example, my test environment displays as:

Release 9.0.0.GA.3924.UBUNTU16.64 UBUNTU16_64 NETWORK edition, Patch 9.0.0_P24.1.

For the output results, note the following issues:

  • Release 9.0.0.GA.3924 corresponds to the initial installation package version and does not change with patch updates
  • Patch 9.0.0_P24.1 is the patch version, which changes when upgrading

Note:

For Zimbra patch updates, refer to:

https://wiki.zimbra.com/wiki/Zimbra_Releases/9.0.0/patch_installation

3. Via Zimbra SOAP API

Under default configuration, the zimbraSoapExposeVersion attribute is set to FALSE. Query command:

zmprov gs `hostname` | grep ExposeVersion

Return result:

zimbraImapExposeVersionOnBanner: FALSE
zimbraLmtpExposeVersionOnBanner: FALSE
zimbraPop3ExposeVersionOnBanner: FALSE
zimbraReverseProxyImapExposeVersionOnBanner: FALSE
zimbraReverseProxyPop3ExposeVersionOnBanner: FALSE
zimbraSoapExposeVersion: FALSE

After setting the zimbraSoapExposeVersion attribute to TRUE, the version can be obtained via Zimbra SOAP API. Command to modify the attribute:

su zimbra
/opt/zimbra/bin/zmprov mcf zimbraSoapExposeVersion TRUE

Example SOAP request format:




{token}






Return result under default configuration:

soap:Senderpermission denied: Version info is not available.service.PERM_DENIEDqtp2008966511-673279:1675321733266:bb7c9a24ece078fe

Return result after enabling zimbraSoapExposeVersion:

The version obtained through this method is the accurate version

4. Via IMAP protocol

Require Zimbra to open port 143

Command execution example:

nc 192.168.1.1 143
A001 ID NIL

Return result:

* ID ("NAME" "Zimbra" "VERSION" "9.0.0_GA_4273" "RELEASE" "20220506180442")

The version obtained through this method is the accurate version

5. Via IMAP over SSL protocol

Require Zimbra to open port 993

Command execution example:

openssl s_client -connect 192.168.1.1:993
show id ("a" "a")

Return result:

* ID ("NAME" "Zimbra" "VERSION" "9.0.0_GA_4273" "RELEASE" "20220506180442")

The version obtained through this method is the accurate version

6. Via specific URL

The specific URL contains installation information

Note:

This URL is not unique

Example access location: https://192.168.1.1/js/zimbraMail/share/model/ZmSettings.js

Example return result:

this.registerSetting("CLIENT_DATETIME", {type:ZmSetting.T_CONFIG, defaultValue:"20220324-0623"});
this.registerSetting("CLIENT_RELEASE", {type:ZmSetting.T_CONFIG, defaultValue:"20220324053424"});
this.registerSetting("CLIENT_VERSION", {type:ZmSetting.T_CONFIG, defaultValue:"9.0.0_GA_4258"});

CLIENT_DATETIME and CLIENT_RELEASE are consistent with the creation time of this file. The version obtained through this method is for reference only and cannot be used as an accurate basis for version detection

0x03 Implementation Details

---

Integrating the above detection methods, to adapt to various environments, the program implementation selects three methods: via IMAP protocol, via IMAP over SSL protocol, and via specific URL

1. Via IMAP protocol

Complete example code:

def getversionimap(ip):
try:
print("[*] Try to connect: " + ip + ":143")
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.settimeout(5)
s.connect((ip, 143))
s.sendall(''.encode())
response = s.recv(1024)
if "OK" in response.decode('UTF-8'):
print(" OK")
else:
print(response.decode('UTF-8'))
s.close()
sys.exit(0)
s.sendall('A001 ID NIL\r\n'.encode())
response = s.recv(1024)
if "Zimbra" in response.decode('UTF-8'):
versiondata=re.compile(r"VERSION\" \"(.*?)\"")
version = versiondata.findall(response.decode('UTF-8'))[0]

releasedata=re.compile(r"RELEASE\" \"(.*?)\"")
release = releasedata.findall(response.decode('UTF-8'))[0]
print("[+] Version: " + version)
print(" Release: " + release)
return release
else:
print(response.decode('UTF-8'))
s.close()
except Exception as e:
print(e)
return ""

2. Via IMAP over SSL protocol

Need to convert IP to hostname as parameter, example code:

hostname = socket.gethostbyaddr(ip)
print(hostname[0])

Complete example code:

def getversionimapoverssl(ip):
try:
hostname = socket.gethostbyaddr(ip)
print("[*] Try to connect: " + hostname[0] + ":993")
context = ssl.create_default_context()
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s = context.wrap_socket(s, server_hostname=hostname[0])
s.settimeout(5)
s.connect((ip, 993))
s.sendall(''.encode())
response = s.recv(1024)
if "OK" in response.decode('UTF-8'):
print(" Success")
else:
print(response.decode('UTF-8'))
s.close()
sys.exit(0)
s.sendall('A001 ID NIL\r\n'.encode())
response = s.recv(1024)
if "Zimbra" in response.decode('UTF-8'):
versiondata=re.compile(r"VERSION\" \"(.*?)\"")
version = versiondata.findall(response.decode('UTF-8'))[0]

releasedata=re.compile(r"RELEASE\" \"(.*?)\"")
release = releasedata.findall(response.decode('UTF-8'))[0]
print("[+] Version: " + version)
print(" Release: " + release)
return release
else:
print(response.decode('UTF-8'))
s.close()
except Exception as e:
print(e)
return ""

Some environments cannot resolve IP to hostname, causing error: [Errno 11004] host not found. Therefore, the program logic prioritizes using the IMAP protocol.

3. Via specific URL

Complete example code:

def getversionweb(ip):
try:
url = "https://" + ip + "/js/zimbraMail/share/model/ZmSettings.js"
print("[*] Try to access: " + url)
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
}
response = requests.get(url, headers=headers, verify=False, timeout=5)

if response.status_code == 200 and 'CLIENT_RELEASE' in response.text:
print(" Success")
VERSION_name = re.compile(r"CLIENT_VERSION\", {type:ZmSetting.T_CONFIG, defaultValue:\"(.*?)\"}\);")
CLIENT_VERSION = VERSION_name.findall(response.text)

RELEASE_name = re.compile(r"CLIENT_RELEASE\", {type:ZmSetting.T_CONFIG, defaultValue:\"(.*?)\"}\);")
CLIENT_RELEASE = RELEASE_name.findall(response.text)

print("[+] Version: " + CLIENT_VERSION[0])
print(" Release: " + CLIENT_RELEASE[0])
else:
print("[-]")
print(response.status_code)
print(response.text)
except Exception as e:
print(e)

0x04 Open Source Code

---

The complete implementation code has been uploaded to GitHub at the following address:

An open source project

The code first attempts to obtain version information through a specific URL, then reads version information via the IMAP protocol. If that fails, it finally reads version information via IMAP over SSL protocol.

0x05 Summary

---

This article introduces multiple methods for Zimbra version detection, compares their advantages and disadvantages, selects effective methods and implements automation through Python, records development details, and open-sources the code as an excellent learning example.