HG PHP GüvenliğiEl Kitabı
Bölümler 18 / 38

İçindekiler/Kimlik, Yetki ve Oturum

Bölüm 18

Business Logic Vulnerabilities & Race Conditions

16 dk okuma3.280 kelimePHP 8.1+
Ön koşul

Bölüm 1 (shared-nothing modeli, güven sınırı), Bölüm 4 (girdi doğrulama), Bölüm 6 (DB işlemleri).

Bu bölümün kazanımı

İş mantığı açıklarını, "kod doğru çalışıyor ama iş kuralları istismar edilebiliyor" durumu olarak kavrayacak; race condition'ları (TOCTOU, eşzamanlılık) ve bunların neden PHP'de de geçerli olduğunu (shared-nothing miti) göreceksiniz. Bu, Kısım III'ü (Kimlik, Yetki ve Oturum) kapatan bölümdür.

1. Giriş

Şimdiye kadar incelediğimiz açıkların çoğunun ortak bir imzası vardı: kötü niyetli bir girdi (bir tırnak, bir script, bir serileştirilmiş nesne). İş mantığı açıkları bu kalıptan ayrılır. Burada girdi tamamen "geçerli" olabilir — doğru tipte, doğru formatta, tüm doğrulamalardan geçer. Sorun, uygulamanın iş kurallarının istismar edilebilmesidir. Kod, geliştiricinin yazdığı gibi çalışır; ama geliştirici bir varsayımı — "hiçbir kullanıcı negatif miktar göndermez", "ödeme adımını atlayamaz", "aynı kuponu iki kez kullanamaz" — es geçmiştir.

Bu, iş mantığı açıklarını özellikle sinsi yapar: otomatik araçlar (SAST/DAST) onları neredeyse hiç bulamaz. Bir tarayıcı, negatif miktarın bir "hata" mı yoksa "geçerli bir işlem" mi olduğunu bilemez — bunu bilmek uygulamanın niyetini anlamayı gerektirir. İş mantığı açıkları, insan zekâsı ve iş bağlamı gerektiren, güvenliğin en "el yapımı" alanıdır.

Bu bölümün ikinci yarısı, iş mantığıyla sık kesişen özel bir sınıfa ayrılmıştır: race condition'lar (yarış koşulları). Bunlar, eşzamanlı isteklerin paylaşılan durum üzerinde çakışmasından doğar ve genellikle "çift harcama", "limit atlatma" gibi doğrudan iş etkisi yaratır. 2015'teki Starbucks hediye kartı vakası (Bölüm 11), tek bir race condition'ın "sınırsız para" üretebildiğini gösteren ders kitabı örneğidir. Ayrıca PHP geliştiricilerinin sık düştüğü bir yanılgıyı — "PHP shared-nothing, o yüzden race olmaz" — bu bölümde çürüteceğiz.


2. Temel Teori

2.1. İş Mantığı Açıkları

İş mantığı açığı, uygulamanın iş akışının veya kurallarının amaçlanandan farklı biçimde kullanılabilmesidir. Tipik sınıflar:

Sınıf Örnek
Değer manipülasyonu Negatif miktar → negatif fiyat → "iade"; fiyatı istemciden almak
İş akışı atlama Ödeme adımını atlayıp doğrudan "sipariş tamamlandı"ya geçmek
Limit/kural atlatma Aynı kuponu tekrar tekrar; "kullanıcı başına 1" limitini aşmak
Durum tutarsızlığı Bir siparişi "iptal" ve "gönderildi" yapmak
Miktar/aralık istismarı Stoktan fazla sipariş; 0/negatif ile bölme mantığı

Ortak kök: geliştirici, kullanıcının uygulamayı "amaçlandığı gibi" kullanacağını varsayar. Saldırgan ise akışı beklenmedik sıralarda, beklenmedik değerlerle ve beklenmedik hızlarda kullanır. Bu, STRIDE'ın (Bölüm 2) "Tampering" ve "Elevation of Privilege" mercekleriyle yakalanabilecek bir sınıftır.

2.2. Race Condition'lar (Yarış Koşulları)

