some exploration on the authentication mechanism of zimbra mail service

Posted by barello at 2020-03-11

Zimbra is a free email server that is widely used in the world. In the first paragraph of this paper, we will introduce the principle of exploiting the vulnerability in 2019 in detail. In the second paragraph, we will introduce our team's exploration of the authentication mechanism of Zimbra's email service. In the third paragraph, we will point out the research direction of Zimbra's vulnerability in the future for your reference.

I. the utilization principle of Zimbra vulnerability in 2019

        In 2019, Zimbra vulnerability contains three CVE sub vulnerabilities: two of them are xxE vulnerabilities, which are designed to read the content of localconfig.xml file under the conf directory, especially the zimbra_ldap_password field in the content; the other is SSRF vulnerability, which is designed to enable administrator like operations such as reading mailbox without opening the 7071 management port on the public network User list, obtaining mailbox user cookie, creating administrator, uploading webshell and other operations.

(I) step 1: use the xxE vulnerability to obtain important fields

XxE vulnerability, commonly known as XML external entity injection vulnerability, can usually be used to read the file content of the server. Before Zimbra, there were two xxE vulnerabilities, one was blind injection, the other was explicit injection.

(1) xxE blind injection

The version affected by xxE blind injection is 2014 version and part of 2016 version. The vulnerability lies in the source file. There are multiple soap files in Zimbra's service directory. Through the unified file, the request requests of soap are distributed to each specific soap file to handle different request requests, such as batchrequest and authrequest. So the previous xxE blind annotation is caused by the fact that the source file does not prohibit the post input of XML from external entities. As long as the carefully constructed payload is used to request any soap file, any file can be read through the outer belt. Now, the vulnerability has been completely fixed with the following lines of code.

It is worth noting that the xxE blind annotation can only send back data through out band mode, and the normal FTP out band will be blocked when encountering special characters. At present, I haven't got a good solution to this problem. This problem also leads to the fact that the contents of Zimbra mail can be read directly but not.

(2) xxE note

XxE has significantly affected all 2014, 2016 and some 2017 versions. The vulnerability lies in the source file under the service directory, which is still the injection caused by the fact that no external entity is prohibited during XML request processing. Because the injection is explicit, any file content can be obtained directly in the return package only by constructing the payload carefully. At present, it is completely blocked by the following code.

(2) step 2: perform administrator like operations

(1) when the 7071 administrator port is opened in the public network

We carry the read Zimbra ﹣ LDAP ﹣ password, and carefully construct the payload to request / service / admin / soap URI, which will achieve our goal. We can construct getalldomainsrequest to get the website domain, createaccountrequest to create the mailbox account, or even modifyaccountrequest to upgrade the ordinary user to the administrator account.

(2) when the public network does not expose the 7071 administrator port

At this point, we need to take advantage of the third CVE vulnerability mentioned above - SSRF vulnerability, use the / service / proxy interface to access the internal port 7071 of, and make the indirect request of / service / admin / soap to achieve our administrator like operation purpose.

Therefore, some servers block the SSRF vulnerability and fail to open port 7071 in the public network. In this case, the Zimbra vulnerability in 2019 cannot be exploited successfully.

The mechanism of blocking SSRF vulnerability is to detect the parameters followed by / service / proxy. Through regular expression detection, once the target parameter is determined and followed by the URL type, it is banned by 403.

Two. On the authentication mechanism of Zimbra

In the process of its authentication, we compare the tokens generated in each step, and find some rules at present.

(I) the process of user authentication of Zimbra

Zimbra first compares the encrypted account and password entered by the user. If the password entered by the user is correct, preauth is performed. A string of cookies can be generated by SHA1 algorithm. The cookie name is zm_auth_token. Once the password is verified correctly, the cookie generated by preauth has five input factors, as shown in the following figure:

For example:

key: 6b7ead4bd425836e8cf0079cd6c1a05acc127acd07c8ee4b61023e19250e929c

account: [email protected]
by: name
expires: 0
timestamp: 1135280708088

account: [email protected]: nameexpires: 0timestamp: 1135280708088

Account is the complete mailbox name of the user who needs to log in. By is the login method. The value is name, which means log in through the mailbox name. Expires indicates whether it is an administrator, 0 is an ordinary user, 1 is an administrator, and timestamp is a time stamp in UNIX format. The length means a time stamp accurate to millisecond level.

Then, the key to preauth change is the key value of 64 bit length. The key value of each mailbox server is unique and confidential. It is a string of hash values generated by zmprov gdpak command during mail service initialization.

Some readers may have an idea at this time. If you know the domain name of the target email service, you can set up a Zimbra server locally and execute the same command again with the command. Can you generate the same key value, and then you can calculate the ZM auto token value of all users of the target email service?

