SAST Atlatma Yöntemleri

Geliştirme tavsiyesi değildir.

Zaman zaman sorumlu olduğunuz sistemler ile ilgili karşınıza çıkan güvenlik tarama araçlarının bulguları kuvvetle muhtemel canınızı sıkıyordur. Network, veritabanı uzmanı, developer, uygulama sunucu yöneticisi yani daha modern bir tabir ile DevOps sürecinin bir parçasıysanız, kim bilir belki de olur olmaz zamanlarda üzerinize atanmış güvenlik bulgularını anlayıp, çözmek yerine yok olmalarını dilemiş olabilirsiniz.

Developer iseniz doğru yerdesiniz. Bu yazıda, özellikle statik uygulama güvenlik testi (SAST) araçlarının bazı bulgularını düzeltmeden, nasıl kaçınabileceğinizi anlatmaya çalışacağım.

Yazılım güvenliğinde temel kamuflaj teknikleri.

Sadece yukarıdaki son cümle bile bir güvenlik uzmanının tüylerini diken diken etmeye yetecektir. Dolayısıyla bütün şimşekleri üstümüze çekmeden sorumluluk reddini şuraya bırakalım;

Bu yazıda listelenen veya ima edilen yöntemlerin hiç birisi gerçek hayatta uygulanmamalıdır. Bu yazı hem her yazılım takımının iş hayatında önemli bir yer tutması gereken statik güvenlik araçlarının davranışları hakkında hem de yazdığımız kodun güvenliğinin yegane sorumlusunun biz geliştiriciler olduğunu gösteren bilgilendirici bir içerik olarak görülmelidir.

Doğrulama vs. Hata Bulma

Tamam. Şimdi temel bir bilgi ile başlayalım; Kullandığınız otomatik güvenlik aracı hedef uygulamada hiç SQL Injection bulgusu vermezse, bu uygulamanızda gerçekten hiç SQL Injection olmadığı anlamına gelmez.

Bu durum hemen hemen bilgi güvenliği ile uğraşan herkes tarafından bilinmektedir. Daha teknik bir tabir ile otomatik güvenlik tarayıcılar, belirli bir karmaşıklığa sahip herhangi bir güvenlik zafiyeti veya kod kalitesi bulgusu için genel uygulama doğrulayıcı (verifier) değildirler.

Bu araçlar doğaları gereği genel bir doğrulama yapamazlar. Meraklılarına konu hakkında biraz eski de olsa 1979 tarihli “Social Processes and Proofs of Theorems and Programs” isimli ufuk açıcı makaleyi öneririm.

Ancak aynı araçlar birer mühendislik ürünleri olarak, yazılımlarda hata bulma konusunda çok iyi iş çıkarırlar. Hatta tecrübelerime dayanarak rahatlıkla şunu söyleyebilirim;

SAST ürünleri belirli bulgu tipleri için uygulamamızdaki yüksek riskli güvenlik hatalarını bulma konusunda sahip olduğumuz en iyi araçlardır.

Ön Kabuller ve İndirgemeler

Statik uygulama güvenlik testi araçlarından genel beklentimiz, herhangi bir yazılımda bulunan bütün güvenlik zafiyetlerini ortaya çıkarmalarıdır. Karmaşıklık ve performans problemleri nedeniyle bu gerçeklikten uzak beklenti, ancak bazı ön kabüller ve indirgemeler ile ete kemiğe bürünmüş, çalışan ve faydalı ürünlere dönüşür.

Basit bir istatistik ile anlatmaya çalışayım;

Bir geliştiricinin 4 veya 5 günde yapabileceği bir kod analizi* işlemini, otomatik bir ürün 1-2 saatte gerçekleştirilebilir**.

Ortaya koyduğu inanılmaz güvenlik faydalarının yanında işte bu ön kabül ve indirgemelerin hem hatalı alarm (FP) hem de kaçırılan bulgular (FN) anlamında bir etkisinin olduğunu da biliyoruz.

Düşük bir olasılık da olsa, her iki tip eksikliğin de geliştiriciler tarafından sömürülerek düzeltmek istemedikleri kod hatalarını SAST araçlarının gözünden kaçırmaları mümkündür. :)

Gelin hep beraber bazıları çok bariz bazıları ise biraz daha iş gerektiren bu kötüye kullanım senaryolarını 4 başlık altında incelemeye başlayalım.

Hassas Veri Yönetimi Kontrollerinin Atlatılması

Yazılımlarda güvenliği en zor gerçeklenen konulardan biri hassas veri yönetimidir. Kişisel veriler, şifreleme anahtarları, API token’ları, sistem, sağlık, üyelik bilgileri gibi veriler KVKK, GDPR gibi bir çok güvenlik standardına göre hassas veri olarak kabul ediliyor.

Peki otomatik bir program korunması gereken bu verilerin kodlarda geçip geçmediğini, nasıl işlendiklerini ve hangi süreçlerden geçtiklerini nasıl anlayabilir?

Geçerli ve zor sorular. Ama gelin sadece ilki hakkında basit bir tahminde bulunalım.

Bir statik uygulama güvenlik test aracı, kod veya yapılandırma dosyası içerisinde tanımlanmış bir şifreyi ilgili değişken veya anahtar isminden anlayabilir.

Örneğin aşağıdaki Java kodundaki değişken içinde passw kelimesi geçtiği için, aracımız tanımlı gömülü text’in şifre olduğu sonucunu çıkarabilir. Başlı başına bir çok hatalı alarm üretebilecek bir teknik bu ama diğer taraftan kaçırılan bulgu oranını en aza indirmenin de en etkili yöntemi.

String password = "v1o!gjs49#1glb53*"; 

Hemen belirteyim, bu yöntem haricinde çok daha güvenilir ve uygulanması mümkün yöntemler mevcut. Ama basitliği açısından bu tekniğin güvenlik bulgularına konu olmasının atlatılması güzel bir örnek olarak karşımıza çıkıyor.

Değişkenlerin isimlerinin değiştirilmesi bu tür bulguların bazılarının atlatılması doğrultusunda akla gelen en doğal yöntem.

İki tane, belki daha sofistike yöntem daha gösterip konuyu kapatalım. İlki farklı data tipleri kullanmak, örneğin;

String secrets [] = { "v1o!gjs49#1glb53*" };

İkinci yöntem de aynı doğrultuda başka bir teknik;

class PasswordStore
{
public String value;
}
PasswordStore ps = new PasswordStore();
ps.value = "v1o!gjs49#1glb53*";

Dikkat ederseniz, bu iki yöntemin de ilkinden farkı değişken veya sınıf isimlerinin değiştirmesine gerek kalınmaması. :)

Kontrol Akışı Bulgularının Gizlenmesi

Programların kontrol akışları, statik program analizi tekniklerinde önemli bir yer tutmaktadır. Bu akışların çıkarılması sayesinde hem veri akışları daha kolay üretilmekte hem de programların kontrol akışları ile ilgili güvenlik zafiyetleri ortaya çıkarılabilmektedir.

Bırakılmayan veya yanlış bırakılan veritabanı, network, dosya kaynakları bu tür hatalara birer örnek olarak verilebilir. Burada örneğini göstermek istediğimiz konu ise hatalı Exception yönetimleri ile ilgili.

try
{
// çalışma zamanı exception üretebilecek kod bloğu
}
catch(BirExceptionTipi bet)
{
}

Yukarıdaki örnekte var olan kod kalite problemini hemen yakalamışsınızdır. Catch bloğu içinde (finally kısmının olmadığını düşünelim), herhangi bir aksiyon alınmamış. Aksiyon alınmayacak bir hatayı, hiç yakalamadan üst katmana iletmek çok daha mantıklı bir senaryo.

Herhangi bir değişiklik yapmadan bu tür bulgulardan kaçmak için izlenecek ilk akla gelen yol, etkisiz statement’lar ile catch bloğunu doldurmaktır.

try
{
// çalışma zamanı exception üretebilecek kod bloğu
}
catch(BirExceptionTipi bet)
{
int mutlu_SAST_mutlu_Developer = 5;
}

Ne kadar yaratıcı bir yol değil mi?

Hasır altı ettiğimiz, halının altına süpürdüğümüz, görmezden geldiğimiz bu bulguyu artık bulgu listemizde görmememiz kuvvetle muhtemel.

Güvenlik bulgularını düzeltmeden gizlemek kafamızı kuma gömmekten farksız.

Ta ki, kayıt dosyalarındaki satırların da işimize yaramayacağı, çünkü exception’ı yukarıdaki gibi hiç etmişiz, zor bir runtime hatasını çözmek işi bize verilene kadar...

Tehlikeli Veri Akışlarının Kamuflajı

Veri akışı bir statik analiz aracının en önemli kısımlarından biridir. Özellikle uygulamaların baş belası Injection tipli bulguları ancak ve ancak bu analiz tip ile hem hatalı alarm (FP) hem de kaçırılan bulgu (FN) oranlarını olabildiğince düşük şekilde bulmak mümkün olur.

Bu iki oranın da düşük olmasını sağlamak için analiz araçları bir çok algoritmalar, kısayollar ve ön kabuller kullanırlar (k-bounded dereferencing, sensitivities, points-to anaysis, vb.).

Peki bir analiz aracı için hem düşük oranları yakalamak hem de kısa yolları ve ön kabulleri kullanmasak olmaz mı? Bu durumda iki şey ortaya çıkabilir;

  1. İnanılmaz uzun süreli analizler. Daha önce tecrübe ettiyseniz şimdiki analizler çok mu hızlı?” diye sorduğunuzu duyar gibiyim. Daha kötüsünü düşünün.
  2. Büyük maliyetler. Birinci maddedeki eleştirinin benzerini duyar gibiyim. Daha kötüsünü düşünün.
  3. İki madde dedim ama bir tane daha ekleyeyim. Bazı durumlarda ön kabuller kullanılmadan statik analiz yapmanın mümkün olmaması.

Bir SQL Injection zafiyetini gizlemek istemek mantıkla açıklanabilecek bir durum olmasa da, bahsi geçen maliyetlerden dolayı SAST araçlarının kullandığı veri akış algoritmalarının gözden kaçırabileceği akışlar kodlamak mümkündür.

Burada çok tehlikeli ve aslında bulguyu düzeltmekten daha zor olan bu tekniklerden sadece birinin örneğini göstermekle yetineceğim;

static void Main(string[] args)
{
C a = new C();
C b = a;
string input = System.Console.ReadLine();
Mix(a, b, input);
}
private static void Mix(C a, C b, string n)
{
a.x = n;
System.Diagnostics.Process.Start(b.x);
}

Yukarıdaki C# kodundaki veri akışı takip edildiğinde, bir Command Injection zafiyeti barındırdığı görülebilir. Referansların da takibini gerektiren başarılı bir analiz, Mix metodundaki a ve b argümanlarının aslında tek bir memory bloğunu göstermesi nedeniyle bu zafiyeti bulabilecektir.

Ama referansları takip eden sağlam bir analiz algoritması (points-to-analysis) göz ile çıkarımı yapılmasından daha zordur. Her ne kadar bu iş kullanılabilecek iyi bilinen algoritmalar varsa da, maliyetleri nedeniyle çoğu zaman tam anlamıyla uygulanmazlar.

Kısacası referansların hangi hafıza bloklarını gösterdiklerinin hesaplanması statik program analizinin üzerinde on yıllardır çalışılan en temel ama en zor problemlerinden biridir diyor ve bu alt konuyu burada kapatıyorum.

Desteklenemeyen Dil Özelliklerinin Kullanılması

Analiz araçlarının desteklemesi gereken dil özellikleri hem sayıca fazla hem de bazen çok karmaşıktır. Örneğin kontrol akışına sebep olabilecek, mesela throws gibi, özelliklerin yanında, bütün collection veri tipleri, HashMap, Dictionary gibi desteklenmelidir.

Ayrıca Generics, Inheritance ve Reflection gibi karmaşık ve zorlu yapılar ve API’lerde genel bir biçimde analiz algoritmasının içinde olmalıdır.

Hiç bir analiz aracı bütün bu yapıları karmaşıklıktan ve zaman maliyetinden dolayı desteklemez, bazen de teknik nedenlerden dolayı destekleyemez. Kısa yollara başvurur. Hele hele birden fazla dil desteği olan araçlarda bu işin zorluğu had safhaya ulaşır.

Bu maliyetleri biraz olsun indirebilmek adına bütün dillerin tek bir modele çevrilme çabası da zorluğa tuz ve biber olarak katkıda bulunur.

Dil veya framework zenginliğinin analiz araçlarını bulgu kalitesini nasıl etkileyebileceğine bir JSF örneği ile devam edelim. Bilmeyenler için kısaca tanımını yapmamız gerekirse, JSF (Jakarta/Java Server Faces) web uygulamaları için bileşen tabanlı kullanıcı arayüzleri oluşturabiliceğimiz 5-10 sene öncesine kadar ciddi popüleritesi olan bir Java spesifikasyonudur.