Race condition, bir işlemin sonucunun olayların zamanlamasına bağlı olduğu ve eşzamanlı erişimin beklenmeyen bir duruma yol açtığı durumdur. En yaygın biçimi TOCTOU (Time-of-Check to Time-of-Use): bir koşulu kontrol etmek ile o koşula dayanarak işlem yapmak arasında bir zaman aralığı vardır ve bu aralıkta durum değişir.

Klasik örnek — bakiye transferi:

 T1: Kart A bakiyesini KONTROL et → $5 (yeterli)   ┐
 T2: Kart A bakiyesini KONTROL et → $5 (yeterli)   ┘ ikisi de "yeterli" gördü
 T1: Kart B'ye $5 EKLE, Kart A'dan $5 DÜŞ
 T2: Kart B'ye $5 EKLE, Kart A'dan $5 DÜŞ   ← A zaten boş ama kontrol eskiydi
 Sonuç: $5'ten $10 üretildi (çift harcama / double-spend)

Kontrol (check) ile kullanım (use) atomik değilse, iki eşzamanlı istek kontrolü aynı anda geçer ve ikisi de işlemi yapar.

PHP miti — "shared-nothing, o yüzden race olmaz". Bölüm 1'de PHP'nin shared-nothing modelini gördük: her istek temiz bir bellekte başlar. Bu, bellek izolasyonu sağlar — ama eşzamanlılık güvenliği sağlamaz. Gerçek şu: bir PHP uygulaması, aynı anda birden çok isteği birden çok PHP-FPM worker'ıyla işler. Bu worker'lar paylaşılan durum (aynı veritabanı satırları, aynı dosyalar, aynı Redis anahtarları) üzerinde çalışır. Race condition tam olarak bu paylaşılan durumda oluşur. Shared-nothing, isteğin belleğini izole eder; isteğin dokunduğu veritabanını değil. Bu yüzden PHP, race condition'lara diğer diller kadar açıktır.

Race condition'ın gerektirdikleri: (1) paylaşılan bir kaynak (DB satırı, dosya, sayaç); (2) atomik olmayan bir "kontrol-sonra-işlem" dizisi; (3) eşzamanlı erişim imkânı. Saldırgan üçüncüyü sağlar (paralel istekler); savunma birinci/ikinciyi atomikleştirir.


3. Mimarisel Bakış

 İŞ MANTIĞI ATLAMA (negatif miktar):
   İstemci → { item: X, qty: -5 }   (geçerli tip, geçersiz İŞ kuralı)
        ↓  doğrulama yalnızca "sayı mı?" diye bakar → geçer
   total = qty * price = -5 * 20 = -100   → hesaba +100 "iade"
        ✗ İş kuralı (qty > 0) sunucuda zorlanmadı

 RACE CONDITION (çift harcama):
   İstek 1 ─┐
   İstek 2 ─┤ AYNI ANDA → paylaşılan DB satırı
            ↓
   [ Kontrol: bakiye >= 5? ] ← ikisi de "evet" (kontrol atomik değil)
            ↓
   [ İşlem: bakiye -= 5 ]     ← ikisi de yapar → çift harcama

 GÜVENLİ (atomik koşullu güncelleme):
   UPDATE cards SET balance = balance - 5 WHERE id = A AND balance >= 5
        ↓  DB satır kilidiyle SIRAYLA uygular
   İlk istek başarılı (1 satır etkilendi); ikinci başarısız (0 satır) → çift harcama YOK

Kritik gözlem: iş mantığında savunma, iş kuralını sunucuda zorlamaktır; race'te savunma, kontrol ile işlemi atomikleştirmektir (tek bir bölünemez işlem).


4. Güvensiz Kod

Gerçekçi bir "sepet + kupon + bakiye transferi" akışı:

<?php
// ⚠️ GÜVENSİZ — iş mantığı + race condition hataları
declare(strict_types=1);

// 1) İş mantığı: miktar ve fiyat istemciden, kural zorlanmıyor
$qty   = (int)$_POST['qty'];          // negatif olabilir
$price = (float)$_POST['price'];      // FİYAT İSTEMCİDEN — manipüle edilir!
$total = $qty * $price;               // qty=-5 → negatif "iade"