I haven't tried, energetic readers can try, but through the source code analysis, the key generation algorithm in gdpak code is useful to random function, so I guess, even if the command is the same, the key values obtained in different environments at different times are not the same.

(II) another way to obtain ZM ﹣ auth ﹣ token

In addition to the normal authentication process to get the ZM auto token value of each mailbox user of Zimbra, there is another way to get it. That is to get the authToken of each user through ZM admin auth token.

Under normal circumstances, the generation of ZM admin auth token also requires two steps. In the environment of Zimbra 2017, the author compares the authToken of the two steps on multiple servers. (Note: compared with the 2014 version, the 2017 version is more difficult in length and complexity, whether it is the authToken of users or the authToken of admin).

For different mailbox servers in 2017 version, the red string is the same. And the reader can see that the three red strings of authToken generated in the first step and the second step are the same. The yellow field distinguishes between low admin token and the real auth admin token.

If you can find the rules of low admin token and auth admin token generation and the reasons for the difference of each change field, you may be able to construct the authToken recognized by the server. Of course, the premise is that you also need to know the mechanism of admin token generation and authentication.

III. research direction of Zimbra vulnerability in the future

One is that the process and mechanism of auth token authentication of admin have not been studied clearly; the other is whether the low admin token and auth admin token that can be directly authenticated can be calculated in a positive way. The third is whether we can bypass the blocking of SSRF vulnerability by some way. The fourth is whether we can get the authToken of admin or read the authToken of Zimbra LDAP password when we know the authToken of a single user of Zimbra mail service.

Some of the views in this paper are not necessarily correct, hoping to enlighten the readers of this paper. If readers have better methods, they also hope to communicate with the author. Respect hacker spirit - everyone for me, I for everyone.

IV. further research on the mechanism of Zimbra token generation

Zimbra will verify the validity of all the authtokens generated. The verification code is mainly implemented by three source files, namely Zimbra, and By reading the three source files, we understand the verification mechanism of all the authtokens. In the case of ZM admin auth token, there can be more than one valid authToken at the same time point, but can any valid authToken be randomly generated? It's hard!

All authtokens in Zimbra are divided into three sections: 0 ﹐ HMAC ﹐ data. A valid authToken must be calculated together with a 32-bit key value saved in the mail service configuration file. The final value generated is equal to HMAC, so the authToken will be valid.

If you don't know the 32-bit key value saved by the mail service, you can't calculate the HMAC through the data value, so you can't construct a valid ZM admin auth token value.

So I wonder if there is a special data value that can generate HMAC without changing the key value at will?

Therefore, the author combines the three source files to verify the validity of authToken, and tries to control the parameter I through fuzzy, with the change range of 0256, and add the parameter 0 to form the encoded parameter. To see the final result, the source code is as follows:

package test; import; import; import org.apache.commons.codec.binary.Hex; import javax.crypto.Mac; import javax.crypto.SecretKey; public class test { public static void main(String[] args) throws Exception { int i; for(i=0;i<=256;i++) { try { String encoded = "0__"+ (char)(i); System.out.println(encoded); int pos = encoded.indexOf('_'); if (pos == -1) { throw new Exception("invalid authtoken format"); } //String ver = encoded.substring(0, pos); int pos2 = encoded.indexOf('_', pos+1); if (pos2 == -1) { throw new Exception("invalid authtoken format"); } String hmac = encoded.substring(pos+1, pos2); String data = encoded.substring(pos2+1); String key = "sadfsadfdsafdsaf432432423432"; String computedHmac = getHmac(data, key.getBytes()); System.out.println(computedHmac); if (!computedHmac.equals(hmac)) { throw new Exception("hmac failure"); } System.out.println("Success!"); } catch (Exception e) { e.printStackTrace(); } } } public static String getHmac(String data, byte[] key) { try { ByteKey bk = new ByteKey(key); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(bk); return new String(Hex.encodeHex(mac.doFinal(data.getBytes()))); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("fatal error", e); } catch (InvalidKeyException e) { throw new RuntimeException("fatal error", e); } } static class ByteKey implements SecretKey { private static final long serialVersionUID = -7237091299729195624L; private final byte[] mKey; ByteKey(byte[] key) { mKey = key.clone(); } @Override public byte[] getEncoded() { return mKey; } @Override public String getAlgorithm() { return "HmacSHA1"; } @Override public String getFormat() { return "RAW"; } } }

The experimental results show that, basically, it will change with the change of key value. (sad...)

Although the idea of looking for vulnerabilities from the auth_token of admin is broken, the author has found some rules, as shown in the following figure:

The first segment is the authToken of admin. The three segments of 0? HMAC? Data in yellow are unchanged for the same server. The first blue segment in data is actually a time stamp. The change time is 2020 / 1 / 28 8:17:12, which is the author's generation time of authToken + 12 hours, that is, the default expiration time of authToken.