Saldırı altında mısınız? Olay müdahalesi için bize ulaşın

Güvenli yazılım geliştirme

Kullanıcı girdisinden oluşan tüm shell çağrılarını bulmak

Kullanıcı girdisinden oluşturulan shell çağrılarını bulun ve escaping'e güvenmek yerine kütüphane API'leri veya argüman dizileriyle değiştirin.

Diğer diller:EnglishDeutschFrançaisEspañolItalianoPolskiNederlandsEesti

İllüstrasyon: Kullanıcı girdisinden oluşan tüm shell çağrılarını bulmak

OS command injection için güvenilir çözüm, kullanıcıdan etkilenen string'leri bir shell'e hiç iletmemektir. Daha iyi escaping yapmak zayıf bir seçenektir. Pratikte yapılacak iş, kodunuzun süreç başlattığı her noktayı bulmak ve her biri için bu çağrının var olması gerekip gerekmediğine karar vermektir.

Command injection, bir uygulamanın güvensiz kullanıcı verisini sistem shell'ine geçirmesiyle ortaya çıkar ve enjekte edilen komutlar genellikle uygulamanın yetkileriyle çalışır. CWE-78 rahatsız edici bir ayrıntı daha ekler: komutları uygulama çalıştırdığı için, bu etkinlik uygulamadan ya da uygulamanın sahibinden geliyormuş gibi görünebilir.

Escaping neden yanlış ilk cevap

PHP'nin escapeshellarg() fonksiyonu bir değeri tek tırnak içine alır ve shell onu tek bir argüman olarak değerlendirir. Bu, & echo "hello" ifadesinin ikinci bir komut olarak çalışmasını engeller. Ancak saldırganın programa hangi tek argümanı ileteceğini seçmesini engellemez.

Her OS command injection aynı zamanda bir argument injection'dır. OWASP cheat sheet içinde, curl'e geçirilen escape("--help") yine de yardım metnini yazdırır. Programa bağlı olarak etki, bilgi ifşasından remote code execution'a kadar uzanır.

PHP manual sayfasındaki escapeshellarg() bölümünde bir okuyucu notu, aynı kusuru tar ile gösterir. Her dosya adı doğru biçimde escape edilmiştir, ancak tire ile başlayan bir değer yine de seçenek olarak okunur ve tar'ın başka bir komut çalıştıran bir seçeneği vardır. Dosya adlarından önce -- eklemek sorunu çözer, ancak not, her programın bu ayırıcıyı desteklemediğine dikkat çeker.

Windows'ta kılavuza göre escapeshellarg() çift tırnak kullanır ve yüzde işaretlerini, ünlem işaretlerini ve çift tırnakları boşlukla değiştirir. Bir okuyucu notu, sonucun güvenli ama kayıplı olduğunu ekler. Her iki durumda da bu, shell'den kaçınmak için bir nedendir, bir çözüm değildir.

Çağrıları bulun

Bu aramaları tüm repository'lerde çalıştırın. Dosya türlerini kendi yapınıza göre ayarlayın:

rg -n 'shell=True|os\.system|os\.popen' --type py
rg -n "subprocess\.(run|Popen|call|check_output)\(\s*[f\"']" --type py
rg -n '\b(system|exec|shell_exec|passthru|popen|proc_open)\s*\(' --type php
rg -n '`[^`]*\$' --type php
rg -n 'Runtime\.getRuntime\(\)\.exec|ProcessBuilder' --type java
rg -n 'ProcessStartInfo|Process\.Start|UseShellExecute' --type cs
rg -n '\bsystem\s*\(|popen\s*\(|exec[lv]p?e?\s*\(' --type c

İkinci Python deseni, komut olarak geçirilen tek bir string'i yakalar. shell=True olmadan bu yalnızca string bir programı adlandırıyorsa ve başka hiçbir şey içermiyorsa çalışır, bu yüzden her sonuca elle bakılmalıdır. PHP backtick deseni gürültülüdür, çünkü yorumlardaki ve SQL içindeki backtick'leri de eşleştirir. Ayrıca shell script'lerini, deploy script'lerini ve cron wrapper'larını da arayın. Bunlar komutları uygulama kodu kadar sık değişkenlerden oluşturur.

DilAranacak ifadelerYerine kullanılacak
PHPsystem(), exec(), shell_exec(), passthru(), popen(), proc_open(), backtick operatörümkdir() gibi yerleşik fonksiyonlar
Pythonshell=True; os.system; os.popen; tek bir komut string'iListe ile subprocess.run
JavaRuntime.exec(String)Program ve her argümanı ayrı elemanlar olarak alan ProcessBuilder
.NETcmd.exe, powershell.exe, sh -c, oluşturulmuş bir Arguments string'iArgumentList kullanan ProcessStartInfo
Csystem()Bir kütüphane çağrısı

Sabit kodlanmış komutları da dahil edin. OWASP topluluk yazısı, string içinde hiç kullanıcı girdisi olmayan cd /var/yp && make komutunu çalıştıran yetkili bir CGI programını anlatır. Program make için mutlak yol kullanmadığından, saldırgan $PATH değerini değiştirip kendi binary'sini programın yetkileriyle çalıştırabilir.

Sonuçları önceliklendirin

Ham bir sonuç listesi, bulgu listesi değildir. Her sonuç için değeri geriye doğru izleyin ve bir isteğin ona ulaşıp ulaşamadığını sorun. Route ve query parametreleri, form alanları, cookie'ler ve HTTP header'ları bariz kaynaklardır. Yüklenen dosya adları, kullanıcıların yazabildiği bir veritabanından gelen değerler, kuyruk mesajları ve ortam değişkenleri ise insanların unuttuğu kaynaklardır.

Sonuçları üç gruba ayırın: kimliği doğrulanmamış bir istekle ulaşılabilenler, yalnızca giriş yapıldıktan sonra ulaşılabilenler ve hiç ulaşılamayanler. Önce ilk grubu düzeltin. Üçüncü grubu listede tutun, çünkü sonraki bir refactor, kimse fark etmeden onu bir isteğe bağlayabilir.

Çağrıyı bir API ile değiştirin

Yerleşik kütüphane fonksiyonları, amaçlanan görevden başka bir şey yapmaya zorlanamaz. system("mkdir /dir_name") yerine mkdir() kullanın. mail komutunu shell üzerinden çağırmak yerine javax.mail.* içindeki Java mail API'sini kullanın. CWE-78 tasarım için de aynı öneriyi verir: harici süreçler yerine kütüphane çağrıları kullanın.

Süreç başlatmak zorunda olduğunuzda

Bazı araçların kütüphane karşılığı yoktur. O durumda iki katmanı birlikte kullanın: shell içermeyen bir argüman listesi ve doğrulama.

Python subprocess dokümantasyonu, bir argüman dizisinin genellikle tercih edildiğini söyler, çünkü modül escaping ve quoting işini kendisi halleder. shell=True kullanmadan önce Security Considerations bölümünün okunması gerektiğini belirten bir not içerir. Ayrıca çalıştırılabilir dosya için tam nitelikli bir yol önerir. Bu, yukarıdaki make örneğinin istismar ettiği PATH aramasını ortadan kaldırır.

import re, subprocess

if not re.fullmatch(r"[a-z0-9]{3,10}", name):
    raise ValueError("rejected")

subprocess.run(["/usr/bin/tar", "cf", "out.tar", "--", name], check=True)

Desen baştaki tireyi zaten reddettiği için -- gereksiz görünür. Bu bir defense in depth önlemidir: biri regex'i sonradan gevşetirse, ayırıcı name değerinin seçenek olarak okunmasını yine de engeller.

