Hastaya bir randevu bağlantısı gönderdiğinizde, o bağlantı bir kimlik doğrulama aracına dönüşür: bağlantıyı bilen kişi randevuyu görebilir. Bu tasarım tercihinin kendisi makuldur — hastadan şifre istemek açılma oranını yerle bir eder. Ancak bu tercih, adresteki kimliğin tahmin edilemez olmasını zorunlu kılar.
Pratikte en sık yapılan hata budur ve genellikle fark edilmez.
Birinci hata: veritabanı kimliği
En yaygın biçim şudur:
https://randevu.ornek.com/detay?id=48213
Bağlantıyı alan herhangi bir kişi 48212, 48214 deneyerek
başka hastaların adına, telefonuna, hekimine ve randevu saatine ulaşır. Bu bir
saldırı bile değildir — adres çubuğunda bir rakam değiştirmektir. OWASP bunu
Insecure Direct Object Reference olarak adlandırır ve web güvenlik
listelerinde onlarca yıldır ilk sıralardadır.
İkinci hata: UUID'nin yeterli sanılması
"Sıralı kimlik kötüyse UUID kullanalım" doğru içgüdüdür ama eksik uygulanır. Şu adres güvenli görünür:
https://randevu.ornek.com/?GUID=a8faa905-477a-11f0-89df-52e18006e243
Sorun şudur: MySQL'in UUID() fonksiyonu sürüm 1
UUID üretir. Sürüm 1 rastgele değildir; zaman damgası ve sunucunun MAC
adresinden türetilir. Yukarıdaki değeri parçalarına ayıralım:
| Parça | Değer | Ne içerir |
|---|---|---|
| 1–3 | a8faa905-477a-11f0 | 60 bitlik zaman damgası (100 ns çözünürlük) |
| 4 | 89df | Saat dizisi — sunucu yeniden başlarsa değişir |
| 5 | 52e18006e243 | Sunucunun MAC adresi — bütün kayıtlarda aynı |
Yani elinde tek bir bağlantı olan kişi son parçayı zaten bilir. Aynı saniyede oluşturulmuş randevuların ilk parçaları da birbirine çok yakındır. Arama uzayı, düşünüldüğü gibi 2122 değil; pratikte kaba kuvvetle taranabilecek kadar dardır.
Sürüm 1 UUID bir tanımlayıcıdır, bir sırdır değildir. Veritabanı içinde birincil anahtar olarak kusursuzdur; adres çubuğunda güvenlik sağlamaz.
Doğru çözüm: ayrı bir belirteç
Randevunun sistem içindeki kimliği ile hastanın adresindeki değer farklı iki şey olmalıdır. Bağlantı için kriptografik olarak güvenli bir rastgele üreteçten ayrı bir belirteç üretin ve eşleşmeyi veritabanında tutun:
// 16 bayt = 128 bit gerçek rastgelelik, 32 karakter onaltılık
$token = bin2hex(random_bytes(16));
// randevu_baglantilari: token -> (kurum, sube, randevu_kimligi)
Sonuç adres şuna benzer:
https://ihbys.com/r/9f3c1ad84be207e5c6a1d0f4b83e77a2
Bu değerin tahmin edilmesi pratikte imkânsızdır ve komşu randevular hakkında
hiçbir bilgi sızdırmaz. random_bytes() (PHP), secrets
(Python) ve crypto.randomBytes() (Node) işletim sisteminin
kriptografik üretecini kullanır; rand(), mt_rand()
veya uniqid() bu iş için uygun değildir.
Bağlantı yeterli mi? Katmanlar
Tahmin edilemez bir bağlantı, tahmin yoluyla erişimi kapatır. Bağlantının başkasının eline geçmesi ihtimalini kapatmaz — telefonun ödünç verilmesi, ekran görüntüsünün paylaşılması, yanlış numaraya gönderim. Bunun için ek katmanlar gerekir:
Süre sınırı
Bağlantı sonsuza kadar yaşamamalı. Randevu tarihinden 30 gün sonra kapanan bir bağlantı, eski bir mesaj kutusunda unutulmuş bağlantının yıllar sonra kullanılmasını engeller.
İsteğe bağlı ikinci soru
Sayfa açılmadan önce doğum yılı ya da T.C. kimliğin son dört hanesi sorulabilir. Bu, telefonu ödünç alan kişiyi durdurur. Bedeli açılma oranındaki düşüştür; kurumun risk iştahına göre açılıp kapatılabilmelidir.
Hatalı deneme sayısı mutlaka sınırlanmalıdır — dört haneli bir kodun 10.000 olasılığı vardır ve sınırsız deneme hakkı verilirse birkaç dakikada tükenir.
Maskeli gösterim
Ekranda T.C. kimlik 123******01, telefon 0532 *** ** 67
biçiminde gösterilmelidir. Hasta kendi kaydını teyit edebilir; omzunun üzerinden
bakan kişi numarayı öğrenemez.
Sızıntıyı önleyen başlıklar
Randevu sayfası şunları göndermelidir:
Cache-Control: no-store, no-cache, must-revalidate, private
Referrer-Policy: no-referrer
X-Robots-Tag / meta robots: noindex, nofollow, noarchive
no-referrer özellikle önemlidir: hasta sayfadaki bir yol tarifi
bağlantısına tıkladığında, referrer başlığı olmadan hedef site randevu adresini
öğrenemez. Ayrıca robots.txt ile bu yolu kapatın — arama motorunda
dizine düşmüş bir randevu sayfası, bir veri ihlalidir.
Yazma yetkisi vermeyin
Son ve belki en önemli nokta: bağlantıyı açan kişinin randevuyu
silebilmesi tasarım hatasıdır. Bugün birçok sistemde "iptal et" düğmesi
doğrudan bir DELETE sorgusu çalıştırır.
Bunun yerine hasta bir talep oluşturmalıdır. Talep klinik personeline düşer, randevu onaylanana kadar yerinde durur. Bu yaklaşım üç sorunu birden çözer: yanlışlıkla yapılan iptalleri, kötü niyetli iptalleri ve kliniğin habersiz kalmasını.
Kontrol listesi
- Adresteki değer
random_bytes()ile üretiliyor mu? - Veritabanı kimliği ya da UUID adrese yazılıyor mu? (yazılmamalı)
- Bağlantının bir sona erme tarihi var mı?
- Hatalı doğrulama denemeleri sınırlanıyor mu?
- T.C. ve telefon maskeli mi?
noindex,no-store,no-referrergönderiliyor mu?robots.txtbu yolu kapatıyor mu?- Hasta randevuyu silebiliyor mu? (silememeli)
Bu sekiz maddenin tamamı, mevcut bir sistemde birkaç günlük bir çalışmayla düzeltilebilir. Düzeltilmediğinde bedeli, KVKK kapsamında bildirilmesi gereken bir veri ihlalidir.