mirror of https://github.com/zulip/zulip.git
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:
parent
93a344fc3c
commit
a5496f4098
|
@ -121,6 +121,7 @@ log][commit-log] for an up-to-date list of raw changes.
|
||||||
|
|
||||||
## Zulip 4.x series
|
## Zulip 4.x series
|
||||||
|
|
||||||
|
- CVE-2021-43799: Remote execution of code involving RabbitMQ.
|
||||||
- Closed access to RabbitMQ port 25672; initial installs tried to
|
- Closed access to RabbitMQ port 25672; initial installs tried to
|
||||||
close this port, but failed to restart RabbitMQ for the
|
close this port, but failed to restart RabbitMQ for the
|
||||||
configuration.
|
configuration.
|
||||||
|
|
|
@ -13,7 +13,7 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import NoReturn
|
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
|
# Check if rabbitmq port 25672 is listening on anything except 127.0.0.1
|
||||||
rabbitmq_dist_listen = listening_publicly(25672)
|
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:
|
if args.skip_puppet and rabbitmq_dist_listen:
|
||||||
logging.error(
|
logging.error(
|
||||||
"RabbitMQ is publicly-accessible on %s; this is a security vulnerability!",
|
"RabbitMQ is publicly-accessible on %s; this is a security vulnerability!",
|
||||||
", ".join(rabbitmq_dist_listen),
|
", ".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(
|
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 "
|
"(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)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -304,6 +320,16 @@ if rabbitmq_dist_listen:
|
||||||
logging.info("Shutting down rabbitmq to adjust its ports...")
|
logging.info("Shutting down rabbitmq to adjust its ports...")
|
||||||
subprocess.check_call(["/usr/sbin/service", "rabbitmq-server", "stop"])
|
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
|
# Adjust Puppet class names for the manifest renames in the 4.0 release
|
||||||
class_renames = {
|
class_renames = {
|
||||||
"zulip::app_frontend": "zulip::profile::app_frontend",
|
"zulip::app_frontend": "zulip::profile::app_frontend",
|
||||||
|
|
|
@ -10,6 +10,10 @@ else
|
||||||
sudo=(sudo)
|
sudo=(sudo)
|
||||||
fi
|
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_USERNAME=$("$(dirname "$0")/../get-django-setting" RABBITMQ_USERNAME)
|
||||||
RABBITMQ_PASSWORD=$("$(dirname "$0")/../get-django-setting" RABBITMQ_PASSWORD)
|
RABBITMQ_PASSWORD=$("$(dirname "$0")/../get-django-setting" RABBITMQ_PASSWORD)
|
||||||
|
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue