Terminal ekranındaki 'Connection Terminated' hatası gözüme çarparken saat gece yarısını çoktan geçmişti. testCompany için yürüttüğümüz o kritik sızma testinde, Android uygulamasının sertifika sabitleme (SSL Pinning) mekanizmasını bir türlü aşamıyordum. Görünürde her şey doğruydu: Frida scriptlerim hazırdı, cihaz rootluydu ama uygulama, proxy trafiğini gördüğü anda kendini kapatıyordu. İşte o an anladım; karşımda standart bir TrustManager kontrolü değil, özel bir C++ kütüphanesi içine gömülmüş bir kontrol mekanizması vardı. Mobil güvenlik, sadece 'sertifika kontrolü yapalım' demekle bitmiyordu; asıl savaş, uygulamanın çalıştığı o kısıtlı ama vahşi ekosistemde veriliyordu.
SSL Pinning: Güvenliğin Kalesi mi, Kağıttan Şatosu mu?
Birçok geliştirici dostumla konuştuğumda, SSL Pinning'i aşılmaz bir duvar olarak gördüklerini fark ediyorum. Oysa bizim dünyamızda SSL Pinning, sadece kapıyı biraz daha sıkı kapatmaktır. Eğer saldırgan (ya da bizim gibi Red Team ekipleri) cihazın kontrolüne sahipse, o kapının menteşelerini sökmek sadece zaman meselesidir.
Özellikle Android dünyasında, ağ trafiğini dinlememizi engelleyen o meşhur yapıyı kırmak için en sık kullandığımız araç Frida. Frida, çalışma zamanında (runtime) uygulamanın damarlarına sızmamızı sağlayan bir dinamik enstrümantasyon kiti. Uygulama tam sunucuya bağlanacakken araya girip, 'Tamam, bu sertifika benim tanıdığım sertifika, devam et' diyebiliyoruz.
Bakın, aşağıda standart bir SSL Pinning kontrolünü bypass etmek için kullandığımız basit ama etkili bir script'in mantığını görebilirsiniz (Zararsızlaştırılmış/Mock):
/*
Frida Script: SSL Pinning Bypass (Pseudo-code)
Amaç: Güvenlik kontrolü yapan fonksiyonu manipüle etmek
*/
Java.perform(function () {
var CertificatePinner = Java.use('com.squareup.okhttp3.CertificatePinner');
// Uygulamanın kontrol metodunu 'overload' ediyoruz
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function (hostname, peerCertificates) {
console.log('[+] Bypass ediliyor: ' + hostname);
// Fonksiyonun hiçbir hata fırlatmadan 'boş' dönmesini sağlıyoruz
return;
};
console.log('[*] SSL Pinning Bypass Hooking Tamamlandı!');
});
Buradaki temel hata şu: Uygulama, üzerinde çalıştığı işletim sistemine ve donanıma %100 güveniyor. Ancak biz biliyoruz ki, bir cihaz rootluysa veya bir jailbreak işleminden geçmişse, işletim sisteminin 'güvenli' dediği her şey tartışmaya açıktır.
Veri Depolama: 'Kriptoladık ya Abi!'
Mobil sızma testlerinde en çok güldüğüm anlardan biri, uygulamanın yerel depolama alanına (/data/data/com.example.app/) bakmaktır. Genelde geliştiriciler, kullanıcının hassas verilerini (Session tokenlar, kişisel bilgiler) SharedPreferences veya SQLite içinde saklarken 'Sistem zaten bu klasörü koruyor, başka uygulama erişemez' mantığıyla hareket ederler.
Evet, Android'in sandbox yapısı bunu sağlar ama ya telefon çalınırsa? Ya da kullanıcı zararlı bir APK yükleyip farkında olmadan root yetkisi verirse?
Bir keresinde karşılaştığım bir senaryoda, uygulama tüm API anahtarlarını bir veritabanında saklıyordu. Veritabanı SQLCipher ile şifrelenmişti, yani dışarıdan baksanız hiçbir şey okunmuyordu. Ancak uygulamanın kaynak kodlarını (Smali veya Java) incelediğimde, şifreleme anahtarının (Master Key) statik olarak kodun içinde gömülü olduğunu gördüm: String DB_PASS = "S3cur3P@ssw0rd!";. Bu, kasanın üzerine anahtarı bantlamakla aynı şeydir.
Tavsiye: Hassas verileri asla ama asla ham halde saklamayın. Android tarafında EncryptedSharedPreferences veya KeyStore kullanmak, anahtarları donanım tabanlı (TEE - Trusted Execution Environment) birimlerde saklamak sizi bir nebze olsun kurtarabilir.
Tersine Mühendislik: Kodun Çıplak Hali
Mobil uygulamalar, web uygulamalarına benzemez. Web'de kaynak kodu sunucuda saklıyken, mobil dünyada 'malını mülkünü' her şeyi kullanıcının cihazına (yani saldırganın laboratuvarına) paketleyip gönderirsin. Eğer obfuscation (kod karartma) yapmadıysan, jadx veya apktool gibi araçlarla birkaç saniyede tüm iş mantığını (business logic) önüme sererim.
Özellikle ProGuard veya R8 gibi araçları sadece kod boyutunu küçültmek için değil, kodun okunabilirliğini zorlaştırmak için de kullanmalısınız. Ancak dikkat, obfuscation bir güvenlik önlemi değil, sadece bir zaman kazanma taktiğidir.
Peki Ne Yapmalı? (Defensive Mindset)
Sadece 'penetrasyon testi yaptırdık, bitti' demekle olmuyor bu işler. Mobil güvenlikte savunma katmanlı olmalı:
- Kök Dizin Kontrolü (Root/Jailbreak Detection): Uygulama, riskli bir cihazda çalıştığını anladığı an kısıtlı modda çalışmalı veya kritik işlemleri kapatmalı. Ama unutmayın, Magisk gibi araçlar bunları da gizleyebilir. O yüzden bunu tek başına bir kalkan olarak görmeyin.
- Anti-Frida ve Anti-Debug: Çalışma zamanında kodun manipüle edilip edilmediğini kontrol eden mekanizmalar kurun. Uygulamanın kendi kendini denetlemesini sağlayın.
- Güvenli İletişim: SSL Pinning yapın, evet, ama bunu sadece network seviyesinde bırakmayın. Gelen verinin bütünlüğünü (Integrity) kontrol eden 'message signing' yapılarını kurgulayın.
- Biyometrik Kimlik Doğrulama: Şifreler çalınabilir, sessionlar kopyalanabilir ama parmak izi veya yüz tanıma (eğer düzgün implemente edildiyse) saldırganın işini ciddi anlamda zorlaştırır.
Sabahın 4'ünde testCompany uygulamasını o gece kırmayı başarmıştım ama bu sayede binlerce kullanıcının verisini koruyacak o hardening (sertleştirme) raporunu da hazırlamış oldum. Mobil dünyada güvenlik, bir varış noktası değil, sürekli devam eden bir kovalamacadır.
Kodu yazarken değil, o kodun 'düşman elinde' nasıl davranacağını düşünerek geliştirme yapın. Terminaliniz açık, loglarınız temiz olsun dostlar.
