# Terrier CTF [Part 1]: Network Reconnaissance to SSTI: The Methodology Beneath the Exploit

## Step 0: Network Address Discovery

The Terrier CTF Boot2Root machine presented an interesting challenge from the start: identifying the machine’s IP address on the local network. While this might seem quite straightforward in theory, the practical reality of VM networking configurations often introduces unexpected complexity which is worth documenting.

Usually, most Boot2Root VMs do us a favor and print their IP address to the console when they boot. Without this convinience, we need a more systematic approach to network discovery. I will be demonstrating the approach I used when using VMWare Workstation Pro on my Ubuntu machine.

First, ensure that the networking mode is set to NAT. This ensures that the machines are on the same virtual network segment and can access each other. Following this, in the Advanced settings menu of the dialog box, record the MAC address.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758773359618/d4fde2bc-21c9-4a35-ad71-7447bdab9d7e.png align="center")

Then using the `arp -a` command on the shell, we get the list of various IP addresses associated with the physical addresses

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758773456323/080a2570-cbdb-4cb5-a460-ad4959ff9092.png align="center")

Without console IP disclosure, three discovery vectors exist: ARP cache inspection (fast, requires same subnet), nmap subnet sweep (thorough, time-intensive), or DHCP lease examination (requires hypervisor access). ARP cache provided sufficient granularity, two candidates versus 254; making it the optimal effort-to-information ratio. The ARP command shows the list of IP addresses and their corresponding MAC addresses that your system has recently communicated with on the local network

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Documentation note: Screenshots in this series span multiple testing sessions, so you may notice the target IP address changes between sections. This reflects the dynamic nature of DHCP in virtualized environments and doesn't affect the methodology demonstrated.</div>
</div>

## Step 1: Reconnaissance

Now that we have the IP address, I proceeded with a standard nmap scan to identify open ports. The scan revealed ports 22 (SSH) and 5000 (HTTP) were accessible.

The presence of password based authentication in ssh shows it’s a worthy attack vector, and opening accessing the port 5000 using a web-browser reveals a static web page titled R&D portal.

### SSH exploit:

SSH authentication without username enumeration or organizational context is at best, brute force. This approach is statistically futile in CTF environments which are designed around exploiting than guessing. Port 5000 suggests custom application development ( HTTP service on non-standard port ), indicating a higher probability of implementation vulnerabilities than the hardened SSH daemons.

### Web page exploit:

The homepage served on port 5000 appeared completely static; buttons were non-functional, no dynamic content was visible, and no obvious navigation paths existed. This warranted directory enumeration using `gobuster` with the DirBuster wordlist from [SecLists](https://github.com/danielmiessler/SecLists/), which discovered a `/page` endpoint containing a text input field with greeting functionality.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758807031244/05872a9a-781a-4315-8a54-49a5db2d1888.png align="center")

The greeting mechanism suggested potential Server-Side Template Injection. Arithmetic evaluation (`{{7×7}}`→49) provides unambiguous confirmation. If the server returns ‘Hello 49!’, template processing occurred server side. String operations could reflect client-side JavaScript. Mathematical mutation offers binary clarity, either the template executed, or it didn’t.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>SSTI ( Server Side Template Injection ) </strong>occurs when web applications unsafely embed user input directly into template engines (Jinja2, Twig etc) allowing attackers to inject malicious template directives which execute as server side code. Read more <a target="_self" rel="noopener noreferrer nofollow" href="https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/07-Input_Validation_Testing/18-Testing_for_Server_Side_Template_Injection" style="pointer-events: none">here</a></div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758806696119/8d748efc-e89f-4cf2-be1c-43b9a3732479.png align="center")

As a standard practice, I tried to find out all the available classes using the payload `{{''.class.mro[1].subclasses()}}`. Effectively, this payload traverses to the base `object` class of the empty string using the MRO method, which can traverse the parent classes of a given class. The `subclasses()` method helps us enumerate the currently available modules, which may be used to set up a reverse-shell.

Upon running the payload, a long list of empty strings along with a singular class name was returned It looked something like `[, , , , , , , , ,…..,typing.Any, , , , …..]` In order to optimize the effort, I tried searching for the `_wrap_close` module, which helps in [gaining access to the OS module](https://agrohacksstuff.io/posts/os-commands-with-python/). The `_wrap_close` wraps file like objects, and critically its `__init__.__globals__` dictionary typically references to system modules like `sys, os` which are required for file operations. It is a reliable, and well documented path from the template context to OS-level execution. It is consistently available across Python versions.

The payload used was `{% for sc in ''.class.mro[1].subclasses() %}{% if` [`sc.name`](http://sc.name) `== '_wrap_close' %}{{ loop.index0 }}{% endif %}{% endfor %}`

Running this command is expected to return is the index of the `_wrap_close` module, and sure enough, the number 140 was returned.

The next step is trying to access the `os` module from this, so I first tried this payload: `{{ ''.class.mro[1].subclasses()[140].init.globals['os'].listdir() }}`. This payload effectively tried to access the `os` module via the `__globals__` directory of the class’ `__init__` method. `os.listdir()` is a basic function which lists the files in the current directory.

The direct `__globals__['os']` approach returned HTTP 500, indicating either namespace limitations or filtering. Since `__builtins__` provides Python's core import machinery and typically exists in all execution contexts, pivoting to dynamic import via `__import__` bypasses potential restrictions on pre-imported modules. I tried the payload `{{ ‘‘.__class__.__mro__[1].__subclasses__()[140].__init__.__globals__[‘__builtins__’][‘__import__’](‘os’).popen(‘ls -la’).read() }}`

The payload finally succeeded, and the output shows the contents of the filesystem of the `www-data` user, which is a common username used for service accounts used to serve web content over the server. One of the file, `F14@_0n3.txt` stands out in particular, and may reveal the value of the first flag. The app.py is probably the process which the application connects to, reading this could provide more vulnerabilities or logic flaws.

![Screenshot of the directory listing returned by the payload](https://cdn.hashnode.com/res/hashnode/image/upload/v1759742744923/57417c0d-5f22-4fc2-a6c3-19d3ecd06f6a.png align="center")

Slightly modifying the payload to read the `F14@_0n3.txt` file instead of listing the directory structure reveals the first flag to us.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759742979524/ffa60fd9-b58d-417b-ae56-5f2607d00946.png align="center")

The first flag secured, but `www-data` user access is merely a foothold, not privilege. The SSTI vulnerability that gave us file reading capabilities can offer something far more valuable: Arbitrary Command Execution. This is a common way of establishing presence.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Next in series: <strong>Terrier CTF [Part 2]: PCAP Dissection: Carving Secrets from Captured Packets</strong></div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Shout out to my friend, <a target="_self" rel="noopener noreferrer nofollow" href="https://www.linkedin.com/in/abhijeet-somashila/" style="pointer-events: none">Abhijeet</a> for the awesome cover image he designed!</div>
</div>
