
☣️ Questo repository contiene la descrizione e una prova di concetto per CVE-2024-34313
È stato scoperto che VPL Jail System fino alla v4.0.2 contiene una vulnerabilità di Path Traversal che consente la sovrascrittura arbitraria di file e, di conseguenza, l'escalation dei privilegi all'utente root.
Questa vulnerabilità può essere combinata con CVE-2024-34312 per prendere il controllo di un'istanza Moodle da remoto senza richiedere alcuna autenticazione preliminare.
Il jail server è un server C++ che esegue codice non fidato in un ambiente sandbox come utente non privilegiato. Il server rimane in ascolto delle connessioni in ingresso su una porta specificata e genera un nuovo processo per ogni connessione.
La funzione commandUpdate in jail.cpp riceve dal client una mappa dei file e dei loro contenuti.
ProcessMonitor::writeFile viene chiamata con il nome del file e il suo contenuto e si limita a concatenare la home directory dell'utente jail con il nome del file per ottenere il percorso completo.
Il file viene quindi scritto nel filesystem tramite Util::writeFile.
Ciò consente a un attaccante di scrivere file arbitrari nel filesystem tramite path traversal.
Questa vulnerabilità può essere utilizzata da un attaccante per sovrascrivere /etc/ld.so.preload con il percorso di un file oggetto condiviso che verrà caricato da ogni eseguibile dinamicamente linkato sul sistema, come spiegato qui. Questo file oggetto condiviso può poi essere utilizzato per eseguire codice arbitrario come root, ad esempio generando una reverse shell. Per facilitare l'exploit, il file oggetto condiviso viene caricato anche ogni volta che viene eseguito un binario setuid e la richiesta al server può includere uno script che tenterà di eseguire un binario setuid come sudo, garantendo che il file oggetto condiviso venga caricato immediatamente.
Nella configurazione predefinita del jail system, l'attaccante non ha bisogno di alcuna autenticazione.
Un esempio di payload per scrivere "hello-world.txt" nella root del sistema sarebbe:
{
"method": "request",
"params": {
"filestodelete": [],
"files": {
"../../../hello-world.txt": "Hello, world!"
},
"fileencoding": {
"../../../hello-world.txt": 0
},
"adminticket": "82350372182271",
"pluginversion": 2021061600,
},
"id": "3-32354-684945600",
}
L'adminticket può essere ottenuto facilmente inviando una richiesta non autenticata al jail system. Nonostante il nome, è solo una forma di token di sessione. I file binari codificati in Base64 possono essere caricati impostando fileencoding su 1.
Lo snippet di codice vulnerabile è riportato di seguito.
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");
}