Önceki dersimizde, bir verinin kaynaktan (source) hedefe (sink) olan yolculuğunu fonksiyon ve modül sınırları boyunca takip ederek manuel veri akışı analizinin temellerini öğrendik. Bu "açık" (explicit) veri akışları, güvenlik analizinin temelini oluştursa da, uzman bir gözün fark edebileceği daha sinsi ve dolaylı yollar da mevcuttur.
Bu derste, veri akışının daha gizemli bir boyutunu, örtük (implicit) veri akışlarını ele alacağız. Öğrenme hedefimiz, verinin doğrudan parametreler veya dönüş değerleri aracılığıyla değil, global state, ortam değişkenleri ve paylaşılan kaynaklar üzerinden nasıl "sızdığını" veya "ışınlandığını" tespit etmektir. Bu tür akışlar, birçok otomatize aracın gözden kaçırdığı, ancak ciddi mantık hatalarına ve zafiyetlere yol açabilen durumlardır. Bu yetenek, sizi standart bir analistten, sistemin durumunu ve gizli varsayımlarını anlayan bir uzmana dönüştürecek kritik bir adımdır.
Veri Akışının Ötesi: Durum ve Değişmezler (Invariants)
Her güvenlik açığı, basit bir "source-to-sink" veri akışı problemi değildir. En zorlu ve ilginç zafiyetler, genellikle sistemin durumu (state) ve değişmezleri (invariants) ile ilgili varsayımlar çöktüğünde ortaya çıkar. Bir değişmez, sistemin her zaman doğru olduğunu varsaydığı bir kuraldır (örneğin, "bu nesne her zaman doğrulanmış kullanıcı verisi içerir"). Örtük akışlar, bu değişmezleri sessizce ihlal edebilir.
Bu konuya derinlemesine bir bakış açısı sunan OpenAI'nin bir makalesini inceleyerek başlayalım. Makale, SAST araçlarının neden yeterli olmadığını açıklarken, aslında bizim bu derste odaklanacağımız "örtük akış" ve "bağlamsal doğruluk" sorunlarına parmak basıyor.
Codex Security Neden Bir SAST Raporu İçermiyor? - OpenAI
Bu makale, en zorlu güvenlik açıklarının basit veri akışı problemlerinden ziyade, güvenlik kontrollerinin bağlamsal olarak yetersiz kaldığı durumlarda nasıl ortaya çıktığını açıklıyor. Bu, örtük akışları anlama motivasyonumuzu güçlendirecek bir çerçeve sunar.
Lütfen makalenin giriş bölümünü ve bu paragrafı dikkatlice okuyun. Burada vurgulanan, "savunmanın gerçekten işe yarayıp yaramadığını anlama" fikri, dersimizin ana temasını oluşturuyor. Ardından, "SAST araçları halen çok önemlidir" başlığı altındaki son paragrafı okuyun. Bu kısım, bu derste ele alacağımız zafiyet türünü mükemmel bir şekilde özetliyor: sorun kirli bir verinin hedefe ulaşması değil, programın bir bütün olarak istenmeyen bir duruma girmesidir.
Gördüğünüz gibi, mesele sadece verinin yolunu izlemek değil, aynı zamanda verinin geçtiği yollar boyunca sistemin durumunu nasıl etkilediğini anlamaktır. Şimdi bu etkiyi yaratan örtük kanalları inceleyelim.
Kanal 1: Global State ve Framework Büyüsü
Örtük veri akışının en yaygın kanallarından biri, paylaşılan bellek alanlarıdır. Bunlar global değişkenler, statik sınıf üyeleri veya uygulama genelinde tek bir örneği olan Singleton nesneleri olabilir. Bir fonksiyon, kullanıcıdan aldığı bir veriyi böyle bir paylaşılan alana yazar; başka bir fonksiyon ise, bu veriyi doğrudan bir parametre olarak almadan bu alandan okur.
Bu durum, özellikle Aspect-Oriented Programming (AOP) kullanan modern framework'lerde (örneğin Spring) belirginleşir. AOP, kodun farklı noktalarına "araya girerek" (intercept) loglama, güvenlik veya transaction yönetimi gibi işlemleri merkezi olarak uygular. Bu "araya girme" işlemi, açık bir fonksiyon çağrısı olmadan gerçekleşir ve dolaylı bir veri akışına neden olabilir.
Aşağıdaki görsel, bir Spring uygulamasında bu durumu mükemmel bir şekilde göstermektedir.

