Flask Uygulamasında Güvenli Giriş: Parola Hash'i, Oturum Çerezi ve CSRF
Küçük bir Flask yönetim paneline kullanıcı girişi eklerken parolaları doğru saklamak, oturum çerezlerini sertleştirmek, CSRF'e karşı korunmak ve deneme sayısını sınırlamak için gerekli adımlar.
İç ağda çalışan küçük bir yönetim paneli bile bir gün internete açılabilir ya da ağdaki başka bir cihaz tehlikeye girebilir. Kullanıcı girişini baştan doğru kurmak, sonradan yamamaktan çok daha ucuzdur. Bu yazıda bir Flask uygulaması için dört temel konuyu ele alıyoruz: parola saklama, oturum çerezi, CSRF ve deneme sınırlaması.
Aşağıdaki örnekler Flask 3 ve Werkzeug 3 ile uyumludur; kütüphane sürümünüz eskiyse varsayılanlar farklı olabilir, bu yüzden her adımda ne yaptığını açıkça belirtiyoruz.
1. Parolayı asla düz metin saklamayın
Veritabanında parolayı olduğu gibi tutarsanız, veritabanı yedeği ya da bir SQL açığı tüm kullanıcıların parolasını ifşa eder. Çözüm, parolayı yavaş çalışan, tuzlu bir özet (hash) fonksiyonundan geçirmektir. Werkzeug bunu hazır sunar:
from werkzeug.security import generate_password_hash, check_password_hash
h = generate_password_hash("kullanicinin-parolasi")
# -> 'scrypt:32768:8:1$...$...' (yöntem, parametreler, tuz ve özet)
check_password_hash(h, "kullanicinin-parolasi") # True
check_password_hash(h, "yanlis") # False
generate_password_hash her çağrıda rastgele bir tuz üretir; aynı parola iki kez hash'lenirse farklı çıktılar alırsınız ve bu normaldir. Doğrulama için her zaman check_password_hash kullanın, kendiniz karşılaştırma yapmayın. Kullandığınız yöntem kayıtlı özetin başında yazar; böylece ileride varsayılan algoritma değişse bile eski kayıtlar doğrulanmaya devam eder.
MD5 veya SHA-256 gibi hızlı özetleri parola için kullanmayın; saldırgan saniyede milyarlarca deneme yapabilir. Parola özeti için tasarlanmış scrypt, argon2 ya da bcrypt gibi algoritmalar bilerek yavaştır.
Sütun uzunluğunu da düşünün: bazı özetler 100 karakterin üzerinde olur. Veritabanında en az 255 karakterlik bir alan kullanın.
2. Gizli anahtarı koddan ayırın
Flask, oturum çerezini imzalamak için SECRET_KEY kullanır. Anahtar kodun içindeyse ve kod bir yere yüklenirse, herkes geçerli bir oturum üretebilir. Anahtarı ortamdan okuyun:
import os
app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"]
Rastgele bir anahtar üretmek için:
python3 -c "import secrets; print(secrets.token_hex(32))"
systemd servisinde bu değeri bir EnvironmentFile içine koyup dosya izinlerini 600 yapın (servis rehberinde örneği var). Anahtar değişirse mevcut oturumlar geçersiz olur; bu da sızıntı şüphesinde kullanışlı bir "herkesi çıkar" düğmesidir.
3. Oturum çerezini sertleştirin
Flask oturumu istemci tarafında, imzalı bir çerezde tutar. İçeriği şifreli değildir, yalnızca değiştirilemez; bu yüzden içine hassas veri koymayın. Çerez ayarları:
app.config.update(
SESSION_COOKIE_HTTPONLY=True, # JavaScript okuyamasın (varsayılan zaten True)
SESSION_COOKIE_SECURE=True, # yalnızca HTTPS ile gönderilsin
SESSION_COOKIE_SAMESITE="Lax", # başka sitelerden gelen POST'larda gönderilmesin
PERMANENT_SESSION_LIFETIME=timedelta(hours=8),
)
SESSION_COOKIE_SECURE=True yalnızca HTTPS üzerinden erişiyorsanız anlamlıdır; düz HTTP ile test ediyorsanız giriş yapamıyormuş gibi görünürsünüz. Dışarıya bir Cloudflare Tunnel ile açıyorsanız HTTPS sonlandırması Cloudflare'de olur; bu durumda uygulamanızın arkasındaki ters vekil başlıklarını doğru işlemesi (ProxyFix) gerekebilir.
Giriş başarılı olduğunda oturumu temiz başlatın:
from flask import session
session.clear()
session["user_id"] = user.id
session.permanent = True
Çıkışta da session.clear() çağırın.
4. CSRF: başka sitenin sizin adınıza işlem yapmasını önlemek
Kullanıcı panelinizde oturum açıkken kötü niyetli bir sayfayı ziyaret ederse, o sayfa sizin adresinize gizli bir form gönderebilir ve tarayıcı oturum çerezini kendiliğinden ekler. SameSite=Lax bu riski büyük ölçüde azaltır ama tek başına yeterli kabul etmeyin. Flask-WTF ile her formda rastgele bir belirteç (token) zorunlu kılabilirsiniz:
pip install flask-wtf
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
Şablonda her POST formuna belirteç ekleyin:
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
...
</form>
JavaScript ile fetch gönderiyorsanız belirteci X-CSRFToken başlığında iletin. Veriyi değiştiren her işlemin GET değil POST olduğundan emin olun; "sil" bağlantısı olarak GET kullanmak hem CSRF hem de tarayıcıların önceden yükleme özelliği nedeniyle tehlikelidir.
5. Deneme sayısını sınırlayın
Giriş formu sınırsız denemeye açıksa parola tahmini kolaylaşır. Flask-Limiter ile giriş yoluna oran sınırı koyabilirsiniz:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(get_remote_address, app=app, default_limits=[])
@app.route("/giris", methods=["GET", "POST"])
@limiter.limit("10 per minute")
def giris():
...
Ters vekil arkasındaysanız get_remote_address hep vekilin adresini görür; bu durumda gerçek istemci adresini doğru başlıktan almanız gerekir, aksi halde herkes tek bir kişi sayılır. Daha sertini isterseniz, başarısız girişleri veritabanında sayıp belirli bir eşikten sonra hesabı birkaç dakika kilitleyebilirsiniz; ama saldırganın bu yolla başkalarını kilitleyebileceğini de hesaba katın.
6. Giriş hata mesajı ve zamanlama
"Kullanıcı bulunamadı" ve "Parola yanlış" mesajlarını ayırmayın; saldırgan hangi kullanıcıların var olduğunu öğrenir. Her iki durumda da aynı genel mesajı verin:
user = Kullanici.query.filter_by(kullanici_adi=ad).first()
if not user or not user.aktif or not check_password_hash(user.parola_hash, parola):
flash("Kullanıcı adı veya parola hatalı.")
return redirect(url_for("giris"))
Burada kullanıcı bulunamadığında hash kontrolü atlandığı için yanıt süresi kısa farkla değişir. Çok sıkı bir tehdit modeliniz varsa, kullanıcı yokken de sahte bir hash ile doğrulama yaparak süreyi eşitlemeyi düşünün. Küçük bir iç panel için genellikle bu kadarı gerekmez, ama bilmek faydalıdır.
7. Hesap yönetimi ve kayıt tutma
- Kullanıcıyı silmek yerine
aktifalanınıFalseyapın; geçmiş kayıtlar (kim ne yaptı) kaybolmaz. - Başarılı ve başarısız girişleri tarih, kullanıcı adı ve istemci adresiyle kaydedin; bir şüphe halinde en değerli veri budur.
- Yönetici işlemlerini (kayıt düzeltme, silme) ayrıca denetim kaydına yazın.
- İlk yönetici parolasını kodda bırakmayın; kurulum betiğinde bir kez isteyin ve hemen değiştirtin.
Kişisel veri içeren paneller için hukuki gerekliliklerin bir kısmı da bu teknik önlemlerden geçer; ayrıntılar için KVKK kontrol listesine bakın.
Hızlı kontrol listesi
- Parolalar
generate_password_hashile saklanıyor mu? SECRET_KEYortam değişkeninden mi geliyor ve dosya izni kısıtlı mı?- Çerez
Secure,HttpOnly,SameSiteayarlı mı? - Tüm POST formlarında CSRF belirteci var mı, veriyi değiştiren işlemler GET değil mi?
- Giriş yoluna oran sınırı konmuş mu?
- Hata mesajları kullanıcı varlığını sızdırmıyor mu?
- Giriş ve yönetici işlemleri kaydediliyor mu?
Bu liste her güvenlik sorununu çözmez, ama bir iç paneli, internette sıkça saldırıya uğrayan zayıf girişlerin çok ötesine taşır. Güvenlik tek seferlik değil süreklidir: kütüphaneleri güncel tutun ve yedeklerinizi test edin.