To solve this machine, we begin by enumerating open ports using
nmap – finding ports
80 open. From the webserver, we find an FQDN, which we use to enumerate an additional vHost. Going to the vHost, we find it is hosting an application to convert images to text using the Flask framework. Exploiting an SSTI (Server-Side Template Injection) RCE (Remote Code Execution) vulnerability in the application, we are able to get the SSH key of the
svc_acc user, and login and read
user.txt. Enumerating the machine further, we find a file that stands out. After further analysis, we see that we are able to append to the file. After triggering an SSH event,
root will execute the file. This allows us to get a reverse shell as
root – gaining access to
Like all machines, we begin by enumerating open ports using
nmap. From our scans, we find ports
$ sudo nmap -v -p- --min-rate 3000 $RHOST [...] $ sudo nmap -v -n -Pn -sV -A -p 22,80 -oA enum/nmap/tcp-scripts $RHOST # Nmap 7.92 scan initiated Wed May 4 08:40:25 2022 as: nmap -v -n -Pn -sV -A -p 22,80 -oA enum/nmap/tcp-scripts 10.129.112.17 Nmap scan report for 10.129.112.17 Host is up (0.064s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.6 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 02:5e:29:0e:a3:af:4e:72:9d:a4:fe:0d:cb:5d:83:07 (RSA) | 256 41:e1:fe:03:a5:c7:97:c4:d5:16:77:f3:41:0c:e9:fb (ECDSA) |_ 256 28:39:46:98:17:1e:46:1a:1e:a1:ab:3b:9a:57:70:48 (ED25519) 80/tcp open http nginx 1.14.0 (Ubuntu) |_http-title: Late - Best online image tools |_http-favicon: Unknown favicon MD5: 1575FDF0E164C3DB0739CF05D9315BDF |_http-server-header: nginx/1.14.0 (Ubuntu) | http-methods: |_ Supported Methods: GET HEAD Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Aggressive OS guesses: Linux 4.15 - 5.6 (95%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.3 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.0 (93%) No exact OS matches for host (test conditions non-ideal). Uptime guess: 16.668 days (since Sun Apr 17 16:38:32 2022) Network Distance: 2 hops TCP Sequence Prediction: Difficulty=263 (Good luck!) IP ID Sequence Generation: All zeros Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel TRACEROUTE HOP RTT ADDRESS 1 64.22 ms 10.129.112.17 Read data files from: /usr/bin/../share/nmap OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . # Nmap done at Wed May 4 08:40:38 2022 -- 1 IP address (1 host up) scanned in 14.00 seconds
Since we have a webserver running on port
80, we do further enumeration using
gobuster. These tools did not return any useful information. Since the TCP port count was low, we launch a basic UDP scan, however, this did not return any common ports. Going to the web server in the browser, we see the page is pretty simple.
At the bottom of the page, we find the FQDN
late.htb at the bottom of the page.
After adding the FQDN to our
/etc/hosts file, we start to enumerate it. Going
late.htb in the browser returns the same page as accessing the site via IP address. Next, we use
gobuster to enumerate vHosts, and find the vHost
Going to the vHost in the browser, we learn it’s an application that converts images to text using Flask. Based on this, we know the application is likely written using Python, and makes use of the Jinja templating engine.
Running tools like
gobuster on the vHost, unfortunately, do not yield any additional useful information.
As we suspect the server is using Jinja, and image upload is all we can do on the website, we decide to test for Server-Side Template Injection (SSTI). Using BurpSuite to manipulate our request, we change the magic bytes of an image we upload, and see that we are able to leak the path on the system.
When we try to fuzz the filename parameter with code, we do not see any results.
Lastly, we try something simpler by putting the injection code in an image we upload. This time, it works, and we get the results in the response.
As we now have a working proof-of-concept, we check for Remote Code Execution (RCE) by seeing if we are able to run the
id command on the system. Doing so, we see the application is running as the
Next, we want to try to get a shell on the machine. As we know SSH is running on the machine, we can attempt to get
svc_acc‘s SSH key. For this, we have to play around with the image settings, as the software is really finicky depending on the payload size. The settings we are able to use are “Bitstream Vera Sans Mono, 14pt” font, 1000×100 image size, and adjusting the kerning to 1.5. After uploading the payload, we get the user’s SSH key in the response, and login as
We are now able to read
Having gained a shell on the machine as
svc_acc, we begin enumeration using
LinPEAS. From the output, we see that port
3000 is listening locally, however, when we look into it, we see that it’s the flask application we used to gain access to the system. Next, we see
/usr/local/sbin/ssh-alert.sh listed as an interesting file modified within the past 5 minutes, and decide to look into it.
Looking at the file, we see that it sends the
root user a system mail every time a user logs in.
To get addition context, and to look for a route to exploitation, we upload and execute
pspy. We then start another SSH session. From
pspy, we see the script get executed as
root, then shortly after, we see cron jobs copy a fresh copy of the script, and set the script to be owned by
After some additional time, there appears to be a cron job that makes the script only appendable. As we will eventually own the file, we should be able to append a reverse shell and get access as
root when the SSH event triggers the file execution. After starting a socket handler using
netcat, we append the reverse shell after
svc_acc is set as the owner, and execute the
logout command. Doing so, we get a reverse shell as
root, and can now read
Thank you for taking the time to read my write-up. I am interested in other ways this machine has been solved. Feel free to reach out to me and we can discuss it. Thanks!