Yazılım geliştirme süreçlerinde ortaya çıkarılan uygulamalarının amaçlarına yönelik ve hatasız çalışabilmesi için bu uygulamanın güvenliğini, daha geliştirme aşamasından itibaren koruyabilmek gerekir. Bu yüzden özellikle geliştirme aşamasında güvenli kod, uygulama güvenliğinin temel taşı konumundadır.
Güvenli kod geliştirebilmek için ortaya konulan uygulamanın ne kadar güvenli olduğunu test edebilmek adına statik ve dinamik kod analizleri yapılmaktadır. Bu analiz kodun içinde barındırdığı güvenlik açıklarını tespit edebilmek ve kod kalitesini arttırmak için kritik bir aşamadır.
Statik Kod Analizi Nedir?
Statik kod analizi uygulamanın kaynak kodunu çalıştırmadan kod incelemesi yapmaya odaklanır. Kodun kendi içinde barındırdığı güvenlik açıkları ve problem yaratabilecek kod yazımına odaklanan bu yaklaşım, kodun belli standartlara uyumlu olmasını ve çalışma mantığına aykırı davranmayacak şekilde çalışabilmesini hedefler. Bunun için test ekipleri otomatize bazı SAST (Static Analysis Security Testing) araçlarıyla birlikte manuel kod incelemesi yaparak verimli bir test süreci gerçekleştirirler.
Kod analizine başlamadan önce doğru ve verimli bir analiz süreci gerçekleştirmek için doğru araçlar ve iyi bir kod editörüyle çalışmak önemlidir. Otomatize araçları kullanmadan önce manuel analiz yapabilme yetkinliğine sahip olmak önemlidir. Bu yüzden uygulama içinde doğru noktalara odaklanmak ve doğru sorulara cevap aramak gerekir.
- Uygulama nasıl çalışır?
- Kritik işlem fonksiyonları nerelerde bulunur?
- Kimlik doğrulama fonksiyonları nerededir?
- Yetkilendirme fonksiyonu nerededir?
- Kullanıcı verilerini ele alan fonksiyonlar nerelerdedir?
- Uygulamanın kullanıcıları kimler?
- Kullanıcı yetki seviyeleri ve yetkilendirme dağılımları nasıl yapılandırılmış?
- Kullanılan Framework'ler, eklentiler ve 3. taraf kodlar nelerdir?
Bunun gibi sorulara cevap vermek uygulamada hangi noktalara odaklanmamız gerektiğinin de bir cevabıdır. Bu yüzden öncelikli olarak inceleme yapacağımız noktaları belirlemeli sonrasında bu noktalarda çalışan fonksiyonların içeriğini inceleyerek olası problemlerin tespitini yapmamız gerekir.
Kaynak Koda Nasıl Erişilir?
Tabi ki bütün bu testler kaynak kodun üzerinde uygulanan testler olmasından dolayı öncelikle bu kaynak koduna doğrudan erişmek veya erişmek için bazı yollar denemek gerekir. Gerçekten dışardan gelebilecek tehditlere yönelik bir simülasyon gerçekleştirmek istiyorsak kaynak koduna ulaşmadan önce istemci tarafında elimizde bulunan uygulama koduna bakarak başlayabiliriz. Bu, uygulama mantığını kullanıcı tarafından kavramak açısından önemlidir. Bu tür testler "Blackbox" testleri olarak da adlandırılır. Bu şekilde kullanıcı taraflı izlemeler sayesinde uygulamaya dışardan gelebilecek tehditleri anlayabilir ve kullanıcıların muhtemel açık ve hataları sömürmek için deneyebileceği yolları gözlemleyebiliriz.

Uygulamayı kullanıcı sınırları içerisinde gözlemledikten sonra uygulamanın sunucu tarafındaki kaynak koduna ulaşılabilirliği ölçmek için "Tersine Mühendislik" yöntemi kullanılabilir. Tersine mühendislik kaynak kodlarını görmeden yapılan Black Box yaklaşımını "White Box" yaklaşımına çevirmeye benzer. White Box testleri uygulamanın kaynak kodu üzerinden uygulama bütününe bakarak içerisindeki zayıflıkları tespit etme yöntemidir. Kaynak kodu sistemin çalışma sürecini direkt olarak görebilmeyi sağladığı için bu koda erişim çok daha kritik bilgiler verir. Bu yüzden Tersine Mühendislik ile elimizdeki uygulamayı parça parça analiz ederek uygulamanın arka tarafta nasıl çalıştığı, hangi süreçlerden geçtiği gibi konuları aydınlatarak kaynak kodu tahmin etmeye dayalı parçalardan bütünü görebilmeyi amaçlar.

Bu yöntemlerin yetersiz kalması veya işe yaramaması durumunda kod sızdırma denemeleri yapılabilir. Kod sızdırabilmek için genellikle hatalı kaynak kod yapılandırmaları veya kasıtlı olarak kodun public edilmesi durumları olmalıdır. Örneğin web uygulamalarında kod sızdırmak için "Path Traversal" ile dizin kontrolleri yapılarak hassas dosyalara ulaşma, SSRF (Server-Side Request Forgery) yöntemiyle sunucuyu vekil (proxy) olarak kullanarak sadece sunucunun erişebildiği yerlerdeki private kodlara erişme gibi daha pek çok zafiyet bu tür saldırılara kapı açar ve kaynak kodu ifşa eder.
Son yöntem olarak OSINT süreçleri bizi kaynak koda götürebilir. Geliştiricilerin veya şirketin yanlışlıkla veya kasıtlı olarak public paylaştığı kodlar Github reposu taramalarında, Wayback Machine gibi public arşiv sitelerinde, Google Dork aramalarında, Pastebin gibi metin paylaşım sitelerinde bizi kaynak koda götürebilecek bilgiler bulunabilmektedir.
Data Flow Analysis (Veri Akışı Analizi)
Kaynak koda erişim sağlandığı takdirde artık bu kaynak kod içerisinde kullanıcıdan alınan verilerin belli bir noktaya ulaşana kadarki yolculuğunu takip etme adımına geçilebilir. Bu noktada Source ve Sink kavramlarına değinelim.
- Source: Kod analizinde Kaynak (Source) güvenlik açığının oluşmasına yol açan kod parçasıdır.
- Sink: Güvenlik açığının oluştuğu noktadır.
Örneğin bir komut enjeksiyonu (command injection) saldırısı gerçekleşirken zararlı komutun sisteme enjekte edilmesine olanak sağlayan input verisini alan fonksiyon Source, rastgele olarak Source üzerinden aldığı komutu çalıştıran fonksiyon ise Sink'tir.

→ Örnek Source Noktaları: — Web formundaki input bölümleri (name, username input) — Url içerisindeki parametreler (?id123) — HTTP header bilgileri (User-Agent, cookie bilgileri) — Bir portu dinleyen servislerde ağdan gelen ham paket verileri
→ Örnek Sink Noktaları: — executesql(), query() gibi veri tabanı komutu çalıştıran fonksiyonlar — innerHTML, document.write() gibi HTML içeriğini değiştiren JavaScript kodları — system(), exec() gibi işletim sistemi komutu çalıştıran fonksiyonlar — response.write(), echo gibi ekrana veri yazan fonksiyonlar
Bu şekilde pek çok Source ve Sink noktası arasında akan veri temizlenme (sanitizer) ve doğrulama işlemi yapılmadan birbirine aktarıldığında zafiyet oluşturur ve XSS, SQL saldırıları gibi pek çok saldırı için açık bir kapı bırakılmış olur. Bu süreçte veri akışını kontrol ederken Source ve Sink arasındaki veri akışı sürecinde; Source noktalarında verinin nereden geldiğine, verilerin hangi değişkenlere atanarak ilerlediğine, tehlike oluşturabilecek Sink noktalarına ulaşıp ulaşmadığına ve bu akışta bir temizleme işlemi yapılıp yapılmadığına tek tek bakılarak saldırıların önüne geçilebilmektedir.
Örnek: XSS saldırılarında attacker genellikle URL'ye ?name=<script>alert(1)</script> yazarak Source noktasından bir komut gönderir. Kod bu veriyi değişkene atar, Sink fonksiyonu ise bu değişkendeki değeri alır ve document.write(name) fonksiyonu ile sayfaya basar. Bu sayede attacker burada XSS zafiyetinin başarılı olduğunu görebilir. Bu saldırının başarılı olmasının sebebi de url'ye eklenen kodun herhangi bir temizleme işlemi yapılmadan direkt olarak Sink fonksiyonuna gönderilmesidir. Bunu için Source'dan çıkan veriyi Sink'e ulaşmadan önce uygun yöntemlerle filtreleyerek temizlendiğinden ve doğrulandığından emin olmak için denetlemeler yapılmalıdır.
Manuel Kod Analizi ve Tehdit Yakalama
Manuel olarak uygulama kodu içerisinde bulması kolay ama etkisi büyük olan belirli tehditleri yakalamak için bazı kritik kontrollerin yapılması önemlidir. Örneğin bilinen tehlike oluşturan fonksiyonlar, sabit kodlanmış gizli veriler (hardcoded secrets), zayıf şifreleme ve hash algoritmaları ve uygulama bağımlılıkları gibi kod içinde kolay fark edilebilecek tehditleri belirlemek için çeşitli arama yöntemleri bulunur.
- Tehlikeli fonksiyonlar: eval() fonksiyonu üzerinden örneklendirirsek, bu fonksiyon kullanıcıdan gelen veriyi direkt olarak bir kod parçası gibi çalıştırmaya yarayan bir fonksiyondur. Saldırganın bu fonksiyon ile kendi kodunu çalıştırması özellikle tehlike yaratan sonuçlar verebilir. Bu tür fonksiyonları kod editörü üzerinde "CTRL + SHIFT+ F" ile eval( kelimesini aratarak bu fonksiyonun kullanıldığı kod parçasına ulaşılabilir. Aşağıdaki kod parçası kullanıcıdan gelen HTTP isteğindeki "module" isimli parametreyi hiç bir kontrolden geçirmeden alır ve "engine.eval()" fonksiyonu ile de doğrudan JS kodu gibi çalıştırır. Bu açık saldırganın istediği zararlı komutu doğrudan çalıştırabileceği bir alan tanır.

- Sabit Kodlanmış Gizli Veriler: Sabit kodlanmış gizli verilerden kasıt uygulama içerisinde açık metin olarak paylaşılan API Keys, Encription Keys, veri tabanı şifreleri, Access Token'lar gibi public olarak paylaşılmaması gereken kritik verilerdir. Bu yüzden açıkça bu verilerin kod üzerine yazıldığı kodları tespit etmek gerekir. Bu tür veriler genelde belirli kalıplara göre karakter dizisi içerdiğinden, "Regex" aramaları kullanılarak bu veriler tespit edilebilir. Örneğin AWS API Key'ler genellikle "AKIA" ile başlayan 16 karakter uzunluğunda bir kalıba sahip olduğu için Ctrl+ Shift+F kullanarak aşağıdaki gibi bir regex araması yapılabilir.
AKIA[0-9A-Z]{16} # AKIA ile başlayan 16 karakter uzunluğundabüyük harf ve rakamdan oluşan sonuçlarBu arama sonucunda karşımıza çıkan veriler:

Bu tür sabit olarak kodun içine açık bir şekilde yazılmış gizli veriler güvenliği kritik seviyede tehlikeye sokacak durumlar oluşturabilir. Bu yüzden bu tür kodların analiz edilerek bulunması ve açık paylaşımını engellemek gereklidir.
- Zayıf Kriptografik Algoritmaları: Uygulamada kullanılan kriptografik algoritmalar, uygulama içerisinde kullanılacak uygulama processlerinin kullandığı önemli verilerin (kullanıcı adı, parola, şifreleme anahtarları vs.) açık metin şeklinde bu süreçler arasında paylaşılmasını önleyen ve dış ortamdan insanların eline geçmemesini sağlayan algoritmalardır. Kısaca gizlilik gerektiren her türlü verinin şifrelenmesi ile gizliliğin korunması ve hash algoritmalarıyla da bütünlüğünün sağlanması gibi güvenliğin en önemli adımlarını içermektedir. Bu yüzden bu kriptografik algoritmaların da dışardan deşifreye olabildiğince kapalı olması ve kırılmaması gereklidir. Güçlü olmayan şifreleme ve hash algoritmaları bu anlamda güvenlik zafiyeti oluşturan etkenlerdir. Örneğin MD5, SHA-1 gibi hash algoritmaları ve DES gibi şifreleme algoritmaları kırılmaya müsait algoritmalar olmasından dolayı uygulama içinde kullanılmaması önemlidir. Bunun için kod analizinde bu algoritmaların kullanıp kullanılmadığı kontrol edilmelidir.
- Üçüncü Taraf Kodlar ve Bağımlılık Kontrolü: Günümüzde uygulamalarda en çok açık veren kısımlar 3.taraf kodlar ve kütüphanelerdeki zafiyetlerden kaynaklanmaktadır. Örneğin yazılım içerisinde güncel olmayan kütüphanelerin kullanılması bilinen CVE açıklarıyla ilişkili kütüphanelerin kullanılması demektir. Bu da bu açıkların sömürülmesi ve güvenliği tehlikeye atması anlamına gelir. Bu yüzden güvenlik testlerinde uygulamada kullanılan yazılım diline göre farklı dosyalar içerisinde kullanılan kütüphaneleri görebilirsiniz. Örneğin Python için "requirements.txt" , JS için "package.json" , Java için "pom.xml" dosyalarında kullanılan kütüphaneler kontrol edilmelidir.

Buradaki örnekte görüldüğü gibi pom.xml sayfasında bağımlılıklar ile kullanılan kütüphanelere ilişkin bilgi edinilebilir.
Daha derin ve kapsamlı aramalar yapmak istersek büyük çapta bir uygulama için analiz süreçlerinin verimliliği açısından tek tek bütün kodları analiz etmek yerine belirli noktalara özellikle dikkat edilmesi çok daha odaklı bir yaklaşım olacaktır. Örneğin:
>>> Kullanıcı verilerini işleyen fonksiyonlar; kimlik doğrulama, yetkilendirme fonksiyonları ve uygulama için kritik olabilecek işlemleri gerçekleştiren diğer fonksiyonlar
>>> Kullanıcıdan girdi alınan her fonksiyon değerlendirilmeli çünkü bu fonksiyonlardaki zafiyetler saldırganlar için bir giriş kapısı niteliğinde olabilmektedir. HTTP request parametreleri, HTTP header, dosya yükleme alanları, kullanıcı adı- şifre inputları, formlar vs.
>>> Kullanıcı yetkilerini ve rollerini belirleyen fonksiyonlar oldukça önemlidir. Yanlış yetki yapılandırmaları büyük sızıntılara sebep olabilmektedir. Bu yüzden yetki ve rol dağılımları yapan ve yöneten fonksiyonlar analiz edilmelidir.
>>> Tehlike oluşturabilecek fonskiyonlar (Sink) "grep" ile aranmalı ve kimler bu fonksiyonları nasıl kullanır ve nasıl manipüle eder bunun analizi yapılmalıdır. Örneğin :
- OS Command Injection için: grep -rnE "system\(|exec\(|popen\(|os\.system\(" .
- SQL Injection için: grep -rnE "execute\(|raw_query\(|db\.query\(" .
- XSS için: grep -rnE "innerHTML|document\.write\(|echo|response\.write\(" .
>>> Sink fonksiyonlarını direkt olarak tehlike kabul etmeden önce bu fonksiyona giren değişkenlerin nereden geldiğinin kontrolü yapılmalıdır. Örneğin: Sink fonksiyonu olarak bulduğumuz "system(user_cmd)" satırında fonksiyona giren değişkene bakılmalıdır. "user_cmd" değişkeni nerede tanımlanmış tespit edilmeli. Eğer değişken doğrudan bir kullanıcı girişinden veri alıyorsa arada bir filtre kullanılmaması system() fonksiyonunun manipüle edilmesine sebep olur.
Bunun gibi kritik verilerin işlendiği, özellikle kullanıcı verilerini alan ve işleyen fonksiyonlar, uygulamaya girişlerini ve yetkilerini sağlayan-denetleyen fonksiyonlar gibi kritik fonksiyonlar öncelikli olarak denetlenmelidir.
Statik Kod Analiz Araçları
Statik kod analizi yaparken bu işlemleri otomatize yapabilecek araçlar kullanmak manuel kod analizini kolaylaştırır ve daha hızlı inceleme süreci yaratır. Test aşamalarında kullanabilecek bazı araçlar:
- SAST (Static Application Security Testing) Araçları: Kaynak kodun içine girerek Source ve Sink yollarını otomatik olarak tarar ve bağlam kurar. Semgrep, SonarQube, Synk Code, Bandit gibi araçlar hataları noktasal olarak gösterebilir bazı çözüm önerileri sunar ve kendi kurallarınızı oluşturma gibi opsiyonlar sunar.
- SCA (Software Composition Analysis) Araçları: Bu araçlar uygulama içerisinde 3.taraf kullanılan kodlar ve kütüphane bağımlılıklarının zafiyetini (CVE) tespit eder. Synk Open Source, OWASP Dependency-Check, Github Dependency Graph& Dependabot gibi araçlar; hangi kütüphanenin hangi versiyonlarında açık olduğunu tarayabilir ve güncelleme önerileri sunabilir.
- Gizli Veri Tespit Araçları: Kodun içine açık metin olarak gömülü şifreler, API anahtarları ve sertifikaları bulmak için kullanılır. TruffleHog, Gitleaks, Git-secrets olarak örneklendirebileceğimiz bu araçlar üst düzey taramalar ile mevcut koddan önceki geçmişi dahil olmak üzere taramalar yapar. Tespit ettiği içerikli kod dosyalarının Github gibi platformlarda paylaşımını ve dışarı sızmasını önler.
- Manuel Analiz Destek Araçları: Kodun içerisinde tek tek noktasal aramalar yaparken bu gezintiyi kolaylaştıran araçlardır. Örneğin JADX aracı mobil uygulamaları decompile (geri çevirme) işlemleri yaparak APK veya .NET uzantılı projelere çevirir. Bu da manuel analizi yapmaya yardımcı olmaktadır. Ayrıca Visual Studio Code gibi editörlerdeki "Synk" ve "Semgrep" gibi eklentiler kod yazarken belirli zafiyetleri anlık olarak göstermektedir.
Bu araçları proaktif bir şekilde kullanmak ve verimli kod analizi gerçekleştirmek için belirli bir sıraya göre her tarama için süreçleri sırayla takip edebilir ve analiz sonuçlarını rapor ederek ve gerekli düzenlemeleri yaparak başarılı statik analizler gerçekleştirebiliriz.
Kaynaklar: