ReDoS: Düzenli İfade ile Hizmet Reddi Saldırısı ve Korunma
Tek bir kötü düzenli ifade (regex), özenle hazırlanmış kısa bir girdiyle sunucunun CPU'sunu kilitleyebilir. ReDoS, katastrofik geri-izleme yüzünden tek çekirdekli bir isteğin dakikalarca dönmesine yol açar. Zararlı regex kalıpları nasıl tanınır, hangi girdiler tetikler ve güvenli motor, zaman aşımı, girdi sınırı ile nasıl korunulur.
Hızlı cevap: ReDoS (Regular Expression Denial of Service), kötü tasarlanmış bir düzenli ifadenin (regex), özenle seçilmiş kısa bir girdiyle aşırı uzun süre çalışmasına yol açarak sunucunun işlemcisini tüketmesidir. Kök neden "katastrofik geri-izleme"dir (catastrophic backtracking): bazı regex motorları, iç içe ve belirsiz tekrar içeren kalıplarda (örneğin
(a+)+$) eşleşmeyi bulmak için girdinin uzunluğuyla üstel sayıda yol dener. 30-40 karakterlik bir girdi, tek bir isteği saniyeler veya dakikalar boyunca bir CPU çekirdeğine kilitleyebilir; birkaç eşzamanlı istek tüm sunucuyu durdurur. Çözüm, geri-izleme yapmayan doğrusal-zamanlı bir regex motoru (örneğin RE2) kullanmak, regex çalışmasına zaman aşımı koymak, girdi uzunluğunu sınırlamak ve tehlikeli kalıpları (iç içe kuantör, çakışan alternatif) ayıklamaktır.
ReDoS, enjeksiyon kadar görünür olmayan ama tek bir kötü satırla koca bir servisi diz çöktürebilen sessiz bir açıktır. En tehlikeli yanı, zararlı regex'in çoğu zaman tamamen masum görünmesi ve normal girdilerle hiç sorun çıkarmamasıdır; açık yalnızca saldırganın özenle hazırladığı girdiyle tetiklenir. Bu rehber katastrofik geri-izlemenin neden olduğunu, hangi kalıpların ve girdilerin tehlikeli olduğunu, nasıl tespit edildiğini ve kalıcı savunmayı anlatır.
Katastrofik geri-izleme neden olur
Çoğu geleneksel regex motoru "geri-izlemeli" (backtracking) çalışır: bir eşleşme denemesi başarısız olduğunda geri döner ve başka bir olasılığı dener. Çoğu kalıpta bu hızlıdır. Ama kalıpta iç içe veya çakışan belirsiz tekrarlar varsa, motorun denemesi gereken yol sayısı girdi uzunluğuyla üstel büyür.
Klasik örnek (a+)+$ kalıbıdır. Girdi aaaa...aaaX (sonunda eşleşmeyi bozan bir karakter) olduğunda, motor "a"ları gruplara nasıl böleceğine dair astronomik sayıda kombinasyonu dener, hepsi başarısız olur ve her biri için geri-izler. 20 "a" ile fark edilmez, 30 "a" ile saniyeler, 40 "a" ile dakikalar sürebilir. Girdi kısadır ama işlem süresi patlar. Buna üstel (exponential) ReDoS denir. İç içe olmayan ama çakışan alternatiflerde ((a|a)* gibi) de daha yumuşak ama yine tehlikeli bir büyüme görülür.
Önemli nokta: bu bir mantık hatası değil, motorun çalışma biçiminin doğal sonucudur. Kalıp yanlış değil, motorun o kalıp için yaptığı iş pahalıdır.
Hangi kalıplar ve girdiler tehlikeli
| Risk işareti | Örnek kalıp | Not |
|---|---|---|
| İç içe kuantör | (a+)+, (a*)* |
En tehlikelisi, üstel |
| Gruplu tekrar + sonek | (\d+)*$, (.*a){10} |
Bozucu karakterle patlar |
| Çakışan alternatif | (a|a)*, (|a)+ |
Belirsizlik üretir |
Açgözlü .* zincirleri |
(.*,){20} benzeri |
Uzun girdide yavaş |
| Kullanıcı girdisinden derlenen regex | kullanıcı regex'i sunar | Ayrıca enjeksiyon riski |
Tehlike çoğu zaman beklenmedik yerlerdedir: e-posta doğrulama regex'leri, URL ayrıştırıcılar, girdi temizleme kuralları, log ayrıştırma ve üçüncü-taraf kütüphanelerin içindeki regex'ler. Bir kütüphanenin eski bir sürümündeki tek bir zayıf regex, sizin kodunuz kusursuz olsa bile uygulamanızı ReDoS'a açabilir; bu yüzden bağımlılık güncelliği önemlidir ve yama yönetimi yazımızda ele aldığımız disiplinle örtüşür.
Nasıl tespit edilir
Tespitin iki yolu vardır. Statik yol, kod tabanındaki tüm regex'leri tarayıp iç içe kuantör ve çakışan alternatif gibi tehlikeli kalıpları işaretleyen araçlar kullanmaktır; bu, geliştirme sırasında en etkili yöntemdir. Dinamik yol ise bir uç noktaya artan uzunlukta kötü-amaçlı girdiler gönderip yanıt süresinin doğrusaldan hızlı sapıp sapmadığına bakmaktır: girdi uzunluğu arttıkça süre üstel büyüyorsa ReDoS doğrulanmış olur.
Dinamik testte dikkat: ReDoS doğrudan bir DoS saldırısıdır, bu yüzden yalnızca yetkili test ortamında ve kontrollü girdi uzunluklarıyla yapılmalıdır; amaç açığı kanıtlamaktır, hedefi gerçekten çökertmek değil. Hizmet reddi sınıfının genel savunmasını DDoS saldırısı ve korunma yazımızda ele aldık; ReDoS, tek bir kötü girdiyle gerçekleşen "uygulama katmanı" DoS'udur ve ağ hacmine ihtiyaç duymaz.
Kalıcı savunma: motor, zaman aşımı, girdi sınırı
ReDoS'un çözümü hem kalıbı düzeltmek hem de motoru ve girdiyi sınırlamaktır:
- Doğrusal-zamanlı motor kullanın. Geri-izleme yapmayan, girdi uzunluğuyla doğrusal çalışan bir regex motoru (örneğin RE2) ReDoS'u kökten bitirir, çünkü üstel patlama imkansız hale gelir. Mümkünse kritik yollarda bu tür bir motor tercih edin.
- Regex çalışmasına zaman aşımı koyun. Platformunuz destekliyorsa her regex eşleşmesine bir süre sınırı tanıyın; aşan işlem iptal edilsin. Böylece tek bir kötü girdi çekirdeği süresiz kilitleyemez.
- Girdi uzunluğunu sınırlayın. Regex'e giren veriyi her zaman makul bir üst sınırla kesin (örneğin e-posta için birkaç yüz karakter); üstel patlama için saldırganın belli bir uzunluğa ihtiyacı vardır ve kısa girdi riski küçültür.
- Tehlikeli kalıpları ayıklayın. İç içe kuantörleri (
(a+)+) ve çakışan alternatifleri yeniden yazın; çoğu durumda aynı amaç belirsizlik üretmeyen bir kalıpla ifade edilebilir. - Kullanıcıdan regex kabul etmeyin. Kullanıcının sağladığı regex'i sunucuda derleyip çalıştırmak hem ReDoS hem de mantık riski taşır; gerekiyorsa katı sınırlar ve güvenli motorla yapın.
- Bağımlılıkları güncel tutun. Kütüphanelerdeki bilinen ReDoS açıkları düzenli yamayla kapanır; eski sürüm bırakmayın.
KAOS ve DSET yaklaşımı
DSET olarak ReDoS'u uygulama katmanı hizmet-reddi testlerinin bir parçası sayarız, çünkü tek bir kötü regex, hacimsel bir DDoS'a gerek kalmadan servisinizi düşürebilir. Testlerimizde girdi-doğrulama ve arama uç noktalarına artan uzunlukta kötü-amaçlı girdiler göndererek yanıt süresinin üstel sapıp sapmadığını ölçer, açığı yalnızca tespit etmez, süre-uzunluk ilişkisini kanıtlayarak ve hangi regex'in sorumlu olduğunu göstererek doğrularız; testi her zaman kontrollü ve hedefi çökertmeyecek sınırlarda tutarız. Geliştirdiğimiz KAOS aracı, kod ve bağımlılıklardaki iç içe kuantör gibi tehlikeli regex kalıplarını işaretler, ama son kararı ölçülmüş davranışa bağlar. Uygulamanızı ReDoS ve uygulama katmanı DoS'una karşı test ettirmek için Ankara Hacettepe Teknokent'teki ekibimizle DSET ile iletişime geçin.
Sık sorulan sorular
ReDoS gerçek bir risk mi, yoksa teorik mi? Gerçek ve sahada istismar edilen bir risktir. Büyük kütüphanelerde ve popüler uygulamalarda tekrar tekrar ReDoS açıkları bulunmuş ve yamalanmıştır. Tek bir kötü regex, tek çekirdekli bir isteği dakikalarca meşgul edebildiği için birkaç eşzamanlı istekle tüm sunucu yanıt veremez hale gelir. Etkisi düşük maliyetli ama yüksek olduğundan saldırganlar için caziptir.
Her regex ReDoS'a açık mı? Hayır. Risk, iç içe veya çakışan belirsiz tekrarlar içeren kalıplarda ortaya çıkar; düz ve belirsizlik içermeyen regex'ler güvenlidir. Ayrıca doğrusal-zamanlı bir motor (RE2 gibi) kullanıyorsanız, kalıp ne olursa olsun üstel patlama olmaz. Risk, geri-izlemeli motor ile belirsiz kalıbın birleşmesinden doğar.
Girdi uzunluğunu sınırlamak tek başına yeterli mi? Riski azaltır ama tek başına garanti değildir, çünkü bazı kalıplar kısa girdilerle de ciddi yavaşlar ve "makul" sınır bile yeterince büyük olabilir. En sağlam çözüm katmanlıdır: doğrusal-zamanlı motor veya zaman aşımı birincil savunma, girdi sınırı ve tehlikeli kalıpların yeniden yazımı tamamlayıcıdır. Tek bir önleme güvenmeyin.
Kaynaklar
- OWASP, Regular Expression Denial of Service (ReDoS) açıklaması.
- CWE-1333 (inefficient regular expression complexity).
- Google RE2 doğrusal-zamanlı regex motoru ve katastrofik geri-izleme üzerine teknik kaynaklar.
- DSET blog: DDoS saldırısı ve korunma ve yama yönetimi.
Kimliğinizi doğrulayın
Yetkilendirilmiş erişim alanı. Tüm giriş denemeleri kayıt altına alınır.