Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 48

Java Reflection ve Güvenlik Açıkları

Recommended resources

Harika bir önceki derste, Java'nın en ses getiren zafiyetlerinden biri olan JNDI enjeksiyonunu ve Log4Shell vakasını mercek altına aldık. Bir uygulamanın dışarıdan aldığı veriyi, JNDI lookup gibi tehlikeli bir API'ye göndererek nasıl uzaktan kod çalıştırabildiğini (RCE) kod seviyesinde analiz ettik. O derste, saldırganın kontrolündeki bir sunucudan tamamen yeni bir sınıfın (.class dosyası) çalışma zamanında yüklenip çalıştırılmasına tanık olduk.

Bu derste ise Java'nın çalışma zamanı (runtime) manipülasyon yeteneklerinin bir başka yüzünü, belki de daha temel ve güçlü olanını keşfedeceğiz: Reflection. Amacımız, Java'da reflection API'lerinin (örn: Class.forName) güvensiz kullanımını analiz ederek kod enjeksiyonu ve güvenlik mekanizmalarını atlatma yollarını bulmaktır. JNDI, uzaktan yeni kod getirmeye odaklanırken, Reflection mevcut kod tabanındaki sınıfları, metotları ve alanları çalışma zamanında dinamik olarak keşfetme, değiştirme ve çağırma gücü verir. Bu iki mekanizma, Java'da runtime manipülasyonunun temel taşlarıdır.

Reflection: Kendi Kendini İnceleyen Kod

Reflection, bir programın kendi yapısını (sınıflar, metotlar, arayüzler, alanlar) çalışma anında incelemesine ve değiştirmesine olanak tanıyan bir özelliktir. Bu, derleme zamanında bilinmeyen sınıflarla çalışmayı, metotları dinamik olarak çağırmayı ve hatta özel (private) üyelere erişmeyi mümkün kılar.

Bu yetenek, Spring gibi dependency injection framework'leri, Hibernate gibi ORM'ler ve Jackson gibi serileştirme kütüphaneleri için vazgeçilmezdir. Bu araçlar, konfigürasyon dosyalarına veya annotasyonlara bakarak hangi nesneleri yaratacaklarını, hangi metotları çağıracaklarını ve hangi alanlara değer atayacaklarını reflection kullanarak belirler.

Ancak bu büyük güç, büyük bir sorumluluk ve güvenlik riski de beraberinde getirir. Eğer bir saldırgan, reflection API'lerine giden girdileri (örneğin, yüklenecek sınıfın adı veya çağrılacak metodun adı) kontrol edebilirse, uygulamanın mantığını tamamen altüst edebilir.

Reflection'ın temelini oluşturan anahtar sınıflar şunlardır:

  • java.lang.Class: Bir sınıf veya arayüzün çalışma zamanındaki temsilidir.
  • java.lang.reflect.Method: Bir sınıfın metodunu temsil eder. .invoke() metodu ile dinamik olarak çağrılabilir.
  • java.lang.reflect.Constructor: Bir sınıfın kurucu metodunu (constructor) temsil eder. .newInstance() metodu ile yeni bir nesne örneği yaratılabilir.
  • java.lang.reflect.Field: Bir sınıfın alanını (field) temsil eder. Değerini okumak veya değiştirmek için kullanılabilir.

Güvenlik denetimi perspektifinden bakıldığında, en tehlikeli "sink" fonksiyonları şunlardır:

  • Class.forName(String className): Verilen string ismine sahip sınıfı yükler ve bir Class nesnesi döndürür. className saldırgan kontrolündeyse, keyfi sınıflar yüklenebilir.
  • Method.invoke(Object obj, Object... args): Belirli bir nesne üzerinde bu metodu, verilen argümanlarla çağırır. Method nesnesi veya args saldırgan kontrolündeyse, keyfi metotlar çağrılabilir.
  • Constructor.newInstance(Object... initargs): Verilen argümanlarla yeni bir nesne örneği oluşturur. Constructor nesnesi veya initargs saldırgan kontrolündeyse, keyfi nesneler yaratılabilir.

Derinlemesine Vaka Analizi: Güvensiz Deserialization ile Reflection İstismarı

Teoriyi pratiğe dökmek için, reflection'ın özel bir deserialization mekanizması üzerinden nasıl RCE'ye yol açtığını gösteren gerçek bir zafiyet analizini inceleyeceğiz. Bu analiz, Pwn2Own yarışmasında keşfedilen bir zafiyeti anlatıyor ve bir kod denetçisinin düşünce sürecini adım adım gösteriyor.

Aşağıdaki kaynak, Inductive Automation'ın Ignition platformundaki bir zafiyeti ele alıyor. Zafiyetin kökünde, uygulamanın XML verisini "deserialize" ederken reflection'ı güvensiz bir şekilde kullanması yatıyor.

How Two Hosts Abuse Automation Ignition Custom Deserialization

Bu makale, özel bir XML deserializer'ın reflection API'lerini nasıl güvensiz kullandığını ve bunun nasıl uzaktan kod çalıştırmaya (RCE) yol açtığını derinlemesine inceliyor. Bu bölümü okurken, uygulamanın bir XML dosyasını nasıl işlemeye başladığına, XMLDeserializer, ParseContext ve ObjectDeserializationHandler gibi sınıfların bu süreçteki rolüne odaklanın.

Makaleye başından itibaren göz atın ve data.bin Handling and Two Deserializers başlığı altındaki kod bloğunu inceleyin. Bu kısım, verinin formatına göre (isBinaryFormat) farklı deserialization yollarına saptığını gösterir. Biz XML yoluna odaklanacağız. "XMLDeserializer'ın İç İşleyişi" bölümünü okuyarak XMLParser ve ParseContext'in nasıl temel nesneler olduğunu ve XML etiketlerinin onElementStart ve onElementEnd olaylarını nasıl tetiklediğini anlayın. Son olarak, "Deserialization Handlers" bölümüne geçin. Burası çok önemli; zira ObjectDeserializationHandler adlı çok genel ve güçlü bir "handler"ın varlığını ve "o" etiketi ile tetiklendiğini göreceksiniz. Bu handler'ın Class clazz ve String methodName gibi şüpheli alanlara sahip olduğuna dikkat edin.

Okuduğunuz üzere, süreç bir XML dosyasının okunmasıyla başlıyor. Uygulama, SAX parser kullanarak XML'i işliyor ve etiketlere göre belirli "handler" sınıflarını çağırıyor. Analistin dikkatini çeken ObjectDeserializationHandler, çok genel amaçlı tasarlanmış ve bu da onu potansiyel bir zafiyet vektörü haline getiriyor. Saldırganın hazırladığı özel bir XML dosyası, bu handler'ı tetikleyerek zafiyeti istismar edebilir.

Şimdi zafiyetin kalbine inelim ve bu handler'ın reflection'ı nasıl kötüye kullandığını görelim.

How Two Hosts Abuse Automation Ignition Custom Deserialization

Bu bölümde, ObjectDeserializationHandler'ın startElement, startSubElement ve endSubElement metotlarının içine girerek reflection API'lerinin adım adım nasıl çağrıldığını göreceksiniz.

ObjectDeserializationHandler - startElement başlığı altındaki analizi inceleyin. Özellikle classForNameImpl metodu içinde Class.forName'ın nasıl çağrıldığına dikkat edin. Burası, XML'den gelen sınıf adının hiçbir doğrulama olmadan doğrudan bu tehlikeli metoda verildiği yerdir. Ardından, ObjectDeserializationHandler – endSubElement bölümüne odaklanın. Bu bölüm, zafiyetin en kritik mantığını içerir. Kodun <ctor> (constructor) etiketi için newInstance metodunu ve <c> (call) etiketi için invoke metodunu nasıl çağırdığını dikkatle inceleyin. Bu metotlara verilen argümanların da yine XML'den deserialize edildiğine dikkat edin.

Analizden de gördüğünüz gibi, bu özel deserializer, saldırgana neredeyse sınırsız bir reflection gücü veriyor:

  1. <o class='java.lang.ProcessBuilder'>: Saldırgan, class attribute'u ile istediği sınıfı (java.lang.ProcessBuilder gibi tehlikeli olanlar dahil) Class.forName ile yükletebilir.
  2. <ctor>: Saldırgan, bu etiket ile yüklediği sınıfın istediği public constructor'ını çağırabilir ve ona argümanlar geçebilir. Bu işlem, Constructor.newInstance() ile gerçekleştirilir.
  3. <c m='start'>: Saldırgan, yarattığı nesnenin istediği public metodunu (start gibi) çağırabilir. Bu işlem, Method.invoke() ile yapılır.

Bu üç adımı birleştirdiğimizde, tam teşekküllü bir RCE elde etmek mümkün hale gelir. Şimdi, bu adımları bir araya getiren son payload'u görelim.

How Two Hosts Abuse Automation Ignition Custom Deserialization

Bu son bölüm, önceki analizlerden elde edilen bilgileri birleştirerek tam bir RCE (Remote Code Execution) payload'unun nasıl oluşturulduğunu gösteriyor.

ObjectDeserializationHandler – Summary başlığı altındaki özeti okuyun. Bu paragraf, handler'ın sağladığı "sınırsız reflection yeteneklerini" özetliyor. Ardından, Malicious Serialized Object – RCE Payload başlığı altındaki örnek XML payload'ını inceleyin. Bu payload, java.lang.ProcessBuilder sınıfını kullanarak Windows'ta hesap makinesini (calc.exe) nasıl çalıştırdığını somut bir şekilde göstermektedir.

Bu vaka, kullanıcı kontrolündeki verinin (bu örnekte bir XML dosyası) reflection API'lerine ulaştığında ne kadar yıkıcı olabileceğinin mükemmel bir örneğidir.


Reflection ile Güvenlik Mekanizmalarını Atlatmak

Reflection'ın tehlikesi sadece RCE ile sınırlı değildir. Aynı zamanda, mevcut güvenlik kontrollerini, iş mantığını ve erişim denetimlerini atlatmak için de güçlü bir araçtır. Java'nın eski ama öğretici güvenlik mekanizması olan SecurityManager, tam da bu tür suistimalleri engellemek için tasarlanmıştır.

SecurityManager Devre Dışıysa, Saldırganın Yolu Açık - Medium

Bu makale, SecurityManager'ın rolünü ve devre dışı olduğunda ortaya çıkan riskleri açıklıyor. Özellikle reflection suistimaline karşı nasıl bir koruma sağladığını anlamak için bu kaynağı inceleyeceğiz.

Makalenin başındaki SecurityManager tanımını okuyarak bu mekanizmanın temel amacını anlayın. Ardından, SecurityManager Ne Tür Riskleri Engeller? başlığı altındaki listeye odaklanın. Özellikle "Reflection Abuse" maddesini dikkatle okuyun. Bu bölüm, setAccessible(true) ve invoke() gibi metotların SecurityManager tarafından nasıl denetlendiğini ve bu denetim olmadığında saldırganın neler yapabileceğini açıklıyor.

SecurityManager modern Java uygulamalarında varsayılan olarak aktif olmasa da, anlattığı prensipler geçerliliğini korur. Bir saldırgan, reflection kullanarak:

  • Özel Metotları Çağırabilir: Normalde erişilemeyen ve belki de güvenlik kontrollerini atlayan private metotları method.setAccessible(true) dedikten sonra invoke() ile çağırabilir.
  • Özel Alanları Değiştirebilir: private boolean isAdmin = false; gibi bir alanı field.setAccessible(true) dedikten sonra değerini true yapabilir ve yetki yükseltebilir.
  • Değişmez (Immutable) Nesneleri Değiştirebilir: Normalde değiştirilemez olan nesnelerin iç durumunu manipüle edebilir.
  • Güvenlik Kontrollerini Devre Dışı Bırakabilir: Uygulamanın güvenlik bileşenine bir referans elde edip onun "disable" veya "bypass" gibi bir metodunu çağırarak tüm güvenlik mekanizmasını etkisiz hale getirebilir.

