September 4, 2026
Command Injection
Web Güvenliği Serisi — Bölüm 6

By Yusufbarut
15 min read
Bir web uygulaması, kullanıcıdan aldığı bir değeri arka planda işletim sistemine bir komut olarak teslim ediyorsa, bu özellik saldırganlar tarafından sunucunun tamamen ele geçirilmesine dönüştürülebilir. İşte tam bu noktada karşımıza Command Injection çıkar.
Serinin önceki bölümlerinde SQL Injection ile veritabanına, CSRF ile kullanıcının tarayıcısına, SSRF ile sunucunun kendi ağına duyulan yanlış güveni konuşmuştuk. Bu bölümde mesele daha da temele iniyor: artık kandırılan taraf bir veritabanı motoru ya da bir tarayıcı değil, doğrudan işletim sisteminin kendisi. Uygulama, farkında olmadan bir shell'e dönüşüyor ve saldırgan bu shell üzerinden sunucuyla doğrudan konuşmaya başlıyor.
Kulağa uzak bir senaryo gibi gelebilir ama pratikte Command Injection, hâlâ OWASP Top 10 kapsamındaki en yıkıcı zafiyet sınıflarından biri. Çünkü doğru koşullarda tek bir enjeksiyon, sadece "sunucu beklenmedik bir komut çalıştırdı" demek değil; sunucunun kaynak kodunun okunmasına, iç ağdaki başka sistemlere sıçranmasına, hatta bazı senaryolarda tüm altyapının ele geçirilmesine kadar gidebiliyor.
Bu yazıda Command Injection'ın ne olduğunu, nasıl çalıştığını, hangi türlerinin bulunduğunu, gerçek bir senaryo üzerinden nasıl istismar edildiğini, bir laboratuvar ortamında ve Burp Suite ile pratikte nasıl test edildiğini ve son olarak nasıl korunulacağını adım adım ele alacağız.
Hazırsanız başlayalım.
Sahne: Zararsız Görünen Bir Network Diagnostic Paneli
Orta ölçekli bir hosting şirketinin iç ağında, sistem yöneticilerinin kullanımı için yazılmış küçük bir "Network Diagnostic" paneli var. Amacı basit: bir IP adresi giriyorsunuz, panel arka planda ping komutunu çalıştırıyor ve sonucu ekrana basıyor. Yıllardır kimse bu sayfaya ikinci kez bakmamış. Ne bir güvenlik denetiminden geçmiş, ne de kodu yazan geliştirici hâlâ şirkette. Sayfa çalışıyor, işini görüyor, kimse dokunmuyor.
İç ağ değerlendirmesi yapan bir sızma testi uzmanı bu panele rastlıyor. Formda tek bir input alanı var: "Hedef IP adresini girin." Deniyor: 127.0.0.1. Sayfa birkaç saniye bekliyor, sonra klasik ping çıktısını basıyor:
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.045 msPING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.045 msHer şey normal görünüyor. Ama asıl soru burada başlıyor: bu uygulama, girilen metni gerçekten sadece bir "IP adresi" olarak mı işliyor, yoksa arka planda bu metni işletim sistemine mi teslim ediyor?
Test uzmanı, input kutusuna şunu yazıyor:
127.0.0.1 [AND operatörü] whoami127.0.0.1 [AND operatörü] whoamiEnter'a basıyor. Yanıt biraz daha uzun sürüyor ve çıktı geliyor:
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.041 ms
www-dataPING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.041 ms
www-dataO tek satır — www-data — aslında bir cümle kuruyor: "Bu uygulama, senin yazdığın metni bir komut olarak işletim sistemine teslim ediyor. Ve ben, işletim sisteminin bir parçasıyım artık." İşte zararsız görünen bu ping formu, Command Injection'ı anlamak için mükemmel bir başlangıç noktası.
Command Injection Nedir?
Command Injection, bir saldırganın, hedef web uygulamasının çalıştırdığı işletim sistemi komutlarına kendi komutunu enjekte edebilmesidir. Bunu anlamak için önce şunu sormak gerekiyor: bir web uygulaması, işletim sistemi komutlarını neden çalıştırır?
Cevap pratik: geliştiriciler, bazı işleri sıfırdan kod yazarak çözmek yerine işletim sisteminin zaten sunduğu araçları kullanmayı tercih eder. Bir dosyayı sıkıştırmak için zip, bir görüntüyü dönüştürmek için convert, bir ağ testi için ping ya da traceroute... Bunlar, işletim sisteminde hazır, test edilmiş, güvenilir araçlardır. Uygulama kodundan bu araçları çağırmak, aynı işi PHP, Python ya da Node.js içinde yeniden yazmaktan çok daha hızlıdır.
Bu çağrı, shell (/bin/sh, /bin/bash ya da Windows tarafında cmd.exe) üzerinden gerçekleşir. Uygulama "şu komutu çalıştır" dediğinde, bir shell process'i başlatılır ve verilen string bu shell'e teslim edilir. Shell de string'i yorumlar, komutu ayrıştırır ve işletim sistemine iletir.
Kritik nokta şu: shell, kendisine verilen string'in nereden geldiğini bilmez. Bu string, geliştiricinin sabit kodladığı bir komut mu, yoksa bir kullanıcının form üzerinden girdiği bir değer mi — shell için hiçbir fark yoktur. Shell sadece syntax'a bakar. AND operatörünü görürse "önceki komut bitince bunu da çalıştır" der, komut ayırıcı karakteri görürse "bunu da ayrı bir komut olarak çalıştır" der. Shell'in görevi yorumlamaktır, güven kontrolü yapmak değil.
Not:_ Command Injection'ı anlatırken en sık yapılan yanlış, bu zafiyeti bir "syntax hatası" olarak tanımlamaktır. Oysa Command Injection bir girdi-komut ayrımı sorunudur. Uygulama, kullanıcıdan gelen veriyi güvenilir bir komutun parçasıymış gibi shell'e teslim ettiği anda, kullanıcı artık sadece "veri" göndermiyor — potansiyel olarak yeni bir "komut" gönderiyor. Ve shell bu ikisini ayırt edemiyor._
Bu mantık, SQL Injection'la neredeyse birebir aynı. SQL Injection'da veritabanı motoru, sorgu ile veriyi ayırt edemediği için saldırgan kendi SQL komutunu araya sokabiliyordu. Command Injection'da da aynı sorun var, sadece motor işletim sistemi shell'i, sonuç ise çok daha derin: veritabanı yerine doğrudan sunucunun kendisi.
Command Injection ile SQL Injection Arasındaki Fark
İkisi de "girdi enjeksiyonu" ailesinden gelir ama istismar ettikleri katman ve sonuçları tamamen farklıdır.
Özellik SQL Injection Command Injection Hedef yorumlayıcı Veritabanı motoru İşletim sistemi shell'i İstismar edilen şey Sorgu ile veri ayrımının olmaması Komut ile veri ayrımının olmaması Ana etki Veri okuma/değiştirme/silme Dosya sistemi, ağ, işletim sistemi seviyesinde kontrol Tipik payload [OR koşulu daima doğru] [AND operatörü] whoami, [komut ayırıcı] [dosya oku] /etc/passwd Ana savunma Parametreli sorgular (prepared statements) Shell'den kaçınma, allowlist doğrulama
SQL Injection'da saldırgan veritabanıyla konuşurken, Command Injection'da doğrudan sunucunun işletim sistemiyle konuşur — bu da etki alanını genellikle çok daha geniş kılar.
Command Injection Nasıl Çalışır?
Mekanizmayı, az önceki ping senaryosu üzerinden adım adım izleyelim. Panelin arkasındaki PHP kodu şuna benziyor olabilir:
<?php
$ip = $_POST['ip'];
$output = system("ping -c 4 " . $ip);
echo $output;
?><?php
$ip = $_POST['ip'];
$output = system("ping -c 4 " . $ip);
echo $output;
?>Bu kod ilk bakışta masum görünüyor. system() fonksiyonu, verilen string'i doğrudan sistem shell'ine iletir; "bu string'in içinde sadece bir IP adresi olmalı" diye bir varsayımda bulunmaz.
Normal akışta işler şöyle yürür:
Kullanıcı → Web Uygulaması → Shell → İşletim Sistemi → ÇalıştırmaKullanıcı → Web Uygulaması → Shell → İşletim Sistemi → ÇalıştırmaKullanıcı 127.0.0.1 yazdığında, uygulama ping -c 4 127.0.0.1 string'ini üretir, shell bunu okur ve ping binary'sini bu argümanlarla çalıştırır. Sorun yok — ta ki kullanıcı yerine bir saldırgan bu alanı kontrol edene kadar.
Saldırgan → Web Uygulaması → Shell (komut + enjekte edilen komut) → İşletim SistemiSaldırgan → Web Uygulaması → Shell (komut + enjekte edilen komut) → İşletim SistemiSaldırgan 127.0.0.1 [AND operatörü] whoami yazdığında, uygulama şu string'i üretir:
ping -c 4 127.0.0.1 [AND operatörü] whoamiping -c 4 127.0.0.1 [AND operatörü] whoamiShell bu string'i şöyle okur: "İlk komut ping -c 4 127.0.0.1. Bu komut başarıyla biterse (AND operatörü bunu ifade ediyor), ikinci komutu da çalıştır: whoami." Shell açısından burada hiçbir anormallik yok — iki ayrı komut, aralarında sıradan bir operatör. Shell'in bilmediği tek şey, bu iki komuttan birinin geliştirici tarafından yazıldığı, diğerinin bir saldırgan tarafından enjekte edildiği. Shell için ikisi de eşit derecede meşru.
whoami çıktısı olan www-data, saldırgana uygulamanın hangi ayrıcalık seviyesinde çalıştığını gösterir. Bu genellikle sınırlı bir servis hesabıdır ama "sınırlı" olması "zararsız" olduğu anlamına gelmez — saldırgan artık bu hesabın hangi dizinlere yazabildiğini, uygulamanın kaynak kodunu (ve muhtemelen içindeki veritabanı bağlantı bilgilerini) okuyup okuyamadığını, iç ağda başka hangi servislere erişebildiğini araştırmaya başlayabilir.
Command Injection Türleri
Command Injection tek bir kalıba sığmaz. Komutun çıktısının nasıl geri döndüğüne göre dört ana türe ayrılır.
Tür Çıktı Görünür mü? Tespit Sinyali Tespit Zorluğu Basic (In-Band) Evet Komut çıktısı doğrudan yanıtta Kolay Blind Hayır Yanıtta değişim yok, sinyal yok Orta Time-Based Hayır Yanıt süresindeki gecikme Orta-Zor Out-of-Band (OOB) Hayır DNS/HTTP isteği başka bir kanalda Zor
Basic (In-Band) Command Injection, tam olarak ping örneğimizdeki gibi işler; enjekte edilen komutun çıktısı uygulamanın normal yanıtı içinde doğrudan görünür.
Blind Command Injection, uygulamanın komut çıktısını hiçbir şekilde göstermediği durumlarda ortaya çıkar. Bu, zafiyetin olmadığı anlamına gelmez — sadece görünürlüğün olmadığı anlamına gelir.
Time-Based Command Injection, blind senaryonun bir çözümüdür. Saldırgan çıktıyı göremese bile, uygulamanın davranışını gözlemleyebilir; zamanlama, gözlemlenebilir en güvenilir davranışlardan biridir.
Out-of-Band (OOB) Command Injection, en sofistike senaryodur. Hiçbir görünür geri bildirim yoksa, saldırgan üçüncü bir kanal üzerinden — genellikle DNS veya HTTP üzerinden — kendine bir sinyal gönderilmesini sağlar.
Blind Command Injection: Sessizlik İçinde Bir Kanıt
Diyelim ki güvenlik ekibi ping panelini "iyileştirmiş" ve artık system() çağrısının çıktısını kullanıcıya göstermiyor. Sayfa her zaman aynı mesajı döndürüyor: "Diagnostic tamamlandı."
$ip = $_POST['ip'];
system("ping -c 4 " . $ip . " > /dev/null 2>&1");
echo "Diagnostic tamamlandı.";$ip = $_POST['ip'];
system("ping -c 4 " . $ip . " > /dev/null 2>&1");
echo "Diagnostic tamamlandı.";Kod çıktıyı gizliyor ama komutu çalıştırmaya devam ediyor. Test uzmanı burada farklı bir soru sorar: "Çıktıyı göremiyorsam, komutun çalıştığını başka nasıl kanıtlarım?" Cevap zamanlamada saklı:
127.0.0.1 [AND operatörü] [gecikme komutu] 10127.0.0.1 [AND operatörü] [gecikme komutu] 10Normalde form birkaç yüz milisaniyede yanıt verir. Bu istekte sayfa tam olarak 10 saniye sonra yanıt döner. Bu gecikme rastgele bir ağ yavaşlığı değildir çünkü tekrarlanabilir: gecikme komutu 5 olarak yazılırsa yanıt 5 saniyede, 15 olarak yazılırsa 15 saniyede gelir. Gecikme, verilen parametreyle birebir orantılıdır — bu orantı tesadüfle açıklanamaz çünkü ağ gecikmeleri rastgeledir, gecikme komutunun süresi ise deterministiktir.
Neden İşe Yarar:_ Çıktıyı gizlemek, komutun çalışmasını engellemez — sadece saldırganın gözlem yöntemini değiştirir. Zaman, ölçülebilir ve inkâr edilemez olduğu için bu iş için ideal bir sinyaldir._
Out-of-Band: Ağın Dışına Bir Sinyal Göndermek
Peki ya gecikme tabanlı komutlar da bir şekilde engelleniyorsa, ya da ağ koşulları zamanlamayı güvenilmez kılıyorsa? Bu noktada saldırganlar tamamen farklı bir kanala yönelir: uygulamanın kendisinden değil, başka bir sistemden gelecek bir sinyale.
Eğer sunucu internete çıkış yapabiliyorsa, saldırgan sunucuya "git, kontrolündeki bir adrese bir DNS sorgusu gönder" diyebilir:
127.0.0.1 [AND operatörü] [DNS sorgu komutu] [test-domain]127.0.0.1 [AND operatörü] [DNS sorgu komutu] [test-domain]Bu komut çalıştığında, hedef sunucu saldırganın kontrolündeki DNS sunucusuna bir sorgu gönderir. Saldırgan kendi DNS sunucusunun loglarına bakar ve bu sorgunun geldiğini görür. Bu sorgunun varlığı, tek başına, komutun sunucu üzerinde gerçekten çalıştığının kanıtıdır — çünkü DNS sorgusu, uygulamanın HTTP yanıtından tamamen bağımsız, ayrı bir kanaldır.
Profesyonel sızma testlerinde bu amaçla kullanılan araçlardan biri Burp Suite'in Collaborator özelliğidir. Test uzmanı, hedef sisteme özel, benzersiz bir alan adı üretir ve enjekte ettiği komutun bu alan adına bir istek göndermesini sağlar. O benzersiz alan adına bir DNS sorgusu ya da HTTP isteği gelirse, bu başka hiçbir açıklaması olmayan kesin bir kanıttır.
Not:_ Out-of-Band tekniğinin gücü şurada: uygulamanın kendisi hiçbir bilgi sızdırmasa bile, işletim sistemi seviyesinde çalıştırılan bir komut, ağ üzerinden bağımsız bir iz bırakabilir. Bu, saldırganın "görünmez" bir zafiyeti bile kanıtlayabilmesinin sebebidir._
Hangi Fonksiyonlar Bu Kapıyı Açıyor?
Command Injection, dile özgü bir sorun değil — herhangi bir dilde, uygulama kodu ile işletim sistemi shell'i arasında bir köprü kurulduğu her yerde ortaya çıkabilir. Önemli olan fonksiyonun ismi değil, kullanıcıdan gelen veriyi yorumlanmak üzere bir shell process'ine teslim edip etmediği.
PHP: system(), exec(), shell_exec(), passthru() ve backtick operatörü (komut) — hepsi verilen string'i shell'e gönderir.
Python: Klasik risk os.system() ve subprocess modülünün shell=True parametresiyle kullanılmasıdır. subprocess.run("ping " + ip, shell=True) yazıldığında Python arka planda bir shell process'i başlatır ve string'i ona teslim eder.
Java: Runtime.exec() veya ProcessBuilder, komut tek bir string olarak ve shell üzerinden (/bin/sh -c ile) çalıştırılacak şekilde kurgulanmışsa aynı riski taşır.
Node.js: child_process modülünün exec() fonksiyonu, verilen komut string'ini bir shell üzerinden çalıştırır. Aynı modülün execFile() fonksiyonu ise komut ve argümanları ayrı ayrı alır, shell yorumlaması devreye girmez.
Neden Önemli:_ Tehlike, fonksiyonun isminde değil, fonksiyonun kullanıcı girdisini bir shell'e "yorumlanmak üzere" teslim edip etmediğinde yatıyor. Bir fonksiyon argümanları ayrı ayrı, shell'e uğramadan doğrudan işletim sistemine iletiyorsa, injection riski büyük ölçüde ortadan kalkar._
Linux ve Windows: Aynı Fikir, Farklı Shell Syntax'ı
Command Injection'ın nasıl çalıştığını anlamak, aslında shell'in syntax'ını anlamakla eşdeğerdir. Linux ile Windows arasındaki fark payload'larda değil, shell'lerin kendisinde.
Operatör Linux (bash/sh) Windows (cmd.exe) Anlamı Sıralı çalıştırma komut ayırıcı (;) & Önceki sonuçtan bağımsız, sırayla çalıştır Başarılıysa çalıştır AND operatörü AND operatörü Önceki komut başarılıysa devam et Başarısızsa çalıştır OR operatörü OR operatörü Önceki komut başarısızsa devam et Boru hattı pipe operatörü pipe operatörü Bir komutun çıktısını diğerine bağla Komut ikamesi backtick / $() %komut% (sınırlı) Bir komutun çıktısını başka bir komutun içine göm
Bu farkın önemi şurada: bir saldırgan, hedef sistemin işletim sistemini bilmeden doğru payload'ı seçemez. Bu yüzden gerçek dünyada test uzmanları, hedefin işletim sistemini önce (HTTP başlıkları, hata mesajları, davranışsal ipuçları üzerinden) tahmin etmeye çalışır, sonra buna uygun syntax'ı seçer.
Not:_ Bu operatörlerin hiçbiri "hacking'e özel" karakterler değildir. Hepsi shell'in normal, meşru syntax'ının bir parçasıdır. Command Injection'ı mümkün kılan şey operatörlerin kötü olması değil, uygulamanın bu operatörleri filtrelemeden kullanıcı girdisinin bir parçası olarak shell'e taşımasıdır._
Laboratuvar Ortamında Gösterim
Aşağıdaki bölüm, yalnızca yasal ve izole bir laboratuvar ortamı olan DVWA (Damn Vulnerable Web Application) üzerinde, eğitim amacıyla gerçekleştirilmiştir. Burada anlatılan teknikler hiçbir şekilde izinsiz sistemler üzerinde denenmemelidir; Command Injection de dahil olmak üzere herhangi bir zafiyetin, sahibinden yazılı izin alınmadan üçüncü taraf sistemlerde test edilmesi yasadışıdır ve ciddi hukuki sonuçlar doğurabilir.
Ortam Hazırlığı
DVWA, güvenlik seviyesi ayarlanabilen, kasıtlı olarak zafiyetli bırakılmış bir PHP/MySQL uygulamasıdır. Güvenlik seviyesini "Low" olarak ayarladığımızda, girdi doğrulaması tamamen devre dışı bırakılmış olur; bu da zafiyetin temel mekanizmasını net biçimde gözlemlememizi sağlar.
Adım 1 — İsteğin Yakalanması (Intercept Request)
DVWA'nın "Command Injection" modülünde, kullanıcıdan bir IP adresi alıp ping sonucunu döndüren bir form bulunur. Burp Suite proxy'si üzerinden formu doldurup gönderdiğimizde, Proxy > HTTP History sekmesinde şu isteği görürüz:
POST /vulnerabilities/exec/ HTTP/1.1
Host: dvwa.local
Cookie: PHPSESSID=abc123def456; security=low
ip=127.0.0.1&Submit=SubmitPOST /vulnerabilities/exec/ HTTP/1.1
Host: dvwa.local
Cookie: PHPSESSID=abc123def456; security=low
ip=127.0.0.1&Submit=SubmitAdım 2 — Payload'ın Repeater'da Test Edilmesi
İsteği Burp Repeater'a gönderip ip parametresini değiştiriyoruz:
ip=127.0.0.1 [URL-encoded AND operatörü] whoami&Submit=Submitip=127.0.0.1 [URL-encoded AND operatörü] whoami&Submit=SubmitAdım 3 — Yanıtın Gözlemlenmesi
Sunucudan dönen yanıtta, normal ping çıktısının altında beklenmedik bir satır belirir:
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.032 ms
www-dataPING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.032 ms
www-dataAdım 4 — Blind Senaryonun Simülasyonu
DVWA'nın "Medium" seviyesinde bazı karakterler filtrelenmiş olsa da (örneğin AND operatörü engellenmiş, komut ayırıcı engellenmemiş olabilir), çıktı yine de doğrudan gösterildiği için bu hâlâ in-band bir senaryodur. Blind davranışı gözlemlemek için, çıktıyı bastıran bir payload deneriz:
ip=127.0.0.1 [komut ayırıcı] [gecikme komutu] 10&Submit=Submitip=127.0.0.1 [komut ayırıcı] [gecikme komutu] 10&Submit=SubmitYanıt süresi tutarlı biçimde ~10 saniyeye çıkar; bu, komutun sunucu tarafında gerçekten çalıştığının zamanlama bazlı kanıtıdır.
Önemli Not:_ Bu gösterim yalnızca Command Injection mekanizmasının nasıl çalıştığını somutlaştırmak amacıyla, izole ve kişisel bir laboratuvar ortamında yapılmıştır. DVWA, bWAPP, OWASP Juice Shop ve Mutillidae gibi platformlar tam olarak bu tür eğitim amaçlı denemeler için tasarlanmıştır ve gerçek sistemlere hiçbir zaman izinsiz saldırı düzenlenmemelidir._
Burp Suite İş Akışı
Sızma testi uzmanları, Command Injection zafiyetlerini tespit ve doğrulama sürecinde genellikle Burp Suite'i kullanır. Aşağıda izlenen adımlar özetlenmiştir.
1. Şüpheli Endpoint'lerin Belirlenmesi
Proxy History üzerinde, işlevi itibarıyla işletim sistemiyle etkileşime girme ihtimali yüksek endpoint'ler işaretlenir: ping/traceroute araçları, DNS lookup formları, dosya dönüştürme veya sıkıştırma özellikleri, sistem durumu panelleri, yedekleme tetikleyicileri.
2. Zararsız Doğrulama
İlk denemede, uygulamanın normal işlevselliğini bozmayacak zararsız bir operatör eklenir: 127.0.0.1 [komut ayırıcı] [echo test] test123. Yanıt içinde test123 string'i beklenmedik şekilde beliriyorsa, bu güçlü bir sinyaldir — çünkü bu echo komutu uygulamanın normal işlevselliğinin parçası olamaz.
3. Repeater ile Zamanlama Testi
Yanıtta değişiklik gözlenmiyorsa, sırasıyla farklı gecikme değerleriyle (Windows'ta timeout komutu ile) istekler gönderilir ve yanıt süreleri kıyaslanır. Gecikme komutu 5 saniye olarak yazıldığında yanıt tutarlı biçimde ~5 saniye gecikiyorsa, bu tesadüf olamayacak kadar deterministik bir örüntüdür.
4. Collaborator ile Blind/OOB Doğrulama
Burp Collaborator, benzersiz bir domain üretir. Bu domain, DNS sorgu komutu gibi bir komutun parametresi olarak enjekte edilir:
127.0.0.1 [AND operatörü] [DNS sorgu komutu] [benzersiz-test-domain]127.0.0.1 [AND operatörü] [DNS sorgu komutu] [benzersiz-test-domain]Collaborator sekmesinde bir DNS isteği düşerse, sunucunun komutu gerçekten çalıştırdığı — yanıt hiçbir şey göstermese bile — kanıtlanmış olur.
5. Bulgunun Doğrulanması ve Raporlanması
Zafiyet doğrulandıktan sonra, hangi parametrenin, hangi HTTP metoduyla, hangi payload ile tetiklendiği ve elde edilen kanıt (yanıt içeriği, zamanlama farkı veya Collaborator logu) rapora eklenir. Command Injection bulgularında, etkiyi abartmadan ama küçümsemeden, "hangi kullanıcı bağlamında" (whoami çıktısı) komut çalıştırılabildiği açıkça belirtilir.
Command Injection Nasıl Tespit Edilir?
Manuel Test
Manuel testte odaklanılması gereken temel sorular şunlardır:
- Endpoint, işlevi gereği bir sistem aracını (ping, traceroute, convert, zip vb.) çağırıyor olabilir mi?
- Girdi, herhangi bir shell operatörü (AND operatörü, komut ayırıcı, pipe, backtick, $()) içerdiğinde uygulamanın davranışı değişiyor mu?
- Çıktı doğrudan görünüyor mu, yoksa gizleniyor mu?
- Çıktı gizliyse, zamanlama tabanlı bir enjeksiyon (gecikme komutu) davranışı tetikliyor mu?
- Girdi doğrulaması bir denylist mi (belirli karakterleri engelleme), yoksa allowlist mi (sadece belirli formatlara izin verme)?
Girdi Formatının İncelenmesi
Beklenen girdi formatı (IP adresi, dosya adı, hostname) ile uygulamanın gerçekte kabul ettiği karakter kümesi karşılaştırılmalıdır. Uygulama "IP adresi bekliyorum" diyor ama boşluk, noktalı virgül, ampersand gibi karakterleri reddetmiyorsa, bu ciddi bir gösterge kabul edilir.
Otomatik Araçlar
Burp Suite'in aktif tarayıcısı (Pro sürümde) bazı OS command injection göstergelerini otomatik olarak işaretleyebilir; zamanlama tabanlı ve Collaborator tabanlı doğrulama teknikleriyle bulguları destekler. OWASP ZAP'in aktif tarama kuralları arasında da "Command Injection" kontrolleri bulunur ve bunlar hem zaman tabanlı hem de OOB tabanlı testler içerir.
Not:_ Otomatik araçlar, zamanlama tabanlı sinyalleri ağ gecikmesinden ayırt etmekte bazen hataya düşebilir. Bu yüzden zamanlama tabanlı bir bulgu, birden fazla tekrar ve farklı gecikme değerleriyle manuel olarak da doğrulanmalıdır._
Korunma Yöntemleri
Command Injection'a karşı savunma, tek bir kontrolle değil, katmanlı bir yaklaşımla sağlanır.
🛡 1. Allowlist kullanın, denylist değil. "Şu karakterleri kullanma" demek yerine "sadece şu karakterlere izin var" demek çok daha güvenlidir. Örneğin bir IP adresi alanı için ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ gibi bir regex, AND operatörü, komut ayırıcı, pipe gibi hiçbir shell operatörünü zaten karakter kümesinin dışında bırakır.
Neden İşe Yarar:_ Denylist'ler doğası gereği eksik kalmaya mahkumdur; geliştirici on karakteri engellese bile on birincisini unutabilir. Allowlist ise "beklenen veri neye benziyor" sorusuna cevap verir ve varsayılan olarak reddeder._
🛡 2. Shell'i tamamen devre dışı bırakın. Komut ve argümanları, tek bir birleştirilmiş string olarak değil, ayrı ayrı elemanlar olarak işletim sistemine iletin: Node.js'te exec() yerine execFile(), Python'da shell=True yazmadan subprocess.run(["ping", "-c", "4", ip]), PHP'de escapeshellarg() ile argümanları tek tek işlemek.
# Güvensiz
subprocess.run("ping -c 4 " + ip, shell=True)
# Güvenli
subprocess.run(["ping", "-c", "4", ip])# Güvensiz
subprocess.run("ping -c 4 " + ip, shell=True)
# Güvenli
subprocess.run(["ping", "-c", "4", ip])Neden İşe Yarar:
subprocess.run(["ping", "-c", "4", ip])yazıldığında,ipdeğişkeninin içeriği ne olursa olsun, bu değer ping komutunun tek bir argümanı olarak iletilir. Shell hiç devreye girmediği için AND operatörü, komut ayırıcı gibi karakterler "yorumlanacak syntax" değil, sadece düz metin olarak ele alınır.
🛡 3. En az yetki prensibini (Least Privilege) uygulayın. Web uygulamasının çalıştığı process (www-data gibi), sistem üzerinde sadece işini yapmak için gereken minimum yetkiye sahip olmalıdır.
Neden İşe Yarar:_ Bu prensip zafiyeti ortadan kaldırmaz ama etkisini sınırlar. Root olarak çalışan bir web sunucusunda bir Command Injection, saldırgana doğrudan sistemin tamamını verir; sınırlı bir kullanıcı hesabıyla çalışan bir sunucuda ise saldırganın ulaşabileceği alan önemli ölçüde daralır._
🛡 4. Konteynerleştirme (Containerization) kullanın. Uygulamayı, host sistemden izole edilmiş bir konteyner içinde çalıştırın; host'un dosya sistemine, ağına ve diğer konteynerlere erişimi kısıtlayın.
Neden İşe Yarar:_ Doğru yapılandırılmış bir konteyner, olası bir sızmanın patlama yarıçapını (blast radius) ciddi şekilde küçültür. Saldırganın "www-data olarak komut çalıştırabilme" yeteneğini "tüm altyapıyı ele geçirme" seviyesine taşımasını zorlaştırır._
🛡 5. Sistem çağrılarını loglayın ve anormallikleri izleyin. www-data gibi bir servis hesabının normalde çalıştırmadığı komutları (whoami, curl, wget gibi) tetiklemesi, auditd, EDR çözümleri veya SIEM kuralları ile gerçek zamanlı olarak yakalanabilir.
Neden İşe Yarar:_ Önleme her zaman başarısız olabilir; iyi bir loglama stratejisi, önleme başarısız olduğunda bile saldırının erken aşamada fark edilmesini sağlar ve saldırganın sistemde kalış süresini (dwell time) kısaltır._
Geliştiricilerin Sık Yaptığı Hatalar
Command Injection zafiyetlerinin gerçek dünyada hâlâ sıkça karşımıza çıkmasının arkasında, tekrar eden birkaç geliştirici hatası yatar.
- Kullanıcı girdisini doğrudan string birleştirmeyle komuta eklemek.
system("ping " . $ip)gibi bir yapı, en temel ve en yaygın hatadır. - Denylist tabanlı filtreleme. Belirli karakterleri (AND operatörü, komut ayırıcı, pipe) engellemeye çalışmak, encoding farklılıkları veya unutulan bir operatör yüzünden atlatılabilir.
- Çıktıyı gizleyip "güvenli" sanmak. Komut çıktısının kullanıcıya gösterilmemesi, komutun çalışmadığı anlamına gelmez; sadece Blind senaryoya geçildiğini gösterir.
- Shell fonksiyonlarını gereksiz yere kullanmak. Argümanları ayrı ayrı ileten güvenli API'ler mevcutken, tek string alan
shell=Trueveyasystem()gibi fonksiyonları tercih etmek. - Aşırı yetkili servis hesaplarıyla çalışmak. Web sunucusunun root ya da geniş yetkili bir hesapla çalışması, zafiyetin etkisini gereksiz yere büyütür.
Gerçek Dünya Örnekleri
Command Injection, akademik bir kavram olmanın ötesinde, gerçek dünyada büyük sistemleri etkilemiş bir zafiyet sınıfıdır.
Geçmiş yıllarda, ağ cihazları ve router yönetim panellerinde sıkça Command Injection zafiyetleri raporlanmıştır; bu panellerin "ping testi" veya "DNS ayarları" gibi diagnostic özellikleri, kullanıcı girdisini doğrudan shell'e ileterek saldırganların cihaz üzerinde tam kontrol elde etmesine imkân tanımıştır.
IoT cihaz ekosisteminde de benzer desenler tekrar tekrar görülmüştür; sınırlı işlemci gücüne sahip cihazlarda geliştiriciler işlevselliği hızlıca sağlamak için işletim sistemi komutlarına doğrudan güvenmiş, bu da büyük botnet kampanyalarının cihazları ele geçirmesine zemin hazırlamıştır.
Kurumsal yazılımlarda da, özellikle dosya dönüştürme, log analiz veya yedekleme gibi "yardımcı" işlevlerde Command Injection bulguları bug bounty programları kapsamında sıkça raporlanır; bu işlevler genellikle ana kimlik doğrulama akışının gölgesinde kaldığı için güvenlik gözden geçirmelerinde atlanma eğilimindedir.
Bu örneklerin ortak noktası şudur: Command Injection, genellikle "yardımcı" veya "ikincil" görülen işlevlerde (diagnostic araçları, dönüştürme özellikleri, otomasyon script'leri) gözden kaçırılır. Ana uygulama mantığı güvenli olsa bile, bu tür ikincil işlevlerin işletim sistemiyle kurduğu doğrudan bağlantı, saldırganlara giriş noktası sağlar.
Özet
Command Injection, teknik olarak karmaşık bir istismar gerektirmeyen, buna rağmen etkisi son derece ciddi olabilen bir zafiyet sınıfıdır. Bu makalede ele aldığımız temel noktaları bir araya getirdiğimizde şu sonuçlara varıyoruz:
- Command Injection, bir syntax hatasından değil, uygulamanın kullanıcı girdisini bir shell'e "yorumlanmak üzere" teslim etmesinden doğar. Shell, kendisine verilen string'in kaynağını umursamaz; sadece syntax'a bakar.
- Zafiyetin görünürlüğü değişebilir — Basic, Blind, Time-Based veya Out-of-Band — ama temel mekanizma her zaman aynıdır: kullanıcı girdisi, komut olarak yorumlanabilir hâle gelir.
- Etkili savunma, tek bir yönteme değil katmanlı bir yaklaşıma dayanır: allowlist tabanlı girdi doğrulama, shell'den tamamen kaçınan güvenli API'ler, en az yetki prensibi, konteynerleştirme ve sistem çağrısı loglaması bir arada kullanıldığında güçlü bir savunma hattı oluşturur.
- Geliştiricilerin en sık yaptığı hata, kullanıcı girdisini doğrudan string birleştirmeyle bir komuta eklemek ve bunu denylist tabanlı, yüzeysel bir filtreyle "güvenli" sanmaktır.
En önemli ders şudur: modern DevOps kültüründe otomasyon script'leri, CI/CD pipeline'ları ve konteyner orkestrasyon sistemleri sürekli olarak shell komutları çalıştırıyor. Bulut ortamlarında bir Command Injection, artık sadece tek bir sunucuyu değil, o sunucunun eriştiği cloud metadata servislerini, IAM rollerini ve diğer mikroservisleri de tehlikeye atabiliyor. Bir uygulamanın arayüzü ne kadar basit görünürse görünsün, arkasında bir işletim sistemi varsa, o işletim sistemine giden her kapı dikkatle korunmayı hak eder.
Kaynaklar
- OWASP Foundation, OS Command Injection Defense Cheat Sheet
- OWASP Cheat Sheet Series, resmi GitHub deposu
- PortSwigger Web Security Academy, OS command injection bölümü
- MITRE, CWE-78: Improper Neutralization of Special Elements used in an OS Command
- PayloadsAllTheThings, Command Injection bölümü
📚 Bu Serinin Diğer Yazıları
✅ Web Güvenliği Serisi — Bölüm 1: Cross-Site Scripting (XSS)
✅ Web Güvenliği Serisi — Bölüm 2: SQL Injection
✅ Web Güvenliği Serisi — Bölüm 3: Cross-Site Request Forgery (CSRF)
✅ Web Güvenliği Serisi — Bölüm 4: Server-Side Request Forgery (SSRF)
✅ Web Güvenliği Serisi — Bölüm 5: File Upload Vulnerabilities
✅ Web Güvenliği Serisi — Bölüm 6: Command Injection (Bu Yazı)
⏳ Web Güvenliği Serisi — Bölüm 7: Directory Traversal (Yakında)