
☣️ Этот репозиторий содержит описание и доказательство концепции для CVE-2024-34312
Virtual Programming Lab для Moodle до версии 4.2.3 содержит уязвимость межсайтового скриптинга (XSS) через свой IDE-компонент.
Браузер напрямую подключается к websocket, работающему на jail-сервере. Через websocket jail-сервер может отправлять сообщения напрямую в браузер. Эти сообщения разбираются браузером и обрабатываются с помощью «executionActions». Действие «run:browser» уязвимо для XSS из-за конкатенации HTML с ненадёжным вводом и вставки его в тело страницы. Скомпрометированный jail-сервер может использовать это для получения административного доступа к экземпляру Moodle через XSS.
Злоумышленник, контролирующий ненадёжный jail-сервер, может установить вредоносный jail-сервер, который отправляет вредоносную нагрузку любому пользователю, использующему Virtual Programming Lab, вызывая 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);
}
};