Bugünlerde derin bir hayal kırıklığı içinde "Şifreyi kurtar" düğmesine hiç tıklamayan neredeyse hiç kimse yok. Şifre şüphesiz doğru gibi görünse bile, şifreyi kurtarmanın bir sonraki adımı genellikle e-postadaki bir bağlantıyı ziyaret etmek ve yeni şifreyi girmekle sorunsuz bir şekilde gerçekleşir (kimseyi kandırmayalım; az önce yazdığınız için pek yeni değildir) iğrenç düğmeye basmadan önce 1. adımda zaten üç kez).
Bununla birlikte, e-posta bağlantılarının ardındaki mantık, üzerinde dikkatle durulması gereken bir konudur; çünkü neslinin güvensiz bırakılması, kullanıcı hesaplarına yetkisiz erişime ilişkin bir dizi güvenlik açığına yol açar. Ne yazık ki burada, muhtemelen pek çok kişinin karşılaştığı, yine de güvenlik yönergelerine uymayan UUID tabanlı kurtarma URL yapısının bir örneği verilmiştir:
https://.../recover/d17ff6da-f5bf-11ee-9ce2-35a784c01695
Böyle bir bağlantının kullanılması genellikle herkesin şifrenizi alabileceği anlamına gelir ve bu kadar basittir. Bu makale, UUID oluşturma yöntemlerine derinlemesine dalmayı ve bunların uygulamalarına yönelik güvenli olmayan yaklaşımları seçmeyi amaçlamaktadır.
UUID, iki değerli özelliğe sahip sözde rastgele tanımlayıcıların oluşturulmasında yaygın olarak kullanılan 128 bitlik bir etikettir: yeterince karmaşık ve yeterince benzersizdir. Çoğunlukla bunlar, kimliğin arka uçtan ayrılması ve kullanıcıya ön uçta açıkça gösterilmesi veya genel olarak gözlemlenebilme özelliğiyle API üzerinden gönderilmesi için temel gereksinimlerdir. Kimlik = 123 (karmaşıklık) ile karşılaştırıldığında tahmin edilmesi veya kaba kuvvet uygulanmasını zorlaştırır ve oluşturulan kimlik daha önce kullanılana kopyalandığında, örneğin 0'dan 1000'e kadar rastgele bir sayıya (benzersizlik) çarpışmaları önler.
"Yeterli" kısımları aslında, öncelikle Evrensel Benzersiz Tanımlayıcı'nın bazı sürümlerinden gelir ve onu küçük kopyalama olasılıklarına açık bırakır; ancak bu, ek karşılaştırma mantığıyla kolayca hafifletilir ve zorlukla kontrol edilen koşullar nedeniyle bir tehdit oluşturmaz. onun oluşması. İkinci olarak, makalede çeşitli UUID sürümlerinin karmaşıklığının ele alınması açıklanmaktadır; diğer köşe durumları dışında genel olarak oldukça iyi olduğu varsayılmaktadır.
Veritabanı tablolarındaki birincil anahtarlar, UUID ile aynı karmaşık ve benzersiz olma ilkelerine dayanıyor gibi görünüyor. Birçok programlama dilinde ve veritabanı yönetim sisteminde yerleşik yöntemlerin geniş çapta benimsenmesiyle, UUID genellikle depolanan veri girişlerini tanımlamak için ilk tercih olarak ve genel tabloları ve normalleştirmeyle bölünmüş alt tabloları birleştirmek için bir alan olarak gelir. Belirli eylemlere yanıt olarak bir veritabanından gelen kullanıcı kimliklerinin API üzerinden gönderilmesi, fazladan geçici kimlik oluşturmaya gerek kalmadan veri akışlarının birleştirilmesi ve bunların üretim veri depolamasındakilere bağlanması sürecini kolaylaştırmak için de yaygın bir uygulamadır.
Parola sıfırlama örnekleri açısından, mimarinin büyük olasılıkla, kullanıcı düğmeyi her tıklattığında oluşturulan UUID ile veri satırlarını ekleyen böyle bir işlemden sorumlu bir tablo içermesi muhtemeldir. Kullanıcının user_id'si ile ilişkilendirildiği adrese bir e-posta göndererek ve sıfırlama bağlantısı açıldığında sahip olduğu tanımlayıcıya göre hangi kullanıcının şifresinin sıfırlanacağını kontrol ederek kurtarma işlemini başlatır. Bununla birlikte, bu tür tanımlayıcılar için kullanıcılar tarafından görülebilen güvenlik yönergeleri vardır ve belirli UUID uygulamaları, bunları değişen derecelerde başarı ile karşılar.
UUID neslinin 1. Versiyonu, 128 bitini, cihaz üreten tanımlayıcının 48 bitlik bir MAC adresini, 60 bitlik bir zaman damgasını, değeri artırmak için saklanan 14 biti ve sürüm oluşturma için 6'yı kullanmaya böler. Böylece benzersizlik garantisi, kod mantığındaki kurallardan, üretimdeki her yeni makineye değerleri doğru şekilde ataması gereken donanım üreticilerine aktarılır. Yararlı değiştirilebilir veri yükünü temsil etmek için yalnızca 60+14 bit bırakmak, özellikle arkasındaki şeffaf mantık varken tanımlayıcının bütünlüğünü bozar. Sonuç olarak oluşturulan UUID v1 sayısının dizisine bir göz atalım:
from uuid import uuid1 for _ in range(8): print(uuid1())
d17ff6da-f5bf-11ee-9ce2-35a784c01695 d17ff6db-f5bf-11ee-9ce2-35a784c01695 d17ff6dc-f5bf-11ee-9ce2-35a784c01695 d17ff6dd-f5bf-11ee-9ce2-35a784c01695 d17ff6de-f5bf-11ee-9ce2-35a784c01695 d17ff6df-f5bf-11ee-9ce2-35a784c01695 d17ff6e0-f5bf-11ee-9ce2-35a784c01695 d17ff6e1-f5bf-11ee-9ce2-35a784c01695
Görüldüğü gibi "-f5bf-11ee-9ce2-35a784c01695" kısmı sürekli aynı kalıyor. Değiştirilebilir kısım, 3514824410 - 3514824417 dizisinin 16 bitlik onaltılık gösterimidir. Üretim değerleri genellikle aradaki daha önemli boşluklarla üretildiğinden bu yüzeysel bir örnektir, dolayısıyla zaman damgasıyla ilgili kısım da değişir. 60 bitlik zaman damgası kısmı aynı zamanda tanımlayıcının daha önemli bir kısmının daha büyük bir kimlik örneği üzerinde görsel olarak değiştirildiği anlamına da gelir. Temel nokta aynı kalıyor: UUIDv1, başlangıçta rastgele görünse de kolayca tahmin edilebilir.
Verilen 8 kimlik listesinden yalnızca ilk ve son değerleri alın. Tanımlayıcılar kesin bir şekilde oluşturulduğundan, verilen ikisi arasında (onaltılık değiştirilebilir kısımlar çıkarılarak) yalnızca 6 kimlik oluşturulduğu ve bunların değerlerinin de kesin olarak bulunabileceği açıktır. Bu tür bir mantığın ekstrapolasyonu, UUID'nin bu iki sınır değerini bilmesini kaba kuvvetle engellemeyi amaçlayan Sandviç saldırısı olarak adlandırılan saldırının altında yatan kısımdır. Saldırı akışı basittir: Kullanıcı, hedef UUID üretimi gerçekleşmeden önce UUID A'yı ve hemen sonrasında UUID B'yi oluşturur. Statik 48 bit MAC parçasına sahip aynı cihazın üç neslin hepsinden sorumlu olduğunu varsayarsak, kullanıcıyı hedef UUID'nin bulunduğu A ve B arasında bir dizi potansiyel kimlikle ayarlar. Oluşturulan kimlikler ile hedef arasındaki zaman yakınlığına bağlı olarak aralık, kaba kuvvet yaklaşımıyla erişilebilen hacimlerde olabilir: boş olanlar arasında mevcut olanları bulmak için olası her UUID'yi kontrol edin.
Daha önce açıklanan şifre kurtarma uç noktasına sahip API isteklerinde, mevcut URL'yi belirten bir yanıt bulunana kadar yüzlerce veya binlerce isteğin ardışık UUID'lerle birlikte gönderilmesi anlamına gelir. Parola sıfırlamayla, kullanıcının, erişimi olmayan ancak yalnızca e-posta/giriş bilgilerini bildiği hedef hesaptaki kurtarma düğmesine mümkün olduğunca yakın bir şekilde kontrol ettiği iki hesapta kurtarma bağlantıları oluşturabildiği bir kuruluma yol açar. Kurtarma UUID'leri A ve B'ye sahip kontrollü hesaplara gönderilen mektuplar daha sonra bilinir ve hedef hesabın şifresini kurtaracak hedef bağlantı, gerçek sıfırlama e-postasına erişim olmadan kaba kuvvetle uygulanabilir.
Güvenlik açığı, kullanıcı kimlik doğrulaması için yalnızca UUIDv1'e güvenme kavramından kaynaklanmaktadır. Parolaların sıfırlanmasına erişim sağlayan bir kurtarma bağlantısı gönderildiğinde, bağlantıyı takip eden kullanıcının, bağlantıyı alması gereken kişi olarak kimliğinin doğrulandığı varsayılır. Bu, UUIDv1'in basit kaba kuvvete maruz kalması nedeniyle kimlik doğrulama kuralının başarısız olduğu kısımdır, tıpkı birisinin kapısının her iki komşu kapısının anahtarının neye benzediğini bilerek açılabilmesi gibi.
UUID'nin ilk sürümü esas olarak eski olarak kabul edilir, çünkü oluşturma mantığı rastgeleleştirilmiş bir değer olarak yalnızca tanımlayıcı boyutunun daha küçük bir kısmını kullanır. V4 gibi diğer sürümler, sürüm oluşturma için mümkün olduğunca az yer tutarak ve 122 bit'e kadar rastgele veri yükü bırakarak bu sorunu çözmeye çalışır. Genel olarak, şu anda tanımlayıcı benzersizliği gereksinimi ile ilgili "yeterli" kısmı karşıladığı ve dolayısıyla güvenlik standartlarını karşıladığı düşünülen 2^122
boğmacasına toplam olası varyasyonları getirir. Oluşturma uygulamasının rastgele kısım için kalan bitleri bir şekilde önemli ölçüde azaltması durumunda kaba kuvvet güvenlik açığına açıklık ortaya çıkabilir. Ancak üretim araçları veya kütüphaneler olmadan durum böyle mi olmalı?
Gelin biraz kriptografiye dalalım ve JavaScript'in UUID oluşturma konusundaki yaygın uygulamasına yakından bakalım. Sözde rastgele sayı üretimi için math.random
modülüne dayanan randomUUID()
işlevi şöyledir:
Math.floor(Math.random()*0x10);
Ve rastgele fonksiyonun kendisi, kısacası, bu makalenin konusunun sadece ilgi çekici kısmıdır:
hi = 36969 * (hi & 0xFFFF) + (hi >> 16); lo = 18273 * (lo & 0xFFFF) + (lo >> 16); return ((hi << 16) + (lo & 0xFFFF)) / Math.pow(2, 32);
Sözde rastgele üretim, yeterince rastgele sayı dizileri üretmek amacıyla bunun üzerinde matematiksel işlemler gerçekleştirmek için temel olarak tohum değerini gerektirir. Bu tür işlevler yalnızca buna dayalıdır, yani daha önce olduğu gibi aynı tohumla yeniden başlatılırlarsa çıktı sırası eşleşecektir. Söz konusu JavaScript işlevindeki tohum değeri, her biri 32 bitlik işaretsiz bir tam sayı olan hi ve lo değişkenlerini içerir (0'dan 4294967295'e kadar ondalık sayı). Her ikisinin bir kombinasyonu, kriptografik amaçlar için gereklidir; bu, büyük sayılarla tamsayı çarpanlarına ayırmanın karmaşıklığına dayandığından, katlarını bilerek iki başlangıç değerini kesin olarak tersine çevirmeyi neredeyse imkansız hale getirir.
İki adet 32 bitlik tamsayı bir araya gelerek, UUID'ler üreten başlatılmış fonksiyonun arkasındaki hi ve lo değişkenlerini tahmin etmek için 2^64
olası durumu bir araya getirir. Hi ve lo değerleri bir şekilde biliniyorsa, üretim fonksiyonunu kopyalamak ve çekirdek değere maruz kalma nedeniyle ürettiği ve gelecekte üreteceği tüm değerleri bilmek hiçbir çaba gerektirmez. Ancak güvenlik standartlarındaki 64 bit'in anlamlı olabilmesi için ölçülebilir bir zaman diliminde kaba kuvvete karşı toleranssız olduğu düşünülebilir. Her zaman olduğu gibi sorun spesifik uygulamadan kaynaklanmaktadır. Math.random()
hi ve lo'nun her birinden çeşitli 16 bitleri alarak 32 bitlik sonuçlara dönüştürür; ancak bunun üzerine randomUUID()
, .floor()
işlemi nedeniyle değeri bir kez daha kaydırır ve birdenbire tek anlamlı kısım artık yalnızca hi'den gelir. Üretimi hiçbir şekilde etkilemez ancak tüm üretim fonksiyonu tohumu için yalnızca 2^32
olası kombinasyon bıraktığından kriptografi yaklaşımlarının bozulmasına neden olur (lo herhangi bir değere ayarlanabileceğinden hem hi hem de lo'ya kaba kuvvet uygulanmasına gerek yoktur) değerdir ve çıkışı etkilemez).
Kaba kuvvet akışı, tek bir kimliğin edinilmesinden ve onu oluşturabilecek olası yüksek değerlerin test edilmesinden oluşur. Biraz optimizasyon ve ortalama dizüstü bilgisayar donanımıyla, yalnızca birkaç dakika sürebilir ve Sandwich saldırısında olduğu gibi sunucuya çok fazla istek gönderilmesini gerektirmez, bunun yerine tüm işlemleri çevrimdışı olarak gerçekleştirir. Böyle bir yaklaşımın sonucu, parola kurtarma örneğinde oluşturulan ve gelecekteki tüm sıfırlama bağlantılarını almak için arka uçta kullanılan oluşturma işlevi durumunun kopyalanmasına neden olur. Güvenlik açığının ortaya çıkmasını önlemeye yönelik adımlar basittir ve crypto.randomUUID()
gibi kriptografik olarak güvenli işlevlerin kullanılması için haykırır.
UUID harika bir konsept ve birçok uygulama alanında veri mühendislerinin hayatını çok kolaylaştırıyor. Ancak bu makalede kimlik doğrulamayla ilgili olarak asla kullanılmamalıdır, çünkü bu makalede üretim tekniklerinin belirli durumlarındaki kusurlar gün ışığına çıkarılmıştır. Açıkçası bu, tüm UUID'lerin güvensiz olduğu fikrine dönüşmüyor. Ancak temel yaklaşım, insanları bunları güvenlik amacıyla kullanmamaya ikna etmektir; bu, dokümantasyonda bunların hangi amaçla kullanılacağı veya nasıl oluşturulmayacağı konusunda karmaşık sınırlar koymaktan daha verimli ve daha güvenlidir.