// 2) İş akışı: ödeme kontrolü olmadan sipariş tamamlanıyor
completeOrder($_POST['order_id']);    // "paid" durumu kontrol edilmiyor

// 3) Kupon: tekrar kullanım kontrolü zayıf (TOCTOU)
$coupon = getCoupon($_POST['code']);
if ($coupon && !$coupon['used']) {    // KONTROL
    applyDiscount($coupon);
    markCouponUsed($coupon['id']);    // KULLANIM — arada yarış var
}

// 4) Bakiye transferi: kontrol-sonra-işlem, atomik değil (Starbucks kalıbı)
$balance = getBalance($fromCard);     // KONTROL
if ($balance >= $amount) {
    addBalance($toCard, $amount);     // ┐ atomik değil
    subBalance($fromCard, $amount);   // ┘ eşzamanlı isteklerde çift harcama
}

5. Açığın Analizi

$qty = (int)$_POST['qty'] + $price istemciden — İki iş mantığı hatası. (1) Negatif miktar: qty=-5 geçerli bir tam sayıdır (doğrulama geçer) ama iş kuralı qty > 0 sunucuda zorlanmadığından, total negatif olur → uygulama kullanıcıya "iade" eder. (2) İstemciden fiyat: Fiyat asla istemciden alınmaz; saldırgan price=0.01 gönderir. Fiyat sunucuda, ürün kimliğinden bakılmalıdır.

completeOrder (ödeme kontrolü yok)İş akışı atlama. Sipariş, ödeme adımının başarıyla tamamlandığı kontrol edilmeden "tamamlandı"ya geçiyor. Saldırgan ödeme adımını atlayıp doğrudan tamamlama uç noktasını çağırır. Akış sırası (state machine) sunucuda zorlanmalı.

Kupon kontrolü (TOCTOU) — Kupon "kullanıldı mı?" kontrolü ile "kullanıldı işaretle" arasında bir aralık var. İki eşzamanlı istek, ikisi de used = false görüp indirimi iki kez uygular. Kontrol ve işaretleme atomik değil.

Bakiye transferi (Starbucks kalıbı) — Ders kitabı race condition. getBalance (kontrol) ile addBalance/subBalance (işlem) arasında aralık var. İki eşzamanlı transfer, ikisi de bakiyeyi "yeterli" görür ve ikisi de ekler → paradan para üretilir (çift harcama). Ayrıca add ile sub arasında da tutarsızlık penceresi var (biri başarılı biri başarısız olursa).

Kök sorun: iş kuralları sunucuda zorlanmıyor (miktar/fiyat/akış) ve kontrol-sonra-işlem dizileri atomik değil (kupon/bakiye). İlki iş mantığı, ikincisi race condition.


6. Hacker Bakış Açısı

Saldırgan bu açıkları nasıl arar?

İş kurallarını "amacının dışında" test eder. Miktar alanına negatif, sıfır, çok büyük veya ondalık değerler; fiyat/indirim alanlarına manipüle edilmiş değerler gönderir. "Bu alanı geliştirici hangi varsayımla yazdı?" diye düşünüp o varsayımı kırar. Negatif miktar, klasik ilk denemedir.

İş akışını yeniden sıralar. Çok adımlı akışları (sepet → ödeme → onay) inceleyip adımları atlar, tekrar eder veya sıra dışı çağırır: ödeme yapmadan onay uç noktasını çağırmak, bir adımı iki kez göndermek. "Sunucu her adımın önkoşulunu kontrol ediyor mu?" diye yoklar.

Limitleri ve tekilliği zorlar. "Kullanıcı başına 1", "kupon tek kullanım", "günde 3 deneme" gibi kuralları aşmaya çalışır — özellikle eşzamanlı isteklerle (aşağıya bakın).

Race condition'ı yoklar. İş etkisi olan (bakiye, stok, kupon, limit) her işlemi eşzamanlılık için test eder: aynı isteği paralel olarak (aynı anda onlarca) gönderip, kontrol-sonra-işlem penceresini yakalamaya çalışır. Bu, bug bounty'de "para/bakiye/voucher" işlemlerinin standart testidir. Starbucks vakasında Homakov, iki farklı tarayıcıyla (farklı oturum çerezleriyle) aynı transferi eşzamanlı başlatarak bunu yaptı.

PHP'yi "güvenli" sanmaz. Deneyimli saldırgan, PHP'nin shared-nothing olmasının race'i engellemediğini bilir; paylaşılan DB/dosya/Redis üzerinde yarış arar.

Savunmacı dersi: Saldırgan uygulamayı "amaçlandığı gibi" değil, tersine kullanır: yanlış değer, yanlış sıra, yanlış hız. Savunma, iş kurallarını sunucuda zorlamalı ve kritik işlemleri atomikleştirmelidir.


7. Exploit Mantığı

Bu bölüm çalıştırılabilir saldırı içermez. Bu açıkların neden yüksek etkili olduğu kavramsaldır:

  • Doğrudan finansal/işlevsel etki. İş mantığı ve race açıkları genellikle doğrudan paraya (iade, çift harcama, ücretsiz ürün), stoka veya yetkiye dokunur. Etki soyut değil, ölçülebilir ve çoğu zaman geri döndürülemezdir.
  • "Geçerli" isteklerle sömürü. Girdi geçerli olduğundan, WAF ve girdi doğrulama bunları yakalamaz; istek imza tabanlı savunmaları temiz geçer.
  • Ölçeklenebilirlik. Bir race condition otomatikleştirilince, "sınırsız para" üretimine (Starbucks'ta olduğu gibi) dönüşebilir; tek bir açık, sistematik dolandırıcılığa açılır.
  • Tespit zorluğu. İstekler meşru göründüğünden ve otomatik araçlar niyeti anlamadığından, bu açıklar uzun süre fark edilmeden kalabilir.

Araştırmacının çerçevesi: (a) iş kuralı sunucuda zorlanıyor mu (değer/akış/limit), (b) kritik işlem atomik mi (kontrol+işlem tek parça mı), (c) eşzamanlı erişim penceresi var mı. Savunma (a)'yı zorlayarak ve (b)'yi atomikleştirerek her ikisini kapatır.


8. Güvenli Kod

İş kurallarını zorlayan ve kritik işlemleri atomikleştiren sürüm:

<?php
// ✅ GÜVENLİ — iş kuralları zorlanır, işlemler atomik
declare(strict_types=1);

// 1) Miktar iş kuralı + fiyat SUNUCUDAN
$qty = filter_var($_POST['qty'] ?? null, FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'max_range' => 100],   // qty > 0 zorunlu
]);
if ($qty === false) { http_response_code(422); exit('Geçersiz miktar'); }

$product = Product::findOrFail((int)$_POST['product_id']);
$price   = $product->price;              // FİYAT SUNUCUDAN, istemciden DEĞİL
$total   = $qty * $price;                // artık negatif/manipüle olamaz

// 2) İş akışı: durum makinesi sunucuda zorlanır
$order = Order::findOrFail((int)$_POST['order_id']);
if ($order->status !== 'paid') {         // önkoşul kontrolü
    http_response_code(409);
    exit('Sipariş ödenmeden tamamlanamaz');
}
$order->markCompleted();

// 3) Kupon: atomik koşullu güncelleme (TOCTOU kapatıldı)
//    "used = 0 ise 1 yap" tek bir atomik işlem; yalnızca bir istek başarılı olur
$stmt = $pdo->prepare('UPDATE coupons SET used = 1 WHERE code = ? AND used = 0');
$stmt->execute([$_POST['code']]);
if ($stmt->rowCount() === 1) {           // yalnızca ilk istek 1 satır etkiler
    applyDiscount($_POST['code']);
}

// 4) Bakiye transferi: atomik koşullu güncelleme + transaction (Starbucks çözümü)
$pdo->beginTransaction();
try {
    // Atomik: "yeterliyse düş" — kontrol ve işlem TEK ifadede
    $sub = $pdo->prepare('UPDATE cards SET balance = balance - :amt
                          WHERE id = :from AND balance >= :amt');
    $sub->execute(['amt' => $amount, 'from' => $fromCard]);

    if ($sub->rowCount() !== 1) {        // yeterli değildi veya yarışı kaybetti
        $pdo->rollBack();
        exit('Yetersiz bakiye');
    }
    $pdo->prepare('UPDATE cards SET balance = balance + :amt WHERE id = :to')
        ->execute(['amt' => $amount, 'to' => $toCard]);
    $pdo->commit();
} catch (Throwable $e) {
    $pdo->rollBack();
    throw $e;
}

Öne çıkan modern kalıplar: - İş kuralını sunucuda zorla: Miktar > 0, fiyat sunucudan (ürün kimliğinden), akış sırası (durum makinesi) her zaman sunucuda kontrol edilir. İstemci hiçbir iş kararını belirlemez (Bölüm 1, 14). - Atomik koşullu güncelleme: UPDATE ... WHERE ... AND balance >= :amt — kontrol ve işlem tek bir bölünemez DB ifadesinde. rowCount() ile başarı doğrulanır. Race'i kaynağında kapatır. - Veritabanı transaction + izolasyon: İlişkili işlemleri tek transaction'da; gerektiğinde SELECT ... FOR UPDATE (pessimistic lock) veya Serializable/Repeatable Read izolasyon seviyesi. - Benzersizlik kısıtı (unique constraint): Tekillik (bir kupon-bir kullanıcı, bir işlem-bir kez) DB düzeyinde UNIQUE ile garanti edilir; yarış olsa bile ikinci ekleme reddedilir. - Optimistic locking: Bir version sütunuyla; güncelleme WHERE version = :expected ile yapılır, uyuşmazsa yeniden dener. - Idempotency key: Kritik işlemlere istemcinin ürettiği bir idempotency anahtarı; tekrar/eşzamanlı istekler aynı sonucu verir, çift işlem olmaz.


9. Patch Analizi

Neden işe yarar? İş kurallarını sunucuda zorlamak, istemcinin manipüle ettiği değerleri (miktar, fiyat, akış) etkisiz kılar — güven sınırı doğru yere (sunucuya) çekilir. Atomik koşullu güncelleme (UPDATE ... WHERE balance >= :amt), kontrol ve işlemi tek bir bölünemez DB işlemine indirger; veritabanının satır kilidi, eşzamanlı istekleri sıraya sokar, böylece TOCTOU penceresi hiç oluşmaz. rowCount(), işlemin gerçekten uygulandığını doğrular. Bu, race condition'ın kaynağını (atomik olmayan kontrol-işlem) ortadan kaldırır.

Eşzamanlılık savunmalarının karşılaştırması:

Yöntem Nasıl çalışır Uygun olduğu durum
Atomik koşullu UPDATE Kontrol+işlem tek ifadede; satır kilidi Sayaç/bakiye/stok düşme
SELECT ... FOR UPDATE (pessimistic) Satırı kilitle, sonra işle Karmaşık çok-adımlı işlem
Optimistic locking (version) Uyuşmazsa yeniden dene Düşük çakışma, yüksek okuma
UNIQUE kısıt DB tekilliği zorlar Tek-kullanım, tekillik
Serializable izolasyon En sıkı işlem izolasyonu Kritik tutarlılık
Idempotency key Tekrarları aynı sonuca indir Ödeme/API tekrarları

Performans: Atomik UPDATE ve unique kısıt neredeyse maliyetsizdir ve genelde uygulama-seviyesi kilitten daha hızlıdır. Pessimistic lock (FOR UPDATE) ve Serializable izolasyon, yüksek eşzamanlılıkta throughput'u düşürebilir; bu yüzden en dar kapsamda ve en kısa sürede kullanılır. Doğru yöntem, çakışma olasılığına göre seçilir; ama güvenlik-kritik para/stok işlemlerinde tutarlılık, throughput'tan önce gelir.


10. Gerçek Hayat Senaryosu

Kurgu — bir e-ticaret + cüzdan (wallet) platformu. Kullanıcılar cüzdanlarına para yükleyip ürün alıyor. "Cüzdandan cüzdana transfer" özelliği, getBalance ile kontrol edip sonra add/sub yapıyor — atomik değil. Ek olarak, "ilk sipariş %50 indirim" kuponu tek kullanımlık ama kontrolü TOCTOU'ya açık. Bir saldırgan iki şeyi birden yapıyor: (1) transfer isteğini onlarca kez paralel göndererek cüzdanında paradan para üretiyor (Starbucks kalıbı); (2) tek-kullanım kuponunu eşzamanlı isteklerle birden çok siparişe uyguluyor. İkisi de "geçerli" istekler olduğundan WAF ve girdi doğrulama temiz geçiyor. Sonuç: doğrudan finansal kayıp ve envanter tutarsızlığı. Dersler: (1) para/bakiye/stok işlemleri daima atomik olmalı; (2) tekillik DB kısıtıyla garanti edilmeli; (3) "geçerli istek" güvenli istek demek değildir — iş mantığı ayrı bir savunma katmanıdır.


11. Gerçek Vaka Analizi — Starbucks Hediye Kartı Race Condition (2015)

Egor Homakov'un (Sakurity) açıklaması, GeekWire, SecurityWeek ve Schneier analizleriyle doğrulanmıştır. İş mantığı + race condition'ın ders kitabı örneğidir.

Özet. Mayıs 2015'te güvenlik araştırmacısı Egor Homakov, Starbucks'ın hediye kartı sistemindeki bir race condition'ı açıkladı. Üç adet 5 dolarlık kart alıp ($15), kartlar arası bakiye transferindeki bir yarış koşulunu kullanarak paradan para üretti: $15 yatırımını $20'ye çevirdi ve gerçekliğini Starbucks'tan $16.70'lik alışveriş yaparak kanıtladı (sonra adil olmak ve yasal sorun yaşamamak için $10 geri yükledi). Homakov bunu "bakiye/voucher içeren siteler için çok yaygın bir hata" olarak nitelendirdi.

Teknik neden — TOCTOU'nun saf hâli. Starbucks'ın transfer mantığı kavramsal olarak şöyleydi: Kart 1'in bakiyesini kontrol et → yeterliyse Kart 2'yi artır → Kart 1'i azalt. Bu adımlar senkron (sırayla) programlanmıştı, ama çok iş parçacıklı, asenkron bir ortamda çalışıyordu — modern web sunucuları (PHP dahil) aynı anda birçok isteği işler. Homakov, iki farklı tarayıcıyla (farklı oturum çerezleriyle) aynı transferi eşzamanlı başlattı. İki istek de bakiye kontrolünü aynı anda geçti (ikisi de "yeterli" gördü, çünkü kontrol ile düşme arasında kilit yoktu) ve ikisi de hedef karta para ekledi — kaynak kartta yeterli para olmamasına rağmen. Bu, tam olarak Bölüm 2'deki TOCTOU tablosudur: kontrol ile kullanım arasındaki pencere, çift harcamaya açıldı.

Kritik ders şudur: bu bir "girdi" açığı değildi — tüm istekler geçerliydi. Açık, kontrol-sonra-işlem dizisinin atomik olmamasındaydı. Doğru çözüm — atomik koşullu güncelleme veya uygun izolasyonlu bir veritabanı transaction'ı (Schneier'in vurguladığı gibi Serializable/Repeatable Read) — pencereyi tümden kapatırdı.

Etki. Prensipte "sınırsız para": Homakov, kötü niyetli olsaydı dünya çapında sahte kartlar üretip Starbucks kredisini bitcoin karşılığı satabileceğini belirtti. Starbucks açığı ~10 günde düzeltti; ancak (disclosure tartışması olarak) araştırmacıya teşekkür yerine "dolandırıcılık" imasında bulundu — sorumlu açıklama (Bölüm 32) etiğinin de bir örneği.

Çıkarılacak dersler: 1. Kontrol-sonra-işlem atomik olmalıdır. Bakiye/stok/limit gibi paylaşılan kaynaklarda, kontrol ve işlem tek bir bölünemez işleme (atomik UPDATE, transaction, kilit) indirgenmelidir. 2. "Senkron programladım" yetmez; ortam eşzamanlıdır. PHP dahil tüm web sunucuları paralel istek işler; shared-nothing bunu değiştirmez. 3. Race condition'lar "geçerli" isteklerle çalışır. Girdi doğrulama ve WAF onları yakalamaz; savunma veri katmanının atomikliğindedir.


12. Detection

Kod incelemede: Kontrol-sonra-işlem dizilerini ve istemciden gelen iş değerlerini arayın:

# Atomik olmayan kontrol-işlem (race adayı)
grep -rnE 'getBalance|SELECT.*balance|->balance' src/   # sonra add/sub geliyorsa incele
grep -rnE 'if.*used|if.*stock|if.*balance' src/         # kontrol; işlem atomik mi?
# İstemciden iş değeri (iş mantığı adayı)
grep -rnE '\$_(GET|POST)\[.(price|amount|qty|discount|total).\]' src/
grep -rn  'beginTransaction\|FOR UPDATE\|UNIQUE' src/    # atomiklik savunmaları var mı?

Her kritik işlem için: iş kuralı (miktar/fiyat/akış) sunucuda mı zorlanıyor? Kontrol+işlem atomik mi? Tekillik DB kısıtıyla mı garanti?

SAST: İş mantığı ve race condition, SAST'ın en zayıf olduğu alandır — çünkü niyeti/eşzamanlılığı statik analiz göremez. Bazı araçlar TOCTOU kalıplarını (dosya access+open, SELECT+UPDATE) kısmen yakalar; ama çoğu iş mantığı açığı otomatik bulunamaz. İnsan incelemesi ve tehdit modelleme (Bölüm 2) burada vazgeçilmezdir.

DAST: Standart DAST iş mantığını anlamaz; ancak eşzamanlılık testi (aynı kritik isteği paralel gönderip sonucu doğrulama) race condition'ları etkili biçimde ortaya çıkarır. Bug bounty'de bu, para/bakiye işlemlerinin standart testidir.

Loglar/SIEM: Aynı kaynağı kısa sürede etkileyen eşzamanlı istekler (aynı kart/kupon/işlem üzerinde milisaniyeler içinde çoklu istek); iş kuralı ihlali işaretleri (negatif toplam, beklenmeyen bakiye artışı, aynı kuponun çoklu kullanımı); tutarsız durum geçişleri. Finansal işlemlerde tutarlılık denetimleri (reconciliation) ve anomali izleme en güçlü sinyallerdir.


13. Prevention

  • İş kurallarını sunucuda zorla: Miktar > 0, fiyat/indirim sunucudan, iş akışı sırası (durum makinesi) her zaman sunucuda (Bölüm 1, 4, 14). İstemci hiçbir iş kararı vermez.
  • Kontrol-sonra-işlemi atomikleştir: Atomik koşullu UPDATE, SELECT FOR UPDATE, uygun izolasyon seviyesi. Kritik kaynaklarda kontrol ve işlem asla ayrı olmasın.
  • Tekilliği DB'de garanti et: UNIQUE kısıtlarla (tek-kullanım kupon, tek işlem).
  • Idempotency key: Ödeme/kritik API'lerde tekrar ve eşzamanlılığa karşı.
  • Optimistic/pessimistic locking: Çakışma profiline göre.
  • Tehdit modelleme (Bölüm 2): Her iş akışını "kullanıcı bunu yanlış değerle/yanlış sırayla/eşzamanlı yaparsa ne olur?" sorusuyla incele — SAST bulamayacağı için insan analizi şart.
  • Eşzamanlılık testi: Kritik işlemleri paralel-istek testine tabi tut.

14. Mitigation

  • Acil: Etkilenen kritik işlemi atomik UPDATE/transaction ile sağlamlaştır veya geçici olarak serileştir (kuyruk/kilit); iş kuralı zorlamalarını (miktar/fiyat/akış) ekle.
  • Geçici: Tutarlılık denetimiyle (reconciliation) istismar kapsamını belirle (üretilmiş bakiye, çift kullanılmış kupon); etkilenen hesapları/işlemleri düzelt; anomali izlemeyi sıkılaştır.
  • Kalıcı: Kritik işlemleri atomik/transaction'lı kalıba taşı; tekilliği DB kısıtlarıyla garanti et; idempotency ekle; iş akışlarını tehdit modellemeden geçir; eşzamanlılık testlerini CI'a al.

15. Checklist

  • [ ] Tüm iş kuralları (miktar > 0, fiyat, indirim, akış sırası) sunucuda mı zorlanıyor?
  • [ ] Fiyat/indirim gibi değerler istemciden alınmıyor, sunucudan mı üretiliyor?
  • [ ] Çok adımlı akışların her adımı önkoşulunu (durum makinesi) kontrol ediyor mu?
  • [ ] Kontrol-sonra-işlem dizileri atomik mi (atomik UPDATE/transaction/kilit)?
  • [ ] Tekillik (tek-kullanım, tek işlem) DB UNIQUE kısıtıyla mı garanti?
  • [ ] Kritik para/stok işlemleri uygun izolasyon seviyesinde mi?
  • [ ] Ödeme/kritik API'lerde idempotency key var mı?
  • [ ] Kritik işlemler eşzamanlılık (paralel istek) testinden geçiyor mu?
  • [ ] İş akışları tehdit modellemeden (Bölüm 2) geçirildi mi?

16. Laboratuvar

Lab 18.1 — Negatif miktar. İzole ortamda total = qty * price ile bir sepet kur; qty = -5 ile negatif toplam ("iade") üret. Sunucu tarafı qty > 0 zorlamasıyla ve fiyatı sunucudan alarak düzelt.

Lab 18.2 — Race condition (çift harcama). Bir bakiye tablosu kurup getBalance+sub ile transfer yaz. Aynı isteği paralel (ör. xargs -P veya bir betikle 20 eşzamanlı istek) gönderip çift harcamayı gözlemle. Atomik UPDATE ... WHERE balance >= :amt ile düzelt ve rowCount() ile doğrula.

Lab 18.3 — Tek-kullanım kupon. TOCTOU'lu kupon kontrolü yaz; eşzamanlı isteklerle iki kez uygulat. UPDATE ... WHERE used = 0 atomik güncellemesi veya UNIQUE kısıtla düzelt.

Lab 18.4 — İş akışı atlama. Ödeme kontrolü olmadan tamamlanan bir sipariş akışı yaz; tamamlama uç noktasını doğrudan çağır. Durum makinesi (önkoşul: status = paid) ekleyip düzelt.


17. Quiz

  1. İş mantığı açığı, enjeksiyon açıklarından hangi temel açıdan farklıdır?
  2. Otomatik araçlar (SAST/DAST) iş mantığı açıklarını neden zor bulur?
  3. Negatif miktar / istemciden fiyat neden klasik iş mantığı açıklarıdır? Doğru savunma nedir?
  4. TOCTOU nedir? Kontrol ile kullanım arasındaki pencere neden tehlikelidir?
  5. "PHP shared-nothing, o yüzden race olmaz" ifadesi neden yanlıştır? Shared-nothing neyi izole eder, neyi etmez?
  6. Race condition için gereken üç koşul nedir?
  7. Atomik koşullu UPDATE ... WHERE balance >= :amt race'i nasıl kapatır? rowCount() neyi doğrular?
  8. Pessimistic ve optimistic locking arasındaki fark nedir? Hangisi ne zaman uygun?
  9. UNIQUE kısıt ve idempotency key hangi tür saldırıları önler?
  10. Starbucks vakasında kontrol-sonra-işlem penceresi çift harcamaya nasıl açıldı?
  11. Race condition'lar neden "geçerli" isteklerle çalışır ve bu WAF/girdi doğrulama için ne anlama gelir?
  12. İş akışı atlama nasıl önlenir (durum makinesi kavramıyla)?

18. Kaynakça

  • OWASP, Business Logic Vulnerability (WSTG — Testing for Business Logic); Testing for Race Conditions.
  • MITRE, CWE-841: Improper Enforcement of Behavioral Workflow; CWE-362: Concurrent Execution using Shared Resource (Race Condition); CWE-367: Time-of-check Time-of-use (TOCTOU); CWE-840: Business Logic Errors.
  • Egor Homakov (Sakurity), Race conditions on Starbucks gift cards (2015); GeekWire, SecurityWeek ve Bruce Schneier analizleri.
  • PostgreSQL / MySQL dokümantasyonu, Transaction Isolation Levels (Repeatable Read, Serializable) ve SELECT ... FOR UPDATE.
  • PHP Manual, PDO Transactions; Kroll, Race Conditions in Web Applications (kilitleme rehberi).