From a5496f4098e3998c9b84e8dc564aa983d6cdf6e8 Mon Sep 17 00:00:00 2001 From: Alex Vandiver Date: Tue, 7 Dec 2021 20:55:38 +0000 Subject: [PATCH] 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 --- docs/overview/changelog.md | 1 + scripts/lib/upgrade-zulip-stage-2 | 32 +++++++++++++++++++++++--- scripts/setup/configure-rabbitmq | 4 ++++ scripts/setup/generate-rabbitmq-cookie | 27 ++++++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100755 scripts/setup/generate-rabbitmq-cookie diff --git a/docs/overview/changelog.md b/docs/overview/changelog.md index e9aba88835..64bf575f33 100644 --- a/docs/overview/changelog.md +++ b/docs/overview/changelog.md @@ -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. diff --git a/scripts/lib/upgrade-zulip-stage-2 b/scripts/lib/upgrade-zulip-stage-2 index 23747f350f..1bc2f45729 100755 --- a/scripts/lib/upgrade-zulip-stage-2 +++ b/scripts/lib/upgrade-zulip-stage-2 @@ -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", diff --git a/scripts/setup/configure-rabbitmq b/scripts/setup/configure-rabbitmq index 372b5cd9b3..e80b4f921b 100755 --- a/scripts/setup/configure-rabbitmq +++ b/scripts/setup/configure-rabbitmq @@ -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) diff --git a/scripts/setup/generate-rabbitmq-cookie b/scripts/setup/generate-rabbitmq-cookie new file mode 100755 index 0000000000..c54fde643e --- /dev/null +++ b/scripts/setup/generate-rabbitmq-cookie @@ -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:]' "$tmpfile" + mv "$tmpfile" "$cookiefile" + + if [ "$running" == "1" ]; then + service rabbitmq-server start + fi +fi