Hacking misconfigured supervisor

If you manage a server you sometimes need to run some applications in the background.

Supervisor is a service that allows to monitor, gather logs, or automatically restart these processes in a consistent interface. Admins who host many applications on a web server often opt for such a solution. But unfortunately you can be in serious trouble if you leave supervisor configured inappropriately.

In this post we show, how can you exploit misconfigured supervisor and thus perform a privilege escalation attack - get access to root account from a normal, unprivileged one.

Level 1 (python packages)

Let’s assume the admin of a machine with supervisor installed on has given us access to a unprivileged linux account on that machine and we can call:

sudo supervisorctl

Being able to call only this one command with sudo we will get access to system shell with root privileges.

Supervisor is written in python thus it uses python packages which are looked for anytime supervisor starts. Places where these packages can be usually found are e.g. /usr/lib/python2.7/site-packages or /usr/local/lib/python2.7/dist-packages.

Unfortunately we are logged in as a regular user so we can’t access these directories and change files in them. However, supervisor, just like a typical python program, looks for packages in $HOME/.local/lib/python2.7/site-packages also and we indeed have access to our $HOME directory. Let’s make a file org.py (org is one of the default python packages that is included by the supervisor) in $HOME/.local/lib/python2.7/site-packages/org.pythat looks like the following:

import os
print "Running org.py as", os.system("whoami")

Whenever supervisor is started org.py will be fallaciously interpreted as a python package and its code executed with root privileges:

$sudo supervisorctl
Running org.py as root
supervisor>

How to defend?

In /etc/sudoers (accessible via sudo visudo command) we will add a record disabling environment variable $HOME to be passed on to privileged command called via sudo by a regular user.

#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults        env_keep -= "HOME"
Defaults        env_reset
Defaults        mail_badpass
[...]
Now, if a normal user launches supervisor the $HOME variable will point to home directory of root. And again, we don’t have access to it.

Level 2 (history file)

So are we protected? Not very much.

Default supervisor configuration file is located in /etc/supervisor/supervisor.conf, but our privileges are insufficient to edit it or even read. Nonetheless supervisor on startup is looking for configuration file in the current directory as well. If we place our malicious configuration file in the current directory it will be loaded by the supervisor. That configuration can be for example:

;supervisor config file
[...]
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket
history_file=/etc/shadow

Its contents is just a default configuration file with one line added. Because of it /etc/shadow will be treated as a history file i.e. a file that all the previous commands are saved in. Let’s launch supervisor shell

$sudo supervisorctl
supervisor> root:$6$kqn8WIgZ$za4NWroVMm3SvCfBGkongbH7/0RT9x9YSKEv.1cn1wEf01.DG/FJ5ZwY3Wc9Qw37WSk7etywghj0oVC/MPzd50:17463:0:99999:7:::

If we press the up arrow on our keyboard normally we would see the last executed command like in a usual terminal. However, the history file now points to the password file so instead of the last command we will see the last password hash from /etc/shadow. Scrolling through we can read hashes of all of the users, including root. Moreover, every command called by us will be added to /etc/shadow (even if it’s not a valid supervisorctl command), so we can add an user with root privileges that we log in as afterwards.

How to defend?

We can prevent this attack by disabling access to supervisor shell for regular users. Let’s say that a user can now only call sudo supervisorctl status which check the status of all currently running apps.

Level 3 (plugins)

Let’s assume we don’t have access to superviser console now and we can only check the status of currently running applications. This time we’ll exploit the plugins functionality in supervisor, and again, gain root privileges.

They work as following: supervisor calls chosen python function from one of installed packages. The first and only argument is a supervisor class object. Yet we have full control of named arguments (**kwargs). If there is a python library which “ignores” arguments and execute any code with root privileges. Example of such a library is hgext. The config file will look as following:

;supervisor config file
[...]
[ctlplugin:x]
command=touch /i-was-here
supervisor.ctl_factory=hgext.pager:_pagefork
If we launch supervisor
sudo supervisorctl status

Command defined by us will be executed. In this case we created a folder in root-only directory.

How to defend?

We don’t want a regular user to launch supervisor loading malicious config file so we’ll allow him only to call it with -c parameter

sudo supervisorctl -c “/absolute/path/to/config/file”

thus supervisor will load only the correct config file placed in absolute path given after -c parameter.

Is this secure?

We don't know if after applying all the defensive strategies we proposed, there is any way to exploit supervisor - can you do that?

If you come up with a solution how to do so let us know on hackburger@laboratorium.ee and you’ll receive a brand new supervisor badge next to your nickname in the hackburger ranking.

published by Cheetar on Dec. 12, 2017