
☣️ يحتوي هذا المستودع على الوصف وإثبات المفهوم لـ CVE-2024-34312
تم اكتشاف أن المختبر الافتراضي للبرمجة لـ Moodle حتى الإصدار v4.2.3 يحتوي على ثغرة Cross-Site Scripting (XSS) عبر مكوّن IDE.
يتصل المتصفح مباشرةً بويب سوكيت (websocket) يعمل على خادم العزل. عبر هذا الويب سوكيت، يمكن لخادم العزل إرسال رسائل مباشرةً إلى المتصفح. يتم تحليل هذه الرسائل بواسطة المتصفح ويتم التعامل معها عبر "executionActions". يكون إجراء "run:browser" عرضةً لـ XSS بسبب دمج HTML مع إدخال غير موثوق وحقنه في جسم الصفحة. يمكن استغلال ذلك من خلال خادم عزل مخترق للحصول على وصول إداري إلى مثيل Moodle عبر XSS.
يمكن للمهاجم الذي يتحكم في خادم العزل غير الموثوق، تثبيت خادم عزل خبيث يرسل حمولة خبيثة إلى أي مستخدم يستخدم المختبر الافتراضي للبرمجة، مما يؤدي إلى تشغيل XSS.
مثال على حمولة قد يبدو هكذا: run:browser:test\">test</a><script>alert(1)</script><a href=\"test\". سيؤدي هذا إلى تضمين <script>alert(1)</script> في DOM مما يتسبب في تنفيذ alert(1).

الكود القابل للاستغلال في vplide.js يفسّر هذا كأمر ويُلحق وسيطه الثاني (يتم فصل الوسائط بواسطة :) مباشرةً إلى DOM:
executionActions = {
// ...
'run': function(content, coninfo, ws) {
var parsed = /^([^:]*):?(.*)/i.exec(content);
var type = parsed[1];
if (type == 'terminal' || type == 'webterminal') {
// ...
} else if (type == 'vnc') {
// ...
} else if (type == "browser") {
var URL = (coninfo.secure ? "https" : "http") + "://" + coninfo.server + ":" + coninfo.portToUse + "/";
URL += parsed[2] + "/httpPassthrough";
if (isTeacher) {
URL += "?private";
}
var message = '<a href="' + URL + '" target="_blank">';
message += VPLUtil.str('open') + '</a>';
var options = {
width: 200,
icon: 'run',
title: VPLUtil.str('run'),
};
showMessage(message, options);
} else {
// ...
}
},
// ...
}
diff --git a/amd/src/vplide.js b/amd/src/vplide.js
index 586b5ff5..d1f88f47 100644
--- a/amd/src/vplide.js
+++ b/amd/src/vplide.js
@@ -2024,8 +2024,8 @@ define(
'setResult': self.setResult,
'ajaxurl': options.ajaxurl,
'run': function(content, coninfo, ws) {
- var parsed = /^([^:]*):?(.*)/i.exec(content);
- var type = parsed[1];
+ var parsed = /^([^:]*):?(.*)/.exec(content);
+ var type = VPLUtil.sanitizeText(parsed[1]);
if (type == 'terminal' || type == 'webterminal') {
if (lastConsole && lastConsole.isOpen()) {
lastConsole.close();
@@ -2055,7 +2055,7 @@ define(
});
} else if (type == "browser") {
var URL = (coninfo.secure ? "https" : "http") + "://" + coninfo.server + ":" + coninfo.portToUse + "/";
- URL += parsed[2] + "/httpPassthrough";
+ URL += VPLUtil.sanitizeText(parsed[2]) + "/httpPassthrough";
if (isTeacher) {
URL += "?private";
}
diff --git a/amd/src/vplui.js b/amd/src/vplui.js
index 36504a33..648472d6 100644
--- a/amd/src/vplui.js
+++ b/amd/src/vplui.js
@@ -582,8 +582,8 @@ define(
var messageActions = {
'message': function(content) {
var parsed = /^([^:]*):?([^]*)/.exec(content);
- var state = parsed[1];
- var detail = parsed[2];
+ var state = VPLUtil.sanitizeText(parsed[1]);
+ var detail = VPLUtil.sanitizeText(parsed[2]);
if (state == 'running') {
state = running;
}
@@ -607,7 +607,7 @@ define(
}
},
'retrieve': function() {
- var data = {"processid": VPLUtil.getProcessId()};
+ var data = {"processid": coninfo.processid};
pb.close();
delegated = true;
VPLUI.requestAction('retrieve', '', data, externalActions.ajaxurl)
@@ -627,7 +627,7 @@ define(
'close': function() {
VPLUtil.log('ws close message from jail');
ws.close();
- var data = {"processid": VPLUtil.getProcessId()};
+ var data = {"processid": coninfo.processid};
VPLUI.requestAction('cancel', '', data, externalActions.ajaxurl, true);
}
};