Bu örnekte, LoggingAspect, getSensitiveData metodunun etrafına "örülmüştür". Metot tamamlandığında, framework LoggingAspect'i tetikler ve metodun dönüş değerini (result) ona geçirir. UserService'in LoggingAspect'in varlığından haberi bile yoktur. Ancak aralarında örtük bir veri akışı vardır ve bu akış, hassas verinin bir log dosyasına (sink) yazılmasıyla sonuçlanır.
Kanal 2: Paylaşılan Kaynaklar ve Yarış Koşulları (Race Conditions)
Verinin "ışınlanabildiği" bir diğer güçlü kanal da dosya sistemi, veritabanı veya paylaşılan cache gibi harici kaynaklardır. Bir işlem (örneğin bir HTTP isteği) bu kaynaklardan birine kirli veri yazar, tamamen farklı bir işlem (örneğin başka bir istek veya bir arka plan görevi) bu veriyi okur.
Bu durum, özellikle Time-of-Check to Time-of-Use (TOCTOU) zafiyetlerinin temelini oluşturur. TOCTOU, bir kaynağın durumunu kontrol ettiğiniz an (Time-of-Check) ile onu kullandığınız an (Time-of-Use) arasında, durumun kötü niyetli bir şekilde değiştirilebildiği bir yarış koşuludur.

