CVE-2021-43799 (Other, published 2022-01-25T21:15Z)
Description: value: 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 v3 base score: 9.8)
Weakness: CWE-338 \u2014 Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.
Repository: https://github.com/zulip/zulip
Files changed:
docs/overview/changelog.md
scripts/lib/upgrade-zulip-stage-2
scripts/setup/configure-rabbitmq
scripts/setup/generate-rabbitmq-cookie
Commit message: 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


Vulnerable code:
from typing import TYPE_CHECKING
        "To fix the above security issue, re-run the upgrade without --skip-puppet "
        "necessary services.  Running zulip-puppet-apply by itself is not sufficient."

Fixed code:
- CVE-2021-43799: Remote execution of code involving RabbitMQ.
from typing import TYPE_CHECKING, Optional
# 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.")
    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"
        "To fix the above security %s, re-run the upgrade without --skip-puppet "
        "necessary services.  Running zulip-puppet-apply by itself is not sufficient.",
        issue,
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"])

# If the RabbitMQ distribution cookie is insecure, reset it and
# restart RabbitMQ.
"${sudo[@]}" "$(dirname "$0")/generate-rabbitmq-cookie"

#!/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