
Exploit proof-of-concept per CVE-2025-49132 che consente l'esecuzione remota di codice non autenticata in Pterodactyl Panel <= 1.11.10 tramite iniezione del parametro locale. Include modalità di verifica e exploit.
Prova di concetto per CVE-2025-49132 per esecuzione di codice remoto in Pterodactyl Panel <= 1.11.10
Questa PoC prende riferimenti da
# Check if the web application is vulnerable
python3 poc.py -u <url> -m check
# Do something funny
python3 poc.py -u <url> -m exploit -p <path_to_pear_dir> -t <path_to_write_to>
Analizzando il git diff di LocaleController.php tra la v1.11.10 e la v1.11.11 (link), possiamo vedere che il controller accetta i parametri locale e namespace senza alcuna validazione o sanitizzazione.
Per capire perché questo è pericoloso, esamineremo il routing configurato in panel/routes/base.php. La route rimuove esplicitamente i middleware relativi all'autenticazione, consentendo a utenti non autenticati di chiamare /locales/locale.json e fornire i parametri di query richiesti come locale e namespace. Ciò significa che questi parametri sono ora controllati dall'utente.
Route::get('/locales/locale.json', Base\LocaleController::class)
// Remove authentication middleware for this endpoint
->withoutMiddleware(['auth', RequireTwoFactorAuthentication::class]);
Tornando a LocalController.php, la seguente riga di codice fornisce la magia dell'RCE.
$this->loader->load($locale, str_replace('.', '/', $namespace))
La funzione load proviene dal framework di traduzione di Laravel (qui):
public function load($locale, $group, $namespace = null) # 1
{
if ($group === '*' && $namespace === '*') { # 2
return $this->loadJsonPaths($locale);
}
if (is_null($namespace) || $namespace === '*') { # 3
return $this->loadPaths($this->paths, $locale, $group); # 4
}
return $this->loadNamespaced($locale, $group, $namespace);
}
load prende 3 argomenti. Sebbene possa creare confusione all'inizio, il valore str_replace('.', '/', $namespace) viene effettivamente passato al secondo argomento $group.$namespace non sarà mai * poiché sarà sempre NULL. Allo stesso modo $group non sarà mai * poiché possiamo controllare ciò che viene passato.$namespace è null, viene preso il ramo che porta all'esecuzione di loadPaths al #4.Compreso quanto sopra, passiamo ad analizzare loadPaths dove avviene la vera magia:
protected function loadPaths(array $paths, $locale, $group)
{
return (new Collection($paths))
->reduce(function ($output, $path) use ($locale, $group) {
if ($this->files->exists($full = "{$path}/{$locale}/{$group}.php")) { # 1
$output = array_replace_recursive($output, $this->files->getRequire($full)); # 2
}
return $output;
}, []);
}
.php.getRequire(), che esegue il file PHP e restituisce il suo array.Poiché controlliamo il parametro URL namespace, possiamo influenzare quale file l'applicazione tenta di caricare. Fornendo un valore che corrisponde a uno script .php, possiamo far eseguire al server tale script. Ad esempio, per caricare phpinfo.php, possiamo impostare il parametro a: namespace=phpinfo
La PoC tenta di eseguire una scrittura arbitraria di file tramite pearcmd.php. Questo metodo è stato utilizzato in molte PoC simili e ricerche sulla sicurezza.
Nota che pearcmd.php può trovarsi in diverse directory come
/usr/share/php/pearcmd.php/usr/share/php/PEAR/pearcmd.php/usr/lib/php/pearcmd.phpe molte altre.
Il file scritto conterrà il tipico codice PHP malevolo.
Solo per test autorizzati. Non utilizzare su sistemi che non possiedi o per i quali non hai esplicita autorizzazione.
Sei responsabile di rispettare tutte le leggi applicabili; gli autori non si assumono alcuna responsabilità per un uso improprio.
La libreria Python requests è un po' troppo disponibile perché codifica i caratteri nell'URL fornito. Ho passato molto tempo a cercare di risolvere questo problema e ho finito per usare direttamente curl. Apprezzerei se qualcuno potesse fornire aiuto per questo problema.