CVE-2021-43799 (published 2022-01-25T21:15Z)

Zulip is an open-source team collaboration tool. Zulip Server installs RabbitMQ for internal message passing. In versions of Zulip Server prior to 4.9, the initial installation (until first reboot, or restart of RabbitMQ) does not successfully limit the default ports which RabbitMQ opens; this includes port 25672, the RabbitMQ distribution port, which is used as a management port. RabbitMQ\'s default "cookie" which protects this port is generated using a weak PRNG, which limits the entropy of the password to at most 36 bits; in practicality, the seed for the randomizer is biased, resulting in approximately 20 bits of entropy. If other firewalls (at the OS or network level) do not protect port 25672, a remote attacker can brute-force the 20 bits of entropy in the "cookie" and leverage it for arbitrary execution of code as the rabbitmq user. They can also read all data which is sent through RabbitMQ, which includes all message traffic sent by users. Version 4.9 contains a patch for this vulnerability. As a workaround, ensure that firewalls prevent access to ports 5672 and 25672 from outside the Zulip server.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:P/I:N/A:N)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-338
cwe_name: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
description: The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.
extended_description: 
            When a non-cryptographic PRNG is used in a cryptographic context, it can expose the cryptography to certain types of attacks.
            Often a pseudo-random number generator (PRNG) is not designed for cryptography. Sometimes a mediocre source of randomness is sufficient or preferable for algorithms that use random numbers. Weak generators generally take less processing power and/or do not use the precious, finite, entropy sources on a system. While such PRNGs might have very useful features, these same features could be used to break the cryptography.
         
         

Fix information:
repo_url: https://github.com/zulip/zulip
commit_details: repo_url: https://github.com/zulip/zulip
author: Alex Vandiver
author_date: 2021-12-07 20:55:38+00:00
msg: CVE-2021-43799: Set a secure Erlang cookie.

The RabbitMQ docs state ([1]):

    RabbitMQ nodes and CLI tools (e.g. rabbitmqctl) use a cookie to
    determine whether they are allowed to communicate with each
    other. [...] The cookie is just a string of alphanumeric
    characters up to 255 characters in size. It is usually stored in a
    local file.

...and goes on to state (emphasis ours):

    If the file does not exist, Erlang VM will try to create one with
    a randomly generated value when the RabbitMQ server starts
    up. Using such generated cookie files are **appropriate in
    development environments only.**

The auto-generated cookie does not use cryptographic sources of
randomness, and generates 20 characters of `[A-Z]`.  Because of a
semi-predictable seed, the entropy of this password is thus less than
the idealized 26^20 = 94 bits of entropy; in actuality, it is 36 bits
of entropy, or potentially as low as 20 if the performance of the
server is known.

These sizes are well within the scope of remote brute-force attacks.

On provision, install, and upgrade, replace the default insecure
20-character Erlang cookie with a cryptographically secure
255-character string (the max length allowed).

[1] https://www.rabbitmq.com/clustering.html#erlang-cookie
repository: repo_url: https://github.com/zulip/zulip
repo_name: zulip/zulip
description: Zulip server and web application. Open-source team chat that helps teams stay productive and focused.
repo_language: Python
owner: zulip
file_changes: filename: changelog.md
change_type: ModificationType.MODIFY
diff: @@ -121,6 +121,7 @@ log][commit-log] for an up-to-date list of raw changes.
 
 ## Zulip 4.x series
 
+- CVE-2021-43799: Remote execution of code involving RabbitMQ.
 - Closed access to RabbitMQ port 25672; initial installs tried to
   close this port, but failed to restart RabbitMQ for the
   configuration.

programming_language: Markdown
filename: upgrade-zulip-stage-2
change_type: ModificationType.MODIFY
diff: @@ -13,7 +13,7 @@ import re
 import subprocess
 import sys
 import time
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Optional
 
 if TYPE_CHECKING:
     from typing import NoReturn
@@ -116,15 +116,31 @@ IS_SERVER_UP = True
 
 # Check if rabbitmq port 25672 is listening on anything except 127.0.0.1
 rabbitmq_dist_listen = listening_publicly(25672)
+# Check the erlang magic cookie size
+cookie_size: Optional[int] = None
+if os.path.exists("/var/lib/rabbitmq/.erlang.cookie"):
+    with open("/var/lib/rabbitmq/.erlang.cookie", "r") as cookie_fh:
+        cookie_size = len(cookie_fh.readline())
+else:
+    logging.info("No RabbitMQ erlang cookie found, not auditing RabbitMQ security.")
 if args.skip_puppet and rabbitmq_dist_listen:
     logging.error(
         "RabbitMQ is publicly-accessible on %s; this is a security vulnerability!",
         ", ".join(rabbitmq_dist_listen),
     )
+    issue = "issue"
+    if cookie_size is not None and cookie_size == 20:
+        # See the below comment -- this is used as a lightweight
+        # signal for a cookie made with Erlang's bad randomizer.
+        logging.error(
+            "RabbitMQ erlang cookie is insecure; this is a critical security vulnerability!"
+        )
+        issue = "issues"
     logging.error(
-        "To fix the above security issue, re-run the upgrade without --skip-puppet "
+        "To fix the above security %s, re-run the upgrade without --skip-puppet "
         "(which may be set in /etc/zulip/zulip.conf), in order to restart the "
-        "necessary services.  Running zulip-puppet-apply by itself is not sufficient."
+        "necessary services.  Running zulip-puppet-apply by itself is not sufficient.",
+        issue,
     )
     sys.exit(1)
 
@@ -304,6 +320,16 @@ if rabbitmq_dist_listen:
     logging.info("Shutting down rabbitmq to adjust its ports...")
     subprocess.check_call(["/usr/sbin/service", "rabbitmq-server", "stop"])
 
+if cookie_size is not None and cookie_size == 20:
+    # Checking for a 20-character cookie is used as a signal that it
+    # was generated by Erlang's insecure randomizer, which only
+    # provides between 20 and 36 bits of entropy; were it 20
+    # characters long by a good randomizer, it would be 96 bits and
+    # more than sufficient.  We generate, using good randomness, a
+    # 255-character cookie, the max allowed length.
+    logging.info("Generating a secure erlang cookie...")
+    subprocess.check_call(["./scripts/setup/generate-rabbitmq-cookie"])
+
 # Adjust Puppet class names for the manifest renames in the 4.0 release
 class_renames = {
     "zulip::app_frontend": "zulip::profile::app_frontend",

programming_language: Python
filename: configure-rabbitmq
change_type: ModificationType.MODIFY
diff: @@ -10,6 +10,10 @@ else
     sudo=(sudo)
 fi
 
+# If the RabbitMQ distribution cookie is insecure, reset it and
+# restart RabbitMQ.
+"${sudo[@]}" "$(dirname "$0")/generate-rabbitmq-cookie"
+
 RABBITMQ_USERNAME=$("$(dirname "$0")/../get-django-setting" RABBITMQ_USERNAME)
 RABBITMQ_PASSWORD=$("$(dirname "$0")/../get-django-setting" RABBITMQ_PASSWORD)
 

programming_language: Shell
filename: generate-rabbitmq-cookie
change_type: ModificationType.ADD
diff: @@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+#
+# rabbitmq sets an insecure "magic cookie" which is used for auth;
+# reset it to be longer.
+set -eu
+
+cookiefile=/var/lib/rabbitmq/.erlang.cookie
+# If the RabbitMQ distribution cookie is insecure, reset it
+if [ ! -f "$cookiefile" ] || ! size="$(wc -c "$cookiefile")" || [ "${size%% *}" -le 20 ]; then
+    running=0
+    if service rabbitmq-server status >/dev/null; then
+        running=1
+        service rabbitmq-server stop
+    fi
+
+    echo "Setting a more secure RabbitMQ distribution magic cookie"
+    cookie="$(LC_ALL=C tr -dc '[:alnum:]' </dev/urandom | head -c255)"
+    [ "${#cookie}" -eq 255 ] # make sure tr wasn’t OOM-killed
+    tmpfile="$(mktemp "$cookiefile.XXXXXXXXXX")"
+    chown rabbitmq: "$tmpfile"
+    printf '%s' "$cookie" >"$tmpfile"
+    mv "$tmpfile" "$cookiefile"
+
+    if [ "$running" == "1" ]; then
+        service rabbitmq-server start
+    fi
+fi

programming_language: Shell