.NET'te FileName içinde sabit, tam nitelikli bir yol kullanın, UseShellExecute = false ayarlayın ve her argümanı ArgumentList ile ekleyin. Güvenilmeyen girdiyi asla cmd.exe, powershell.exe veya sh -c komutlarına geçirmeyin. Argümanları ayırmak, Java'da da .NET'te de doğrulama yerine geçmez.

Ardından değerleri doğrulayın. Komutu izin verilen komutlar listesine karşı kontrol edin, her argümanı da izin verilen değerlere ya da en fazla uzunluk sınırı olan bir desene karşı kontrol edin. Metakarakterler ve boşluklar dışarıda tutulmalıdır.

curl için -- yeterli değildir, çünkü sonraki argümanlar yine URL operand'ı sayılır ve ek transferler mümkündür. Yalnızca tek bir doğrulanmış URL geçirin, --globoff kullanın ve SSRF korumaları uygulayın.

Uygulamayı ihtiyaç duyduğu en düşük yetkilerle çalıştırın ve tek görevlik işlere kendi izole hesaplarını verin. chroot, AppArmor veya SELinux gibi bir sandbox yardımcı olur, ancak CWE-78 bunun etkinliğini sınırlı olarak değerlendirir. Saldırganın erişebileceği alanı daraltır, kusuru ortadan kaldırmaz.

Nereden başlamalı

  1. Yukarıdaki aramaları, deploy script'leri, CI job'ları ve cron wrapper'ları dahil tüm repository'lerde çalıştırın. Her sonucu, ona ulaşan girdiyle birlikte listeleyin ve listeyi bir isteğin ona ulaşıp ulaşamamasına göre sıralayın.
  2. Her sonuç için bir kütüphanenin veya yerleşik fonksiyonun aynı işi yapıp yapmadığını sorun. Yapıyorsa çağrıyı değiştirin ve süreç başlatmayı silin.
  3. Kalan çağrıları, argüman listesi, shell kullanmama ve mutlak yürütülebilir dosya yolu ile yeniden yazın. Allowlist doğrulaması ekleyin ve program destekliyorsa -- ayırıcısını kullanın.
  4. Süreçleri, gözden kaçan bir durumun elinde daha az koz kalması için özel, düşük yetkili bir hesapla çalıştırın.
  5. Kalan her çağrıyı bob.txt;sleep 5 gibi bir değerle ve tire ile başlayan bir değerle test edin. Çıktıyı beklemek yerine yanıt süresini ölçün. Tire ile başlayan değer reddedilmeli ya da dosya adı olarak ele alınmalıdır. Dışarıdan bir doğrulama için bir sızma testi her ikisini de dener, ya da güvenli yazılım geliştirme ekibimiz kodu sizinle birlikte inceleyebilir.

Nasıl yardımcı olabiliriz

Sızma testi

Ağlarınıza, uygulamalarınıza ve çalışanlarınıza gerçek bir saldırgan gibi saldırır, her açığı nasıl kapatacağınızı gösteririz.

Bize yazın
AngelForce ekibi

Bu yazıyı AngelForce ekibi hazırladı: Avrupa genelindeki kurumların sistemlerini test eden, izleyen ve koruyan; CEH, LPT Master ve CISA sertifikalı güvenlik danışmanları.

Aşağıda listelenen herkese açık kaynaklardan yapay zekâ desteğiyle yazıldı ve yayından önce AngelForce ekibi tarafından incelendi.

Kaynaklar

  1. cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html
  2. community.owasp.org/attacks/Command_Injection
  3. cwe.mitre.org/data/definitions/78.html
  4. docs.python.org/3/library/subprocess.html
  5. www.php.net/manual/en/function.escapeshellarg.php

Güvenli yazılım geliştirme hakkında yazılar

Neyi korumanız gerektiğini anlatın

Altyapınızı ve hedefinizi kısaca yazın, size e-postayla dönelim.

Bize yazın
info@angelforce.eu +372 5705 2044

AngelForce OÜ · Tallinn, Estonya