Bu teorik modelin pratikte ne kadar karmaşık ve tehlikeli olabileceğini görmek için, Mehmet İnce'nin bir kod challenge'ını çözdüğü aşağıdaki videoyu inceleyelim. Burada, dosya sisteminin bir paylaşılan kaynak olarak kullanıldığı karmaşık bir TOCTOU zafiyeti analiz ediliyor.
Round 2: ChatGPT vs Hacker - SonarSource Kaynak Kod Analizi ile Zafiyet Tespiti Challenge'ı
Bu video, SonarSource tarafından hazırlanan bir kod challenge'ının çözümünü içeriyor. Özellikle Python'daki TOCTOU/Race Condition örneği, örtük veri akışlarının nasıl istismar edilebileceğini gösteren ileri seviye bir senaryodur.
Lütfen videonun Python yarış koşulu bölümünü dikkatle izleyin. Burada analistin zafiyeti nasıl adım adım keşfettiğine odaklanın: Örtük Akışın Kaynağı: Analist ilk olarak os.path.join fonksiyonunun davranışındaki bir tuhaflığı fark ediyor (bu an). Eğer birleştirilen yol / ile başlarsa, önceki tüm yolların yok sayıldığını biliyor. Bu bilgi, saldırganın dosyanın yazılacağı hedef konumu (destination) manipüle etmesini sağlıyor. Bu, girdinin doğrudan dosya yolunu oluşturmadığı, ancak bir fonksiyonun davranışı aracılığıyla dolaylı olarak etkilediği örtük bir akıştır. Yarış Koşulu: Analist, upload, install ve clean gibi farklı adımlar arasında bir zamanlama farkı olduğunu fark eder. Saldırganın, dosya yüklendikten sonra, ancak tam işlenmeden önce araya girip dosya sistemindeki durumu manipüle edebileceği bir "pencere" olduğunu anlar (bu analiz). Bu, birden fazla isteğin aynı paylaşılan kaynak (dosya sistemi) üzerinde yarıştığı klasik bir TOCTOU senaryosudur.
Kanal 3: Ortam Değişkenleri ve Konfigürasyon
Uygulamaların davranışları genellikle ortam değişkenleri (environment variables), konfigürasyon dosyaları (.yml, .properties) veya hatta HTTP başlıkları (headers) gibi dış kaynaklardan gelen verilerle şekillenir. Bu değerler, kodun içinde sabit olarak yazılı olmasa da, programın çalışma zamanı durumunu (runtime state) belirler ve bu nedenle örtük bir veri akışı kanalıdır.
Örnek 1: HTTP Başlıkları
Birçok uygulama, load balancer'ın arkasındaki gerçek istemci IP'sini öğrenmek için X-Forwarded-For gibi HTTP başlıklarına güvenir. Geliştirici bu başlığa "güvenilir" bir altyapı bileşeni tarafından eklendiğini varsayabilir, ancak bu başlık genellikle kullanıcı tarafından manipüle edilebilir.
Mehmet İnce'nin videosundaki şu kısa örnek, bu durumu net bir şekilde gösteriyor.
Round 2: ChatGPT vs Hacker - SonarSource Kaynak Kod Analizi ile Zafiyet Tespiti Challenge'ı
Bu videodan kısa bir bölüm daha izleyerek, HTTP başlıklarının nasıl örtük bir veri kaynağı olabileceğini görelim.
Bu PHP örneğinde, X-Forwarded-For başlığından gelen verinin, exec() gibi tehlikeli bir fonksiyona (sink) nasıl ulaştığına dikkat edin. Veri, bir URL parametresi veya form girdisi değildir; isteğin metadatası içinden gelen, kolayca gözden kaçabilecek örtük bir kaynaktır.
Örnek 2: Parametre Enjeksiyonu
Bazen kullanıcı girdisi, bir komutun kendisini değil, o komutun davranışını değiştiren bir parametreyi manipüle eder. Bu, klasik komut enjeksiyonundan daha sinsi bir örtük akış türüdür. Girdi, tehlikeli karakterler (|, ;, $()) içermediği için güvenlik kontrollerini atlatabilir, ancak komutun bir parametresi olarak yorumlandığında beklenmedik sonuçlara yol açabilir.
Aşağıdaki örnekte, bir paket yöneticisi olan Composer'ın hg (Mercurial) komutunu nasıl kullandığı analiz ediliyor.
Round 2: ChatGPT vs Hacker - SonarSource Kaynak Kod Analizi ile Zafiyet Tespiti Challenge'ı
Aynı videodan son bir örnekle, parametre enjeksiyonu adı verilen ileri seviye bir tekniği inceleyelim.
Bu bölümde, kullanıcı tarafından kontrol edilen repository URL'sinin, hg komutunu çalıştıran kod parçasına nasıl aktarıldığını izleyin. Geliştirici, komut enjeksiyonunu önlemek için tehlikeli karakterleri engellemiştir. Ancak analist, URL'ye --config gibi bir parametre ekleyerek hg komutunun davranışını değiştirebileceğini ve bu yolla başka bir komut çalıştırabileceğini fark eder (bu an). Bu, verinin bir komuta eklenmesi değil, komutun çalışma şeklini örtük olarak değiştirmesidir.
Vaka Analizi: Örtük Akışları Zincirlemek
Şimdiye kadar tekil örtük akış kanallarını gördük. Gerçek dünyadaki karmaşık bir saldırı ise genellikle birden fazla örtük akışın ve varsayımın zincirlenmesiyle gerçekleştirilir. Mehmet Dursun İnce'nin PostHog analizi, bu konudaki en öğretici hikayelerden biridir.
Bir Hacker’ın Günlüğü: Gerçek Bir ‘Product Security’ Hikâyesi - Mehmet Dursun İnce - Meetup #58
Bu sunum, bir güvenlik araştırmacısının gerçek bir ürünü (PostHog) analiz etme sürecini anlatıyor. Bizim için önemli olan, araştırmacının farklı bileşenler arasındaki örtük güven varsayımlarını ve paylaşılan kaynakları nasıl birer birer ortaya çıkarıp bunları bir istismar zincirine dönüştürdüğüdür.
İlk İpucu - Eksik Doğrulama (Örtük Güven): Araştırmacı, Webhook URL'sini test eden bir endpoint'te SSRF koruması olduğunu, ancak URL'yi kaydeden başka bir endpoint'te aynı kontrolün olmadığını fark eder (bu keşif). Bu, geliştiricinin "URL zaten test edilmiştir" şeklinde örtük bir varsayımda bulunduğunu gösterir. Bu varsayımın çökmesi, saldırının ilk adımını oluşturur. İç Ağı Keşfetmek (Paylaşılan Ortam): SSRF zafiyetiyle, artık uygulamanın iç ağındaki diğer servislere erişim mümkündür. Araştırmacı, dokümantasyon okuyarak ClickHouse adında bir analitik veritabanının varlığını ve bunun bir HTTP API'si olduğunu öğrenir (bu süreç). İç ağ, tüm servislerin paylaştığı bir kaynaktır ve aralarındaki iletişim genellikle dış dünyaya göre daha az güvenlidir. Varsayılan Şifreler (Paylaşılan Konfigürasyon): ClickHouse'un PostgreSQL veritabanına bağlanabildiğini keşfeden araştırmacı, bu bağlantı için gereken kimlik bilgilerini (credentials) bulmalıdır. Bu bilgilerin, ürünün kurulum betiğinde varsayılan ve herkes için aynı olan değerler olduğunu keşfeder (bu kritik bulgu). Bu docker-compose.yml veya benzeri konfigürasyon dosyası, tüm kurulumların paylaştığı bir kaynaktır ve içerdiği varsayılan şifre, örtük bir zafiyet kaynağıdır. Zinciri Birleştirmek: Son adımda, tüm bu bulgular birleştirilir. SSRF, iç ağdaki ClickHouse'a ulaşmak için kullanılır. ClickHouse'daki bir SQL enjeksiyonu zafiyeti, varsayılan şifreler kullanılarak PostgreSQL veritabanında komut çalıştırmak için tetiklenir (tüm saldırı akışı). Bu, birden fazla örtük akışın, güvensiz varsayımın ve paylaşılan kaynağın nasıl bir araya getirilerek tam bir sistem ele geçirme (compromise) ile sonuçlanabileceğinin muhteşem bir örneğidir.
Sonuç
Bu derste, veri akışının görünen yüzünün ardına geçerek örtük kanalları keşfettik. Verinin sadece fonksiyonlar arasında değil, aynı zamanda global durum, konfigürasyon dosyaları, ortam değişkenleri ve paylaşılan kaynaklar (dosya sistemi, veritabanı) aracılığıyla da aktığını gördük.
Temel Çıkarımlar:
- Örtük Akışlar Durum Değişikliğidir: Veri, bir yerden bir yere doğrudan taşınmak yerine, sistemin genel durumunu değiştirir ve bu durum değişikliği başka bir yerde okunur.
- Güvenilir Olmayan Kanallar: Global değişkenler, HTTP başlıkları, konfigürasyon dosyaları ve dosya sistemi gibi kanallar, genellikle doğrudan kullanıcı girdisi kadar dikkatli incelenmez ve bu da onları sinsi zafiyet kaynakları haline getirir.
- Varsayımları Sorgulayın: Bir geliştiricinin veya sistemin yaptığı örtük varsayımları (örneğin, "bu URL zaten doğrulandı" veya "iç ağ güvenlidir") bulup test etmek, ileri seviye zafiyetleri ortaya çıkarmanın anahtarıdır.
- TOCTOU Her Yerde Olabilir: Bir kaynağın kontrol edildiği ve kullanıldığı her yerde, araya bir zaman penceresi giriyorsa, bir yarış koşulu potansiyeli vardır.
Önceki dersimizde verinin açık izini sürmeyi, bu dersimizde ise örtük gölgesini takip etmeyi öğrendik. Artık verinin hedefe nasıl ulaştığını biliyoruz.
Bir sonraki dersimizde, bu yolculuk sırasındaki en önemli duraklardan birine odaklanacağız: temizleyiciler (sanitizers). Bir verinin bir temizleme fonksiyonundan geçmesi, onun gerçekten güvenli olduğu anlamına gelir mi? Kirli veriye uygulanan temizleme, kodlama veya doğrulamanın, hedefin bağlamı için gerçekten doğru ve yeterli olup olmadığını nasıl değerlendireceğimizi inceleyeceğiz. Bu, "doğrula, sonra kodu çöz" (verify, then decode) gibi klasik hataları ve bağlamsal zafiyetleri anlamamızı sağlayacak.