Bileşen tabanlı sunucu taraflı uygulama çatıları, ViewState’ler ve istek/cevap parametre karmaşıklığı nedeniyle özellikle dinamik güvenlik denetimlerinde genellikle hoş karşılanmazlar. Ama bütün web uygulamalarında olduğu gibi bu çatılar kullanılarak da ciddi güvenlik zafiyetleri oluşturmak mümkündür. :)

Örneğin aşağıdaki XHTML, JSF kodu, HTTP istek içindeki bio isimli zengin içerik barındıran bir parametre değerini browser ekranına göndermektedir. Bu zengin içerik nedeniyle default’ta true olan “escape” attribute’u false yapılmıştır.

Bu hali ile Cross Site Scripting (XSS) zafiyeti barındıran bu JSF kodu, güvenlik statik kod analizi araçları ile yakalanabilmektedir.

<h:body>        
<h:outputText value="#{param['bio']}" escape="false"/>
</h:body>

Eğer bu bulguyu temamıza uygun olarak düzeltmeden atlatmak istersek, aşağıdaki gibi dikkat ederseniz aslında yukarıdaki kod ile aynı işi yapan özel bir template oluşturabiliriz.

<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets" 
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:outputText value="#{param['bio']}" escape="false"/>
</ui:composition>

Bu template’ı ise asıl XHTML sayfamızda aşağıdaki gibi konumlandırmamız mümkün. Yani bu şekilde XSS’e yol açan yapıyı sadece JSF uygulama çatısına has bir teknik ile özelleştiriyor ve olduğu gibi kullanmış oluyoruz.

<h:body>        
<my:customOutputText/>
</h:body>

Statik analiz araçlarının bu gibi dilin veya uygulama çatılarının bütün özelliklerini barındırmalarını beklemek pek inandırıcı değil. Bu nedenle yukarıdaki yapı ile XSS bulgusunu çözmeden atlatmak mümkün olmaktadır.

Burada örneklerini vermeyeceğim ama aynı dolambaçlı yollar, dillerin Inheritance ve Reflection özellikleri kullanılarak da izlenebilir.

Yine de eklemekte fayda var; en doğrusu bulguları giderme eforu daha düşük olabilecek bulguların atlatılması için dolambaçlı yolların takip edilmemesidir.

Ama burda anlatılana benzer teknikler, belki proda çıkmadan düzeltilmek kaydıyla güvenlik birimleri ile aranızda bir eğlence aracı olarak kullanılabilir. ;)

Bütün Bunlar Ne Demek?

Tekrarlamakta fayda var. Bu yazının amacı statik kod analizi bulgularının düzeltilmeden gizlenmesini sağlamak değildir.

Bulguların düzeltilmeden gizlenmesi çok ciddi güvenlik ve dolayısıyla büyük ihtimal adli riskleri beraberinde getirecektir.

Provakatif olmakla beraber yazının meraklı geliştiricilere anlatmak istediği, statik güvenlik analiz ve hatta tüm otomatik güvenlik araçlarının bulgu bulma konusunda eksikliklerinin olduğudur.

Bu araçlar bir çok bakımdan her ne kadar işimizin ayrılmaz bir parçası, olmazsa olmazı olsalar da, kod güvenliğinin geliştiriciler olarak asıl bizim sorumluluğumuzda olduğu gerçeğini değiştirmezler.

Yazdığımız kodlardaki hataları sahiplendiğimiz gibi güvenlik bulgularını da sahiplenmeliyiz.

Geliştirme ekipleri olarak güvenlik konularında bilinçlenmemizi sağlayan, arkamızı kollayan, bize hız kazandıran SAST araçlarının artılarını ve eksilerini kavramamız bu nedenle oldukça önemlidir.

* Geliştiricinin kod inceleme hızı her 400 satır için ortalama 1 saat ve analiz edilecek toplam satır sayısı olarak da 80K-100K satır düşünülmüştür.

** Burada SQLi, XMLi, XSS, Directory Traversal gibi bütün injection problemleri, yapılandırma veya önemli kriptografik zafiyetlerden oluşan 200–300 maddelik kontrollerden bahsediyorum.

CodeThreat is a static application security testing (SAST) solution. Visit codethreat.com

CodeThreat is a static application security testing (SAST) solution. Visit codethreat.com