Güvensiz serileştirme
- Güvensiz serileştirme, uygulamanın güvenmediği baytları bir nesne gibi geri okuyup saldırganın gizlediği kodu sunucuda canlandırmasıdır.
- Zararlı bir yükün neden çalıştığını, gadget zincirinin nasıl kurulduğunu ve tespit noktalarını göreceksin.
- pickle, unserialize ve ObjectInputStream yerine güvenli biçim, dar izin listesi ve imza ile açığı kapatacaksın.
Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +50 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.
Bir program çalışırken nesneleri bellekte tutar. Bu nesneyi diske yazmak, başka bir sürece göndermek ya da önbellekte saklamak için baytlara çevirir. Bu işleme serileştirme denir. Karşı taraf baytları okuyup nesneyi yeniden kurar; bu adıma serileştirmeyi açma denir.
Sorun, geri okunan baytların nereden geldiğidir. Uygulama onları bir çerezden, bir form alanından ya da bir dosya yükünden alıp doğrulamadan açarsa, saldırgan baytların içine kendi kurduğu veriyi yerleştirir. Açılan şey sıradan bir sayı ya da düz metin değil, çalışma zamanında yeniden canlanan bir nesnedir. Bu fark onu öbür girdi türlerinden çok daha tehlikeli kılar, çünkü nesne kurulurken kod çalışabilir.
Bir metin alanı en kötü ihtimalle yanlış veri taşır. Açılan bir nesne ise, doğru hazırlanmışsa, sunucuda saldırganın seçtiği bir eylemi tetikler. Bu farkı görmek, açığı güvenli bir biçimle kapatmanın ilk adımıdır.
Baytların yolculuğu ve güven sınırı
Serileştirme döngüsü pek çok yerde çalışır. Oturum verisini önbellekte tutan bir servis, kuyruğa nesne gönderen bir arka plan işi, durumunu çerezde taşıyan bir API hep aynı döngüyü kullanır. Her nesne baytlara iner, bir yerde saklanır, sonra geri okunur. Bu akışın kritik anı, verinin bir güven sınırını geçtiği noktadır.
Sunucunun kendi yazdığı ve kimsenin eline geçmediği bir bayt dizisi bir yanda durur. Kullanıcının çerezinde taşınan ya da dış bir formdan gelen bayt dizisi bambaşka bir dünyadır; onu saldırgan özgürce değiştirir. Güvenli açma kararı tek bir soruya iner: bu baytlar kimin elinden geçti ve değiştirilmiş olabilir mi?
Diller açma sırasında yalnız veriyi kopyalamaz. Java, PHP, .NET ve Python nesneyi kurarken sınıfın kurucu kodunu ya da özel yöntemlerini çağırabilir. Yani açma işlemi sessiz bir veri okuma değil, kod çalıştırmaya açık bir adımdır. Bir sayıyı ya da düz metni okumak bu riski taşımaz, çünkü orada tetiklenecek bir yöntem yoktur.
Nesne açmaksa farklıdır. Baytların içindeki talimat, hedefte zaten yüklü sınıfları harekete geçirir ve bu sınıfların bazıları açılırken kendi kodunu çalıştırır. Bu ayrımı görmek, neden JSON gibi yalnız veri taşıyan bir biçimin daha güvenli olduğunu da açıklar; JSON açarken hiçbir yöntem çağırmaz.
Etkinlik 1 / 6
Aşağıdaki veri kaynaklarını serileştirmeyi açmak için güvenilir ya da güvenilmez olarak ayır.
Önce bir seçeneğe, sonra aşağıdaki uygun kutuya bas. İstersen seçeneği kutuya sürükleyip bırak. Yerini değiştirmek için aynı işlemi tekrarla.
0/4 seçenek yerleşti.
Gadget zinciri: hazır sınıflardan silah
Saldırgan açma sırasında kendi kodunu yüklemek zorunda değildir. Bunun yerine hedefte zaten duran sınıfları belli bir sırayla dizer.
Her sınıf, açılırken bir sonrakini çağıracak bir davranış taşır. Bu diziye gadget zinciri denir. Zincirin ilk halkaları zararsız görünür, son halka dosya okur, komut çalıştırır ya da yetkiyi değiştirir.
Saldırganın tek yaptığı, bu halkaları doğru sırayla tetikleyen bir bayt dizisi hazırlamaktır. Bu yüzden savunma açık bir kötü kod aramaz. Asıl soru, sınıf yolunda hangi kütüphanelerin durduğu ve bunların açma sırasında ne yaptığıdır. Kullanılmayan bir kütüphane bile zincire uygun bir halka sunabilir, bu yüzden gereksiz bağımlılığı kaldırmak saldırı yüzeyini küçültür.
Gerçek hayatta bu açık çoğu zaman bir kütüphane güncellemesiyle gelir. Bir ekip yıllardır kullandığı bir serileştirme kütüphanesini güvenli sanır, ama sınıf yoluna eklenen yeni bir bağımlılık zincire uygun bir halka getirir. Saldırgan hedefin hangi kütüphaneleri taşıdığını hata mesajlarından, açık kaynak bağımlılık listesinden ya da sürüm bilgisinden tahmin eder. Sonra o kütüphaneye uygun hazır bir zincir dener.
Bir kargo şirketinin güvenlik ekibi bu yüzden yalnız kendi kodunu değil, ürettiği paketin taşıdığı bütün bağımlılıkları da envantere alır. Bir açma noktası varsa, o noktada hangi sınıfların erişilebilir olduğu doğrudan saldırı yüzeyini belirler.
Derinleş:Dil dil serileştirme riski
Her dil açmayı kendince yapar, ama risk ortaktır. Python'da pickle, açma sırasında nesnenin __reduce__ yönteminin döndürdüğü çağrılabilir nesneyi verilen argümanlarla çalıştırır; belge bunu açıkça güvensiz sayar ve veriyi hmac ile imzalamayı önerir. PHP'de unserialize, __wakeup ve __unserialize sihirli yöntemlerini tetikler; PHP 7.0'dan beri allowed_classes ile açılacak sınıflar kısıtlanır, ama belge güvenilmez girdi için json_decode önerir.
Java'da ObjectInputStream akıştaki sınıfları kurar; Java 9 ile gelen ObjectInputFilter bir izin listesi tanımlar. .NET'te BinaryFormatter kısıtsız polimorfik açma yapar ve .NET 9'dan başlayarak kullanıldığında hata fırlatır; belge onu, yükü bağımsız bir çalıştırılabilir gibi başlatmaya benzetir. Node.js'in yerleşik JSON'u güvenlidir, ama tip bilgisini baytlara gömen bazı üçüncü taraf kütüphaneler aynı açığı Node tarafına taşır.
# Güvensiz: kullanıcı çerezini doğrudan açar sepet = pickle.loads(base64.b64decode(request.cookies["sepet"]))
Bu satırdaki tehlike bir hata mesajından anlaşılmaz. Kod çoğu zaman doğru sonucu üretir ve sessizce çalışır. Saldırgan ancak bilinen bir gadget zincirini denediği istekte fark edilir; o ana kadar açık üretimde günlerce sessiz durabilir. CWE-502 bu zayıflığı doğrulanmamış veriyi serileştirmeyi açma olarak tanımlar.
Belge, açılan verinin erişim yöntemleri atlanarak değiştirilebildiğini ve beklenmedik yöntemlerin çağrılabildiğini vurgular. Aynı belge temel savunmaları da sayar: veriyi imzalamak, açılacak sınıfları dar bir izin listesiyle sınırlamak ve açma sırasında yeni nesneyi doğrudan kurmak yerine alanları denetleyerek doldurmak. Belge bir uyarı da ekler: yeni gadget zincirleri sürekli bulunur, bu yüzden izin listesi tek başına yeterli sayılmaz.
Etkinlik 2 / 6
Doğru mu, yanlış mı?
pickle.loads çağrısı hata vermeden çalıştığı için güvenlidir.
Tespit: kod incelemesi ve statik analiz
Bu açığı en ucuz yakalayan yer koddur. Statik analiz araçları, bir açma çağrısını kullanıcı girdisiyle aynı yolda gördüğünde satırı işaretler. Aranan desen bellidir. pickle.loads, unserialize ve ObjectInputStream gibi çağrıların girdisi bir çerez, bir istek gövdesi ya da bir yükleme alanıysa, veri bir güven sınırını geçmiş demektir.
Araç bu iki noktayı, yani güvenilmez kaynağı ve tehlikeli açma çağrısını, aynı akışta birleştirir ve uyarı üretir. Aşağıdaki örnek, aracın neden uyardığını ve bir incelemecinin aynı satırı okurken neye baktığını gösterir.
# Statik analizin kaynak ve akış olarak işaretlediği iki satır veri = request.cookies["oturum"] # kaynak: kullanıcı girdisi nesne = pickle.loads(base64.b64decode(veri)) # akış: güvenilmez veri açılıyor
Örneği satır satır oku. İlk satır veriyi çerezden alır; çerez tamamen kullanıcının elindedir, bu yüzden araç onu bir kaynak olarak işaretler. İkinci satır bu veriyi base64 çözüp pickle.loads ile açar; açma, veri taşıyan bir okuma değil kod çalıştırabilen bir akıştır.
Araç iki noktayı birleştirir: güvenilmez kaynak, tehlikeli akışa ulaşıyor. Aynı mantık PHP'de unserialize, Java'da ObjectInputStream için de kurulur. Bir uyarıyı kapatmanın doğru yolu çağrıyı gizlemek ya da ayrı bir yönteme taşımak değildir; kaynağı güvenilir kılmak ya da açmayı güvenli bir biçimle değiştirmektir. Yeri değişen bir açık, kapanmış bir açık değildir.
Tespit yalnız kodda kalmaz. Çalışan sistemde de iz bırakır. Açma noktası beklenen birkaç sınıf yerine tanımadığı bir sınıfı yüklemeye kalkarsa, bu olağan dışı bir olaydır ve kaydedilmeye değer.
İzin listesi kuran bir uygulama, listede olmayan bir sınıf isteğini reddederken aynı anda bir uyarı da üretir; bu uyarı, birinin gadget denediğinin erken işaretidir. Gövdenin kendisi de iz verir. Java'nın serileştirilmiş akışı AC ED 00 05 baytlarıyla başlar, base64 hâli de rO0AB önekiyle.
Bir çerezde ya da parametrede bu öneki görmek, orada bir açma noktası olduğunu düşündürür. Benzer biçimde, açma çağrısının ardından beklenmedik bir alt süreç açılması ya da dışarı giden yeni bir bağlantı, zincirin son halkasının çalıştığını gösterebilir. Bu yüzden savunma iki kattan oluşur: açığı kodda kapatmak ve kapanana kadar şüpheli açma olaylarını izlemek. İzlemeyle bir denemeyi, sonuç vermeden önce görürsün.
Etkinlik 3 / 6
Bir statik analiz aracı, bir API'de kullanıcı çerezinin doğrudan açma çağrısına ulaştığını işaretledi. En doğru ilk yanıt hangisidir?
Savunma: güvenli biçim, dar izin listesi, imza
Savunmanın ilk kuralı basittir: mümkünse açmayı hiç kullanma. Sepet, oturum ya da API durumu için JSON gibi veri taşıyan ama kod çalıştırmayan bir biçim seç. JSON bir nesne kurar, yöntem çağırmaz. Açmadan vazgeçemiyorsan iki katman ekle.
Birincisi dar bir izin listesidir: yalnız beklenen birkaç sınıf açılır, listede olmayan sınıf reddedilir. Java'da ObjectInputFilter bu listeyi kurar. .NET'te BinaryFormatter ise hiçbir ayarla güvenli hâle gelmez; belgesi SerializationBinder ile kurulan listenin yetmeyebileceğini yazar ve System.Text.Json'a geçmeyi önerir.
İkincisi bütünlük imzasıdır: sunucu veriyi yazarken gizli bir anahtarla imzalar, okurken imzayı doğrular. İmza tutmazsa veri daha açılmadan reddedilir. İki katman birlikte hem kod çalıştırma hem kurcalama kapısını kapatır.
Altay Kargo'nun sepet servisini güvenli biçime taşıyalım. Sepet artık JSON olarak saklanır, yani açma sırasında hiçbir yöntem çalışmaz. Ayrıca sunucu, gövdeyi gizli bir anahtarla imzalar ve çerezde imzayı da taşır. Okuma anında sunucu aynı anahtarla beklenen imzayı yeniden hesaplar ve gelen imzayla karşılaştırır.
Karşılaştırma sabit sürelidir; böylece saldırgan yanıt süresindeki farktan imzayı bayt bayt çıkaramaz. İmza tutmazsa istek daha gövde çözülmeden reddedilir. Böylece hem gadget zinciri hem de çerezi elle değiştirip fiyat ya da miktar oynatma kapanır. Aşağıdaki kod bu iki katmanı birlikte gösterir.
import hmac, hashlib, json
def sepeti_oku(govde: bytes, gelen_imza: str) -> dict:
beklenen = hmac.new(ANAHTAR, govde, hashlib.sha256).hexdigest()
if not hmac.compare_digest(beklenen, gelen_imza):
raise ValueError("imza tutmuyor, istek reddedildi")
return json.loads(govde) # JSON yalnız veri kurar, yöntem çağırmazEtkinlik 4 / 6
Altay Kargo'nun sepet servisini pickle yerine güvenli bir biçime taşıyacaksın. Önereceğin çözümü ve nedenini iki cümleyle yaz.
İki katmanı karıştırma. Biçim değişikliği bir riski, imza ise başka bir riski kapatır. JSON'a geçmek kod çalıştırmayı durdurur, çünkü açma hiçbir yöntem çağırmaz. Ama veriyi hâlâ kullanıcı taşır.
İmza olmadan çerezdeki sayıyı elle değiştiren bir kullanıcı fiyatı ya da miktarı düşürebilir; sunucu bunu fark etmez. İki katman birlikte durur: biri kod çalıştırmayı, öbürü içeriği kurcalamayı engeller. Bir sonraki soru tam bu boşluğu, yani imzasız JSON'un hangi riski açık bıraktığını yoklar.
Etkinlik 5 / 6
Bir ekip sepet verisini JSON'a taşıdı ama imza eklemedi. Hangi risk hâlâ sürüyor?
Kuyruk ve önbellek: aynı risk, başka kapı
Aynı desen mesaj kuyruklarında ve önbelleklerde de görülür. Bir servis kuyruktan nesne okuyup doğrudan açarsa, kuyruğa yazma yetkisi olan herkes aynı riski taşır. Dış bir tedarikçi entegrasyonu ya da ele geçmiş bir iç hesap, kuyruğa zararlı bir bayt dizisi bırakabilir.
İç ağda olmak güveni kendiliğinden artırmaz; önemli olan yazma yetkisini kimin elde ettiğidir. Önbellek de aynı tuzağı taşır: sunucu kendi yazdığını varsayar, oysa önbelleğe erişen başka bir süreç içeriği değiştirebilir.
Doğru soru her yerde aynıdır: bu baytları açmadan önce kaynağını ve bütünlüğünü doğruladım mı? Bu soruyu sormayan her açma noktası, iç ağda olsa bile saldırganın bir sonraki hedefidir. Mesaj kuyruğu ve önbellek, iç oldukları için güvenli sayıldıklarından çoğu zaman en geç kapatılan noktalardır.
Etkinlik 6 / 6
Altay Kargo'nun sipariş kuyruğuna dış bir tedarikçi entegrasyonu da yazabiliyor. Kuyruktaki mesajları okuyan servis, mesajı doğrudan Java ObjectInputStream ile açıyor. En doğru ilk adım hangisidir?
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.
- Etkinlik 1· Baytların yolculuğu ve güven sınırı
- Etkinlik 2· Gadget zinciri: hazır sınıflardan silah
- Etkinlik 3· Tespit: kod incelemesi ve statik analiz
- Etkinlik 4· Savunma: güvenli biçim, dar izin listesi, imza
- Etkinlik 5· Savunma: güvenli biçim, dar izin listesi, imza
- Etkinlik 6· Kuyruk ve önbellek: aynı risk, başka kapı