Kod Denetimi Stratejileri

Java kod tabanında reflection suistimallerini ararken aşağıdaki stratejileri izleyebilirsiniz:

  1. Anahtar Kelime Araması: Kodda aşağıdaki tehlikeli metotları arayarak başlayın:

    • Class.forName(...)
    • .getMethod(...)
    • .getConstructor(...)
    • .invoke(...)
    • .newInstance(...)
    • .setAccessible(true)
  2. Veri Akışı Analizi (Taint Analysis): Bu metotları bulduğunuzda, onlara parametre olarak gelen verinin kaynağını takip edin.

    • Eğer sınıf veya metot adı kod içinde sabit (hardcoded) bir string ise genellikle güvenlidir.
    • Eğer bir konfigürasyon dosyasından geliyorsa, bu dosyanın kimler tarafından değiştirilebildiğini sorgulayın.
    • Eğer bir HTTP isteği, veritabanı kaydı, dosya içeriği veya başka bir kullanıcı kontrollü kaynaktan geliyorsa, bu potansiyel bir zafiyettir.
  3. Özel (Custom) Veri İşleyicilerine Odaklanın: Özellikle özel dosya formatlarını (XML, JSON, binary) işleyen, nesne yaratan veya metot çağıran kod bloklarına şüpheyle yaklaşın. Bu derste incelediğimiz vaka, özel deserialization rutinlerinin ne kadar tehlikeli olabileceğinin altını çizmektedir.

  4. Framework Kullanımını Anlayın: Spring gibi framework'ler yoğun olarak reflection kullanır. Genellikle framework'ün kendi kullanımı güvenlidir. Ancak, framework'ün sağladığı esnekliği kötüye kullanan (örneğin, HTTP isteğinden gelen bir sınıf adını alıp nesne yaratan) özel kodlar yazılmış olabilir.


Özet ve Sonraki Adımlar

Bu dersimizde, Java'nın güçlü ama tehlikeli Reflection API'sini ve kod denetimi perspektifinden nasıl analiz edilmesi gerektiğini öğrendik.

Anahtar Çıkarımlar:

  • Reflection, kodun çalışma zamanında kendi yapısını incelemesini ve değiştirmesini sağlar. Bu, dinamik ve esnek uygulamalar için gereklidir ancak güvenlik riskleri doğurur.
  • Class.forName(), Method.invoke() ve Constructor.newInstance() gibi metotlar, kullanıcı kontrollü verilerle çağrıldığında tehlikeli "sink"ler haline gelir.
  • Güvensiz reflection kullanımı, sadece RCE'ye değil, aynı zamanda private üyelere erişerek güvenlik kontrollerini ve iş mantığını atlatmaya da yol açabilir.
  • Kod denetiminde, reflection API'lerini aramak ve bu API'lere giden verinin kaynağını takip etmek (taint analysis) en etkili yöntemdir.

Önceki dersimizde JNDI ile uzaktan kod yüklemeyi, bu derste ise Reflection ile yerel kodu dinamik olarak çağırmayı gördük. Peki bu iki güçlü mekanizmanın altında yatan temel yapı taşı nedir? Cevap: ClassLoader. Bir sonraki dersimizde, Java ClassLoader hiyerarşisini ve dinamik sınıf yüklemeyi (örn: URLClassLoader) analiz ederek kod manipülasyonu risklerini değerlendireceğiz. Bu konu, Java'nın çalışma zamanı güvenliğinin en temel katmanına inmemizi sağlayacak.