- Konu sahibi
- #1
Command injection, kullanıcının kontrol ettiği verinin işletim sistemi komutuna güvenli sınırlar olmadan eklenmesi ve saldırganın uygulama adına ek komutlar çalıştırabilmesidir. Sorun yalnızca girdinin beklenmeyen bir değer olması değil, uygulamanın bu girdiyi kabuk tarafından yorumlanan bir komut metnine dönüştürmesidir. Başarılı bir saldırı; dosya okuma, veri değiştirme, yetki kötüye kullanımı veya sunucuda çalışan uygulamanın erişebildiği kaynakların ele geçirilmesiyle sonuçlanabilir.
Uygulama, örneğin bir dosya adını veya alan adını alıp bunu
Varsayımsal olarak şu PHP kodu güvensizdir:
Uygulama normalde
En güvenli çözüm, dosya işlemi için hiç kabuk komutu kullanmamaktır:
Dosyayı sunan PHP kodu için uygun PHP sunucusu gerekir. id=rapor metni döndürür, bilinmeyen veya dizi tipindeki id 400 verir; dosya yolu girdiden türetilmez. Kod yalnız herkese açık küçük rapor içindir; özel dosyada ayrıca oturum ve dosya bazlı yetki kontrolü gerekir. Üretimde hata ayrıntıları ekrana basılmamalıdır. Burada komut kabuğu devreye girmez. İhtiyaç gerçekten harici bir program çalıştırmaksa, kabuk üzerinden metin birleştirmek yerine kullanılan dilin kabuk açmayan süreç API’si ve ayrı argüman desteği tercih edilmelidir. Argümanların yine de izin verilen değerlerle sınırlandırılması gerekir; yalnızca kaçışlama fonksiyonuna güvenmek tasarım hatasını ortadan kaldırmaz.
Bir girdinin komuta eklenmesi her zaman aynı tür etkiyi doğurmaz. Aşağıdaki ayrım, düzeltme kararını kolaylaştırır.
Örneğin bir süreç API’si kabuk kullanmasa bile saldırganın değeri programın bir seçeneği olarak yorumlanıyorsa yeni kabuk komutu çalışmasa bile argument injection oluşabilir. Bu, komut enjeksiyonu savunmasında ayrıca ele alınan bir alt risk sınıfıdır. Bu nedenle yalnız özel karakterleri silmek yerine verinin hangi işlem için gerekli olduğunu belirlemek gerekir.
Bir test girdisi reddedildiğinde bu tek başına açığın kapandığını kanıtlamaz. Kod yolu gerçekten kabuğa ulaşıyor mu, alternatif bir endpoint aynı işlemi yapıyor mu ve kullanılan kütüphane girdiyi nasıl işliyor soruları da incelenmelidir. Açık bir command injection bulgusu varsa ilgili kod yolu düzeltilip tekrar test edilmeli, aynı işlevi çağıran arka plan görevleri de gözden geçirilmelidir.
Nasıl çalışır?
Uygulama, örneğin bir dosya adını veya alan adını alıp bunu
system, exec, shell_exec, os.system ya da kabuk çağıran benzer bir işleve doğrudan verirse risk oluşur. Kabuk; ;, &&, ||, komut değiştirme karakterleri ve yönlendirme işaretleri gibi özel karakterleri ayrı bir komut anlamında yorumlayabilir.Varsayımsal olarak şu PHP kodu güvensizdir:
Kod:
$file = $_GET['file'] ?? '';
$output = shell_exec('cat ' . $file);
Uygulama normalde
report.txt değerini bekler. Ancak saldırganın gönderdiği değer komut ayırıcı içeren bir metne dönüşürse, uygulama yalnızca dosyayı okumak yerine ek bir işletim sistemi komutu da çalıştırabilir. Bunun gerçekleşip gerçekleşmeyeceği kullanılan işletim sistemi, kabuk, PHP ayarı ve uygulama kullanıcısının yetkilerine bağlıdır; fakat güvenlik açığının temel nedeni değişmez: dış girdinin komut metnine birleştirilmesi.En güvenli çözüm, dosya işlemi için hiç kabuk komutu kullanmamaktır:
PHP:
<?php
// Yalnız size ait denemede küçük, herkese açık rapor dosyası önceden hazırdır.
// Dizin ve dosya güvenilmeyen kullanıcılarca değiştirilemez olmalıdır.
$files = ['rapor' => '/srv/example-data/public-report.txt'];
$key = $_GET['id'] ?? '';
if (!is_string($key) || !isset($files[$key])) {
http_response_code(400);
exit('Geçersiz rapor');
}
$text = file_get_contents($files[$key]);
if ($text === false) {
http_response_code(500);
exit('Rapor okunamadı');
}
header('Content-Type: text/plain; charset=UTF-8');
echo $text;
Dosyayı sunan PHP kodu için uygun PHP sunucusu gerekir. id=rapor metni döndürür, bilinmeyen veya dizi tipindeki id 400 verir; dosya yolu girdiden türetilmez. Kod yalnız herkese açık küçük rapor içindir; özel dosyada ayrıca oturum ve dosya bazlı yetki kontrolü gerekir. Üretimde hata ayrıntıları ekrana basılmamalıdır. Burada komut kabuğu devreye girmez. İhtiyaç gerçekten harici bir program çalıştırmaksa, kabuk üzerinden metin birleştirmek yerine kullanılan dilin kabuk açmayan süreç API’si ve ayrı argüman desteği tercih edilmelidir. Argümanların yine de izin verilen değerlerle sınırlandırılması gerekir; yalnızca kaçışlama fonksiyonuna güvenmek tasarım hatasını ortadan kaldırmaz.
Benzer sorunlardan ayırma
Bir girdinin komuta eklenmesi her zaman aynı tür etkiyi doğurmaz. Aşağıdaki ayrım, düzeltme kararını kolaylaştırır.
| Sorun | Temel neden | Öncelikli yaklaşım |
|---|---|---|
| Command injection | Girdi yeni bir işletim sistemi komutu çalıştırabiliyor | Kabuk çağrısını kaldırmak veya güvenli süreç API’sine geçmek |
| Argument injection | Girdi aynı programa beklenmeyen seçenek ya da argüman ekliyor | Argümanları ayrı geçirmek ve izin verilen değerleri açıkça sınırlamak |
| Path traversal | Girdi, izin verilen dizinin dışındaki dosyaya erişim sağlıyor | Sabit kök dizin, yol doğrulama ve erişim yetkisi denetimi kullanmak |
Örneğin bir süreç API’si kabuk kullanmasa bile saldırganın değeri programın bir seçeneği olarak yorumlanıyorsa yeni kabuk komutu çalışmasa bile argument injection oluşabilir. Bu, komut enjeksiyonu savunmasında ayrıca ele alınan bir alt risk sınıfıdır. Bu nedenle yalnız özel karakterleri silmek yerine verinin hangi işlem için gerekli olduğunu belirlemek gerekir.
Uygulanabilir düzeltme adımları
- Kodda kullanıcı verisini komut metnine ekleyen çağrıları bulun. Web formlarını değil, arka plan işleri, yönetim panelleri, yedekleme görevleri ve dosya dönüştürme işlemlerini de inceleyin.
- Harici komut gerekmiyorsa işlemi doğrudan uygulama kütüphanesiyle yapın. Dosya okumada dosya API’si, arşiv işlemlerinde uygun arşiv kütüphanesi, DNS veya ağ işlemlerinde ilgili protokol kütüphanesi kullanılmalıdır.
- Harici program zorunluysa komut adını sabit tutun, kullanıcı girdisini ayrı argüman olarak geçirin ve kabuk yorumlamasını devre dışı bırakan süreç yöntemini seçin.
- Her argüman için beklenen veri türünü belirleyin. Dosya adı, sayısal kimlik veya seçenek gibi değerleri allowlist ile sınırlandırın; geçersiz girdiyi işlemden önce reddedin.
- Düzeltmeden sonra normal değerler, boş değerler, çok uzun değerler, ayraç karakterleri ve seçenek biçimindeki değerlerle test yapın. Sonuç beklenmedik komut çalışması, yetkisiz dosya erişimi veya hatalı seçenek değil, güvenli bir reddetme olmalıdır.
- Uygulamanın işletim sistemi hesabını yalnızca ihtiyaç duyduğu dosya ve işlemlere yetkilendirin. Bu, açığı ortadan kaldırmaz; açığın etkisini sınırlar. Olay kayıtlarında ham gizli verileri saklamadan reddedilen istekleri ve ilgili işlem bağlamını izleyin.
Bir test girdisi reddedildiğinde bu tek başına açığın kapandığını kanıtlamaz. Kod yolu gerçekten kabuğa ulaşıyor mu, alternatif bir endpoint aynı işlemi yapıyor mu ve kullanılan kütüphane girdiyi nasıl işliyor soruları da incelenmelidir. Açık bir command injection bulgusu varsa ilgili kod yolu düzeltilip tekrar test edilmeli, aynı işlevi çağıran arka plan görevleri de gözden geçirilmelidir.