Merhaba, "İleri Seviye Güvenli Kod İnceleme Metodolojisi" modülümüzün dördüncü dersine hoş geldiniz. Önceki derslerimizde, bir zafiyetin anatomisini anlamak için gereken temel analitik araçları, yani Veri Akış Analizi (DFA) ve Kontrol Akışı Analizi'ni (CFA) ele aldık. Bu teorik temeller, bize güvenilmeyen verinin program içindeki yolculuğunu ve programın mantıksal akışındaki kritik hataları nasıl tespit edeceğimizi öğretti.
Bu derste, teoriden pratiğe geçiş yapıyoruz. En yaygın ve yıkıcı zafiyet sınıflarından biri olan enjeksiyon (injection) zafiyetlerinin, farklı teknoloji yığınlarında nasıl göründüğünü inceleyeceğiz. Amacımız, bir enjeksiyon zafiyetinin Java, Python, JavaScript (Node.js) ve C# gibi farklı dillerdeki kod seviyesindeki tezahürlerini karşılaştırarak, zafiyet tanıma yeteneğinizi dil ve framework'ten bağımsız bir prensip düzeyine taşımaktır. Bu ders, farklı projelerde çalışan bir uygulama güvenliği uzmanı için hayati önem taşıyan "örüntü tanıma" (pattern recognition) becerisini geliştirmeye odaklanacaktır.
Enjeksiyonun Evrensel Prensibi: Dil Bağımsız Bir Bakış
Enjeksiyon zafiyetlerinin temelinde, ilk dersimizde ele aldığımız taint analysis yatar: Güvenilmeyen bir kaynaktan (source) gelen verinin, tehlikeli bir hedefe (sink) doğrulanmadan veya sanitize edilmeden ulaşması. Enjeksiyonun özü, uygulamanın bir parçası olan bir yorumlayıcının (interpreter) —örneğin bir SQL motoru, bir işletim sistemi kabuğu (shell) veya bir LDAP sunucusu— girdi olarak aldığı veriyi, yürütülecek bir komutun parçası olarak yanlış yorumlamasıdır. Veri, kod haline gelir.
Bu konuya dil bağımsız bir metodolojiyle yaklaşmak, farklı kod tabanlarında hızla etkinlik kazanmanızı sağlar. Bu yaklaşımın temel adımlarını anlamak için aşağıdaki makaleyi inceleyelim.
Language Agnostic Security Code Review
Flawed.net.nz'den Dean Jerkovich tarafından kaleme alınan bu yazı, herhangi bir programlama dilinde güvenlik incelemesi yapmak için genel bir strateji sunuyor. Özellikle source ve sink kavramlarını merkeze alan yaklaşımı, dersimizin ana fikrini mükemmel bir şekilde özetlemektedir.
Lütfen makaledeki "Sources & Sinks" başlığı altındaki bölümü okuyarak başlayın. Bu bölüm, zafiyetlerin büyük bir kısmının, saldırgan kontrolündeki verinin bir güven sınırını aşarak tehlikeli bir sink'te kullanılmasından kaynaklandığını vurgular. Ardından, "The Basic Workflow" bölümünü dikkatlice inceleyin. Bu iş akışı, güvenli kod incelemesinin temelini oluşturur: Hedef dildeki potansiyel olarak tehlikeli sink'lerin bir listesini oluşturun. grep veya benzeri bir araçla bu sink'lerin kod tabanındaki tüm örneklerini bulun. Her bir bulguyu analiz ederek, saldırganın bu sink'e giden veriyi etkileyip etkileyemeyeceğini belirlemek için geriye doğru iz sürün (taint analysis).
Bu iş akışı, bu derste izleyeceğimiz yaklaşımın da temelini oluşturmaktadır. Şimdi, bu metodolojiyi en yaygın enjeksiyon türlerinden ikisine, yani Komut Enjeksiyonu ve SQL Enjeksiyonu'na uygulayarak farklı dillerdeki sink'leri ve zafiyetli kod desenlerini karşılaştıralım.
Vaka Çalışması 1: İşletim Sistemi (OS) Komut Enjeksiyonu
OS Komut Enjeksiyonu, kullanıcı girdisinin doğrudan işletim sistemi kabuğunda çalıştırılan bir komuta dahil edilmesiyle oluşur. Bu, en basit ve en tehlikeli enjeksiyon türlerinden biridir çünkü başarılı bir saldırı, sunucu üzerinde tam kontrol sağlayabilir.
Bu zafiyetin temellerini, saldırı vektörlerini ve farklı tekniklerini (sonuç tabanlı, kör, zaman tabanlı) daha iyi anlamak için aşağıdaki videoyu izleyebilirsiniz.
Introduction to OS Command Injections - Full Course
Cybr kanalının hazırladığı bu video, OS Komut Enjeksiyonu'nun ne olduğunu, neden tehlikeli olduğunu ve çeşitli saldırı tekniklerini pratik örneklerle açıklıyor.
Lütfen şu bölümlere odaklanın: Giriş bölümü, OS Komut Enjeksiyonu'nun tanımını yapar ve bir uygulamanın shell komutlarına güvenli olmayan kullanıcı girdisi enjekte etmesiyle nasıl mümkün olduğunu açıklar. Teknikler bölümü, sonuç tabanlı (results-based) ve kör (blind) enjeksiyonlar arasındaki farkı, zaman gecikmesi (time delay) ve çıktı yönlendirme (output redirection) gibi kör enjeksiyon tekniklerini ve farklı işletim sistemleri için komut ayırıcılarını (&, ;, |) açıklar.
Şimdi, bu zafiyetin farklı dillerdeki kod seviyesindeki tezahürlerini karşılaştıralım. Tehlikeli sink fonksiyonları dilden dile değişse de, zafiyetli desen olan string birleştirme (concatenation) evrenseldir.
Komut Enjeksiyonu Sink'leri ve Zafiyetli Kod Örnekleri
| Dil | Tehlikeli Sink'ler (Örnek) | Zafiyetli Kod Deseni (Örnek) |
|---|---|---|
| Java | Runtime.getRuntime().exec() |
Runtime.getRuntime().exec("ping " + ipAddress); |
| Python | os.system(), subprocess.Popen(shell=True) |
os.system("ls " + directory) |
| JavaScript (Node.js) | child_process.exec() |
exec("cat " + filename, (err, stdout) => { ... }); |
| C# | Process.Start("cmd.exe", "/c " + command) |
Process.Start("cmd.exe", "/c ping " + ipAddress); |
Saldırı Senaryosu: Yukarıdaki tüm örneklerde, ipAddress veya filename gibi bir değişkene 8.8.8.8; whoami gibi bir değer atandığında, orijinal komutun ardından whoami komutu da çalıştırılacaktır.
Savunma Prensibi: Savunmanın temel prensibi, komut ile veriyi birbirinden ayırmaktır. Komut ve argümanları, tek bir string olarak birleştirmek yerine, bir dizi (array) veya liste olarak ilgili fonksiyona geçirmek, kabuğun (shell) bu argümanları meta karakterler olarak yorumlamasını engeller.
- Java:
new ProcessBuilder("ping", ipAddress).start(); - Python:
subprocess.run(["ls", directory]) - Node.js:
spawn("cat", [filename]) - C#:
Process.Start("ping.exe", ipAddress);
Gördüğünüz gibi, sözdizimi farklı olsa da, güvenli desen (ProcessBuilder, spawn, argüman listeleri) her dilde mevcuttur ve aynı prensibe dayanır.
Vaka Çalışması 2: SQL Enjeksiyonu (SQLi)
SQL Enjeksiyonu, belki de en bilinen enjeksiyon türüdür. Tıpkı komut enjeksiyonu gibi, temelinde kullanıcı girdisinin dinamik olarak oluşturulan bir SQL sorgusuna birleştirilmesi yatar. Sizin gibi deneyimli bir profesyonel için SQLi'nin ne olduğunu tekrarlamak yersiz olacaktır; bunun yerine doğrudan farklı diller ve ORM'lerdeki zafiyetli ve güvenli kod desenlerine odaklanalım.
Aşağıdaki kaynak, bu karşılaştırmayı yapmak için mükemmel örnekler içermektedir.
Secure Code Warrior'ın bu rehberi, SQL enjeksiyonunu popüler diller üzerinden somut kod örnekleriyle açıklıyor. Zafiyetli ve güvenli kodları yan yana göstermesi, desenleri karşılaştırmak için oldukça etkilidir.
Lütfen aşağıdaki diller için verilen "Vulnerable" ve "Remediation/Prevention/Secure" kod bloklarını inceleyin. Her birinde aynı temel hatanın (string birleştirme) ve aynı çözümün (parametreli sorgular) farklı sözdizimleriyle nasıl ifade edildiğine dikkat edin: Python Örneği: cur.execute("...'%s';" % name) (zafiyetli) ve cur.execute("...%s;", (name,)) (güvenli) arasındaki fark. Java Örneği: Statement ile string birleştirmenin tehlikesi ve PreparedStatement ile parametrelerin (?) nasıl güvenli bir şekilde bağlandığı. C# (Entity Framework) Örneği: FromRawSql ile FromSqlInterpolated arasındaki kritik fark. FromSqlInterpolated'ın string interpolasyon syntax'ını ($"") kullanmasına rağmen arka planda parametreleme yaptığını unutmayın. JavaScript (Node.js) Örnekleri: pg kütüphanesinde $1, $2 notasyonu ile parametrelerin nasıl geçtiği ve Sequelize ORM'indeki bind seçeneği.
Bu örneklerden çıkarılacak sonuç nettir: Hangi dil, kütüphane veya ORM kullanılırsa kullanılsın, dinamik SQL sorgularını string birleştirme yoluyla oluşturmak her zaman bir anti-pattern'dir. Güvenli alternatif, her zaman kütüphanenin sunduğu ve veriyi SQL komutundan ayırarak yorumlayıcıya gönderen parametreli sorgu (prepared statement) mekanizmasını kullanmaktır.
Sadece String Birleştirme Değil: Diğer Enjeksiyon Vektörleri
Enjeksiyon zafiyetleri her zaman basit string birleştirmeden kaynaklanmaz. Bazen, uygulamanın mantığı veya kullanılan teknolojinin özellikleri daha incelikli saldırılara olanak tanır.
NoSQL Enjeksiyonu
MongoDB gibi NoSQL veritabanları, SQL yerine JSON benzeri sorgu nesneleri kullanır. Eğer bir uygulama, kullanıcıdan gelen girdiyi doğrudan bu sorgu nesnesinin içine yerleştirirse, saldırganlar $ne (not equal), $gt (greater than) gibi operatörleri enjekte ederek sorgunun mantığını tamamen değiştirebilir.
Bu konsepti somut bir örnekle görelim.
JavaScript Security Vulnerabilities Tutorial – With Code Examples
freeCodeCamp.org kanalındaki bu video, JavaScript'te sıkça görülen güvenlik zafiyetlerini ele alıyor. NoSQL enjeksiyonu bölümü, konuyu çok net bir şekilde açıklıyor.
Lütfen NoSQL Injection bölümünü izleyin. Videoda, kullanıcı adını doğrulamak için rec.body.username değerinin doğrudan bir MongoDB sorgusuna nasıl yerleştirildiği gösteriliyor. Saldırgan, string bir kullanıcı adı yerine {"$ne": null} gibi bir JSON nesnesi göndererek, username alanı null olmayan tüm kullanıcıların verilerini nasıl çekebildiğine dikkat edin. Buradaki savunma, girdinin tipini kontrol etmektir (typeof ... === 'string'). Bu, SQLi'deki klasik sanitizasyondan farklı, daha çok tip ve yapı doğrulamasına dayalı bir savunmadır.
Komut Argümanı Enjeksiyonu (Command Argument Injection)
Bazen bir komutun kendisi sabit olsa bile, ona geçirilen argümanlar manipüle edilebilir. Özellikle escapeshellarg() gibi "güvenli" olduğu varsayılan fonksiyonların bile aşılabileceği durumlar mevcuttur.
Aşağıdaki PHP kod parçacığı, gerçek bir zafiyetten (Ivanti CSA - CVE-2024-22024) alınmıştır ve bu incelikli durumu göstermektedir.

