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
This commit is contained in:
Alex Vandiver 2021-12-07 20:55:38 +00:00
parent 93a344fc3c
commit a5496f4098
4 changed files with 61 additions and 3 deletions

View File

@ -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.

View File

@ -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(
"To fix the above security issue, re-run the upgrade without --skip-puppet "
"RabbitMQ erlang cookie is insecure; this is a critical security vulnerability!"
)
issue = "issues"
logging.error(
"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",

View File

@ -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)

View File

@ -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 wasnt 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