Burada escapeshellarg() fonksiyonu, girdiyi tek tırnak içine alarak kabuk meta karakterlerini (|, &, ; vb.) etkisiz hale getirir ve komut enjeksiyonunu önlemeyi hedefler. Örneğin, değer; whoami girdisi 'değer; whoami' haline gelir ve tek bir argüman olarak işlem görür.
Ancak zafiyet, komutun kendisinin yorumlama biçimindedir. Saldırgan, TW_ID parametresi olarak --debug gibi tripwire programının kendisinin anladığı yeni bir seçenek (option) gönderirse, escapeshellarg bunu '--debug' olarak sarar. tripwire komutu, bu tırnak içindeki ifadeyi bir argüman olarak alır ve bazı durumlarda bu yeni seçenek, programın beklenmedik bir şekilde davranmasına ve potansiyel olarak başka zafiyetleri tetiklemesine yol açabilir. Bu, klasik komut enjeksiyonundan daha incelikli bir argüman enjeksiyonu türüdür ve bağlamın (context) ne kadar önemli olduğunu gösterir.
Özet ve Sonraki Adımlar
Bu derste, enjeksiyon zafiyetlerinin farklı dillerdeki kod seviyesindeki yansımalarını karşılaştırdık ve bu zafiyet sınıfının altında yatan evrensel prensipleri pekiştirdik.
- Evrensel Prensip: Enjeksiyon, güvenilmeyen verinin bir yorumlayıcı tarafından "kod" olarak algılanmasıdır. Bu,
source'tansink'e olan veri akışını takip ederek tespit edilir. - Dil Bağımsız Desen: Zafiyetli kod genellikle string birleştirme kullanırken, güvenli kod parametreli arayüzler (prepared statements, argüman listeleri) kullanarak veri ile komutu birbirinden ayırır.
- Komut Enjeksiyonu: Tehlikeli
sink'lerexec,system,Popen(shell=True)gibi fonksiyonlardır. Güvenli alternatif, argümanları bir liste olarak alanProcessBuilderveyaspawngibi yapılardır. - SQL Enjeksiyonu: Zafiyetli desen,
Statementve string birleştirmedir. Güvenli desen isePreparedStatementveya ORM'lerin parametre bağlama mekanizmalarıdır. - İleri Vektörler: NoSQL enjeksiyonunda olduğu gibi, saldırı her zaman string manipülasyonu olmayabilir; bazen de tip ve yapı manipülasyonu (örn: JSON nesnesi enjekte etmek) şeklinde olabilir. Komut argümanı enjeksiyonu ise, "güvenli" kaçış fonksiyonlarının bile bağlama göre yetersiz kalabileceğini gösteren daha incelikli bir örnektir.
Artık farklı dillerdeki enjeksiyon zafiyetlerini tanımak için sağlam bir zihinsel modele sahipsiniz. Bir sonraki dersimizde, yine bir enjeksiyon türü olan ancak kendine özgü dinamikleri bulunan Cross-Site Scripting (XSS) zafiyetine odaklanacağız. Farklı web framework'lerinin (Spring, Django, React, ASP.NET) XSS'e karşı sunduğu bağlama özgü (context-aware) savunma mekanizmalarını karşılaştırarak, çıktı güvenliğinin neden tek bir "escape" fonksiyonundan ibaret olmadığını derinlemesine inceleyeceğiz.