Uygulama ve Veritabanı Katmanında PCI DSS Uyumlu Loglama – Bölüm 4

Merhaba

Seri: SOC Analistleri İçin Log İzleme Rehberi (Ödeme Kuruluşları)

  1. Log İzleme Temelleri
  2. Mevzuat, Retention ve Silme Yasağı
  3. Windows/AD ve Linux Hardening
  4. Uygulama ve Veritabanı Katmanı (Bu Yazı)
  5. Ağ Katmanı (Firewall/IDS-IPS/WAF/DDoS)
  6. Container ve Kubernetes
  7. Bulut (AWS/Azure/GCP)
  8. XDR/EDR ve Bütünsel Korelasyon

Ön Not – Kapsam ve Amaç

Bu seri, kritik logları izlemeyi ve anlamayı öğretmek amacıyla yazılmıştır. Odak noktası tek tek ürünler değil, sürecin kendisidir: hangi olayın neden önemli olduğu, nasıl toplandığı, korele edildiği ve korunduğu.

Metindeki donanım, yazılım ve uygulama örnekleri (ürün adları, Event ID’ler, konfigürasyon parametreleri, komutlar) versiyona, sürüme ve yıllara göre değişiklik gösterebilir. Bir ayarın adı değişebilir, bir ürün yaşam döngüsünü tamamlayabilir, yeni bir alternatif ortaya çıkabilir. Bu nedenle örnekleri birebir kopyalanacak reçeteler olarak değil, mantığı kavramak için birer illüstrasyon olarak okuyun; kendi ortamınıza uygularken ilgili üreticinin güncel dokümantasyonunu esas alın.

Aynı şekilde, “Ödeme Kuruluşları İçin PCI DSS, HIPAA ve Türkiye Mevzuatı Bağlamında” hazırlanan bölümler de yürürlükteki düzenlemelere dayanır ve mevzuat değiştikçe güncellenmesi gerekir. PCI DSS sürümleri, TCMB tebliğleri, KVKK/MASAK düzenlemeleri ve retention süreleri zaman içinde değişebilir. Bu rehber hukuki veya denetim danışmanlığı yerine geçmez; nihai uygulama için kurumunuzun uyumluluk ekibine, QSA’sına ve güncel resmi mevzuata başvurun.

Kısacası: amaç, süreci ve mantığı anlamaktır. Örnekler bugünün fotoğrafıdır; ilke ise kalıcıdır.

Serinin dördüncü bölümü. Sunucu katmanını hardening ettikten sonra denetimde en sık kesilen yere geliyoruz: uygulama ve veritabanı logları. Loglanmayacak veriler (CVV, PIN, PAN maskeleme), audit log JSON şeması, üç katmanlı maskeleme, production DEBUG yasağı; PostgreSQL/SQL Server/MySQL native audit ve silme yasağının DB katmanındaki teknik zorlaması.

4.22. Uygulama Katmanı – Log Tipleri ve Regülatif Değeri

İşletim sistemi ve veritabanı katmanını hardening ettiniz. Ancak denetimde en sık kesilen nokta genellikle uygulama loglarıdır çünkü burada log’u üreten, altyapı ekibi değil geliştiricidir.

Tip Regülatif Değeri Retention Kritik Uyarı
Error / Exception 🟡 Orta – availability kanıtı 1 – 2 yıl 🔴 Stack trace’te CHD/PII sızıntısı en yaygın PCI ihlalidir
Debug / Diagnostic 🔴 Risk – regülatif değeri yok Kısa (7 – 30 gün) 🔴 Production’da AÇIK BIRAKILAMAZ – DEBUG level tam request/response basar
Transaction / Event 🔴 Yüksek – 6493/TCMB zorunlu 10 yıl Ödeme emri yaşam döngüsü; append-only, silinemez
Audit 🔴 En yüksek – PCI 10.2, HIPAA §164.312(b), 6493 10 yıl Kim, ne zaman, ne yaptı. Denetimin ana kanıtı
Access / Auth 🔴 Yüksek – PCI 10.2.1.1 / 10.2.1.4 10 yıl Her CHD erişimi bireysel loglanmalı
Fraud / Risk 🔴 Yüksek – MASAK 8 yıl ŞİB üretimi, kural tetiklemesi
Performance / Metrics 🟢 Düşük 90 gün – 1 yıl Grafana/Prometheus/APM; regülatif değil

4.23. 🔴 En Kritik Kural – Loglanmayacak Veriler

PCI DSS Req. 3.2, 3.3 ve 3.4 denetimde en sık kesilen nokta budur.

Asla Loglanamaz (Hiçbir Koşulda, Hiçbir Ortamda)

Veri PCI DSS Neden
CVV / CVC / CVV2 / CID 3.3.1.1 ❌ Yetkilendirme sonrası saklanması YASAK – log dahil
Tam manyetik şerit / Track 1-2 3.3.1 ❌ Sensitive Authentication Data (SAD)
PIN / PIN Block 3.3.1.2 ❌ SAD
Parola / Passphrase 8.3.1 ❌ Hash’li bile olsa loglanmaz
Şifreleme anahtarı / Private key 3.6 ❌ Vault’ta kalır
Session token / JWT (tam) 8.x ❌ Replay riski
API key / Bearer token 8.x
MFA / OTP kodu 8.x
3D Secure CAVV / AAV 3.3.1 ❌ SAD

Maskelenerek Loglanır

Veri Maskeleme Kuralı Örnek
PAN (kart no) PCI DSS 3.4.1 – max ilk 6 + son 4 411111******1111
TCKN KVKK – maskeleme 123*****89
IBAN Maskeleme TR33****...****4567
E-posta Kısmi b***@bakicubuk.com
Telefon Kısmi 90 5** *** **34
Ad-Soyad Bağlama göre B*** Ç*** (fraud logunda tam olabilir, erişim kısıtlı)
Doğum tarihi Yıl bazlı 1985-**-**

HIPAA ek notu: 18 PHI identifier (isim, adres, SSN, tıbbi kayıt no, biyometrik veri, yüz fotoğrafı vb.) log’da açık bulunamaz. De-identification (164.514) uygulanmalıdır.

Bunu Kod Seviyesinde Nasıl Zorlarsınız

Java – Logback Masking Converter:

package com.example.log;

public class PciMaskingConverter extends MessageConverter {
    private static final Pattern PAN = Pattern.compile(
        "\\b(\\d{6})[ -]?\\d{3,9}[ -]?(\\d{4})\\b");
    private static final Pattern CVV = Pattern.compile(
        "(?i)(cvv|cvc|cvv2|cid|pin)[\"'\\s:=]+([\\d]{3,6})");
    private static final Pattern TOKEN = Pattern.compile(
        "(?i)(bearer|authorization|api[_-]?key|password|passwd|secret)[\"'\\s:=]+([^\\s,\"'}]+)");
    private static final Pattern TCKN = Pattern.compile("\\b([1-9]\\d{2})\\d{5}(\\d{2})\\b");

    @Override
    public String convert(ILoggingEvent event) {
        String msg = super.convert(event);
        msg = PAN.matcher(msg).replaceAll("$1******$2");
        msg = CVV.matcher(msg).replaceAll("$1=***REDACTED***");
        msg = TOKEN.matcher(msg).replaceAll("$1=***REDACTED***");
        msg = TCKN.matcher(msg).replaceAll("$1*****$2");
        return msg;
    }
}
<!-- logback-spring.xml -->
<conversionRule conversionWord="pcimsg"
                converterClass="com.example.log.PciMaskingConverter"/>
<appender name="AUDIT" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>/var/log/payment/audit.json</file>
  <immediateFlush>true</immediateFlush>  <!-- 🔴 Buffer'da kalmasın -->
  <encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <includeMdcKeyName>correlation_id</includeMdcKeyName>
    <includeMdcKeyName>user_id</includeMdcKeyName>
    <includeMdcKeyName>src_ip</includeMdcKeyName>
  </encoder>
</appender>

<!-- 🔴 Production'da DEBUG YASAK -->
<springProfile name="prod">
  <root level="INFO"/>
  <logger name="org.hibernate.SQL" level="OFF"/>
  <logger name="org.hibernate.type.descriptor.sql" level="OFF"/>  <!-- parametre basar! -->
  <logger name="okhttp3" level="WARN"/>  <!-- HTTP body basar! -->
</springProfile>

Python – Logging Filter:

import logging, re

class PCIMaskFilter(logging.Filter):
    PAN   = re.compile(r'\b(\d{6})[- ]?\d{3,9}[- ]?(\d{4})\b')
    CVV   = re.compile(r'(?i)\b(cvv2?|cvc|cid|pin)["\'\s:=]+\d{3,6}')
    TOKEN = re.compile(r'(?i)\b(password|secret|api[_-]?key|authorization|bearer|token)["\'\s:=]+[^\s,"\'}]+')
    TCKN  = re.compile(r'\b([1-9]\d{2})\d{5}(\d{2})\b')

    def filter(self, record):
        if isinstance(record.msg, str):
            m = record.msg
            m = self.PAN.sub(r'\1******\2', m)
            m = self.CVV.sub(r'\1=***REDACTED***', m)
            m = self.TOKEN.sub(r'\1=***REDACTED***', m)
            m = self.TCKN.sub(r'\1*****\2', m)
            record.msg = m
        return True

logger = logging.getLogger("payment")
logger.addFilter(PCIMaskFilter())

.NET – Serilog:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()          // 🔴 Prod'da Debug YASAK
    .Destructure.With()
    .Enrich.FromLogContext()
    .WriteTo.File(new CompactJsonFormatter(), "/var/log/payment/audit.json",
                  rollingInterval: RollingInterval.Day,
                  retainedFileCountLimit: null)   // 🔴 Silme yok
    .CreateLogger();

4.24. Audit Log – Zorunlu JSON Şeması

Bu, denetimin ana kanıtıdır. Aşağıdaki alanların tümü zorunludur.

{
  "timestamp": "2026-07-13T09:14:22.847Z",
  "timestamp_tz": "+03:00",
  "log_type": "AUDIT",
  "event_action": "ROLE_GRANTED",
  "outcome": "SUCCESS",

  "actor": {
    "user_id": "u-10042",
    "username": "b.cubuk",
    "display_name": "B*** Ç***",
    "email_masked": "b***@bakicubuk.com",
    "roles": ["SYS_ADMIN"],
    "auth_method": "MFA_TOTP",
    "session_id": "sess-a91f...",
    "is_service_account": false,
    "on_behalf_of": null
  },

  "source": {
    "ip": "10.20.5.44",
    "channel": "WEB_ADMIN",
    "device_id": "dev-8823",
    "geo_country": "TR"
  },

  "target": {
    "resource_type": "USER_ROLE",
    "resource_id": "u-10099",
    "old_value": "OPERATOR",
    "new_value": "PAYMENT_ADMIN"
  },

  "context": {
    "correlation_id": "corr-7f3a91e2",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
    "change_ticket": "CHG-2026-0412",
    "approval_id": "APR-8891"
  },

  "app": {
    "name": "payment-admin-api",
    "version": "3.4.1",
    "instance": "pay-app-01",
    "environment": "PROD"
  },

  "integrity": {
    "prev_hash": "a3f2...",
    "hash": "9c81..."
  }
}

Zorunlu Alanların Regülatif Karşılığı

Alan PCI DSS 10.2.2 HIPAA 6493 / TCMB
timestamp (ms + TZ)
actor.user_id (gerçek kişi) ✅ 10.2.2 ✅164.312(b)
event_action ✅ Olay tipi
outcome ✅ Başarı/başarısızlık
source.ip ✅ Olayın kaynağı
target.resource_id ✅ Etkilenen kaynak
correlation_id ✅ Uçtan uca izlenebilirlik
change_ticket ✅ 6.5.1 ✅ Değişiklik yönetimi
integrity.hash ✅ 10.3.4 ✅ 164.312(c) ✅ 5651

🔴 actor.user_id asla admin , system veya app olamaz. Paylaşılan hesap = PCI DSS 8.2.1 ihlali = doğrudan denetim bulgusu. Servis hesabı kullanılıyorsa on_behalf_of alanı doldurulmalıdır.

4.25. Ödeme Uygulaması – Event Katalogu

🔴 P1 – AUDIT (10 yıl, İmmutable)

Event Code Olay PCI DSS Windows Karşılığı
AUTH_LOGIN_SUCCESS Başarılı giriş 10.2.1.1 4624
AUTH_LOGIN_FAILURE Başarısız giriş + sebep 10.2.1.4 4625
AUTH_MFA_SUCCESS/FAIL SCA doğrulama 8.4
USER_CREATED Kullanıcı oluşturuldu 10.2.1.5 4720
USER_DELETED Kullanıcı silindi (soft) 10.2.1.5 4726
USER_ENABLED / USER_DISABLED Aktif/pasif 10.2.1.5 4722 / 4725
PASSWORD_RESET_ADMIN 🔴 Admin sıfırladı 10.2.1.5 4724
ROLE_GRANTED ⭐ Yetki/rol verildi 10.2.1.5 4728
ROLE_REVOKED ⭐ Yetki/rol alındı 10.2.1.5 4729
PRIVILEGE_ESCALATION 🔴 Ayrıcalık yükseltme 10.2.1.2 4672
CHD_ACCESSED 🔴 Kart verisine erişim 10.2.1.1
CHD_EXPORTED 🔴 Toplu veri dışa aktarım 10.2.1.1
PII_ACCESSED Kişisel veri erişimi (KVKK)
AUDIT_LOG_ACCESSED Log’a erişim 10.2.1.3 4663
AUDIT_LOG_CONFIG_CHANGED 🔴 Log ayarı değişti 10.2.1.6 4719
DELETE_ATTEMPT_BLOCKED 🔴 Silme girişimi engellendi

🔴 P1 – TRANSACTION (10 yıl, append-only – 6493)

TXN_INITIATED · TXN_AUTHORIZED · TXN_DECLINED (+red kodu) · TXN_CAPTURED · TXN_SETTLED · TXN_REVERSED (storno) · TXN_REFUNDED · TXN_CHARGEBACK · TXN_LIMIT_EXCEEDED · WALLET_TOPUP / WALLET_WITHDRAW · EMONEY_ISSUED / EMONEY_REDEEMED · RECON_RUN / RECON_MISMATCH · SETTLEMENT_FILE_GENERATED

🔴 P1 – FRAUD / AML (8 yıl – MASAK)

KYC_INITIATED / KYC_COMPLETED / KYC_FAILED · SANCTION_SCREENING · 🔴 SANCTION_HIT · FRAUD_RULE_TRIGGERED (+kural ID) · RISK_SCORE_CHANGED · 🔴 STR_GENERATED (ŞİB üretildi) · 🔴 STR_SUBMITTED_MASAK (+referans no) · ACCOUNT_FROZEN / ACCOUNT_UNFROZEN

🟠 P2 – API / Integration

API_CALL_INBOUND (TPP/merchant) · API_CALL_OUTBOUND (banka/kart şeması) · API_AUTH_FAILED · API_RATE_LIMITED · CONSENT_GRANTED / CONSENT_REVOKED (Açık Bankacılık) · WEBHOOK_SENT / WEBHOOK_FAILED · THIRD_PARTY_ACCESS

🟡 P3 – ERROR (1 – 2 yıl)

  • Stack trace maskelenmiş olmalı exception mesajında PAN/CVV sızabilir
  • error_code + correlation_id zorunlu transaction ile eşleşmeli
  • Kullanıcıya dönen mesaj ≠ log mesajı (bilgi ifşası PCI 6.5.5)

4.26. Silme Yasağı – Uygulama Katmanı

Bölüm 4.7 ve 4.17’de veritabanı ve altyapı seviyesinde ele aldığımız silme yasağı, kod seviyesinde de zorlanmalıdır.

// ❌ YASAK
transactionRepository.delete(txn);
jdbcTemplate.update("DELETE FROM transactions WHERE id = ?", id);

// ✅ DOĞRU - Soft delete
@Entity
@SQLDelete(sql = "UPDATE transactions SET is_deleted = true, " +
                 "deleted_at = now(), deleted_by = ? WHERE id = ?")
@Where(clause = "is_deleted = false")
public class Transaction { ... }

// ✅ DOĞRU - Düzeltme = yeni reversal kaydı (storno)
Transaction reversal = Transaction.reversalOf(original);
reversal.setType(TxnType.REVERSAL);
reversal.setOriginalTxnId(original.getId());
repository.save(reversal);   // Orijinal ASLA değişmez
auditLog.record("TXN_REVERSED", original.getId(), reversal.getId());

ORM Seviyesinde DELETE’i Engelleyin:

package com.example.compliance;

@Component
public class DeletePreventionInterceptor extends EmptyInterceptor {
    private static final Set IMMUTABLE = Set.of(
        "Transaction", "AuditLog", "SettlementRecord", "KycRecord");

    @Override
    public void onDelete(Object entity, Serializable id, Object[] state,
                         String[] propertyNames, Type[] types) {
        String name = entity.getClass().getSimpleName();
        if (IMMUTABLE.contains(name)) {
            auditLog.critical("DELETE_ATTEMPT_BLOCKED", name, id);
            throw new ComplianceViolationException(
                name + " silinemez - 6493/TCMB 10 yil saklama zorunlu");
        }
    }
}

Audit Log’da UPDATE de Engellenmelidir (Log İmmutable Olmalı – PCI DSS 10.3.2):

CREATE OR REPLACE FUNCTION prevent_update() RETURNS TRIGGER AS $$
BEGIN
  RAISE EXCEPTION 'Audit log degistirilemez (PCI DSS 10.3.2)';
END; $$ LANGUAGE plpgsql;

CREATE TRIGGER no_update_audit
  BEFORE UPDATE ON payment.audit_log
  FOR EACH ROW EXECUTE FUNCTION prevent_update();

Hash-Chain – Tamper-Evident Audit Log:

@Service
public class AuditChainService {
    public AuditLog append(AuditEvent event) {
        AuditLog prev = repository.findLastRecord();
        String prevHash = (prev != null) ? prev.getHash() : GENESIS_HASH;

        String payload = canonicalJson(event);   // Deterministik serileştirme
        String hash = sha256(prevHash + payload);

        return repository.save(new AuditLog(event, prevHash, hash));  // INSERT-only
    }

    // Günlük bütünlük doğrulama (cron)
    public boolean verifyChain(LocalDate from, LocalDate to) {
        String expected = repository.findHashBefore(from);
        for (AuditLog r : repository.findBetween(from, to)) {
            if (!r.getPrevHash().equals(expected)) return false;
            String computed = sha256(r.getPrevHash() + canonicalJson(r.getEvent()));
            if (!computed.equals(r.getHash())) return false;
            expected = r.getHash();
        }
        return true;
    }
}

Günün son hash’i NEZD ile damgalanır → 5651 karşılanır. Zincir kırılırsa, hangi kaydın değiştirildiği matematiksel olarak ispatlanır.

4.27. Production DEBUG Yasağı

Bu, en sık atlanan noktadır. Prod’da DEBUG açık kalırsa:

  • Hibernate TRACE → SQL parametreleri (PAN dahil) log’a basar
  • Spring Web DEBUG → tam HTTP request/response body
  • OkHttp / RestTemplate DEBUG → Authorization header, kart bilgisi
  • Jackson DEBUG → serialize edilen tüm nesne

Startup Guard – Prod’da DEBUG Varsa Uygulama Açılmasın:

@Component
@Profile("prod")
public class LogLevelGuard {
    @PostConstruct
    public void enforce() {
        LoggerContext ctx = (LoggerContext) LoggerFactory.getILoggerFactory();
        for (Logger l : ctx.getLoggerList()) {
            if (l.getLevel() != null && l.getLevel().toInt() < Level.INFO.toInt()) { throw new IllegalStateException( "PCI DSS: Production'da DEBUG/TRACE yasak -> " +
                    l.getName() + "=" + l.getLevel());
            }
        }
    }
}

Runtime’da DEBUG Açılmasını Engelleyin:

management:
  endpoints:
    web:
      exposure:
        exclude: loggers, env, heapdump, threaddump, configprops
  endpoint:
    loggers:
      enabled: false     # 🔴 Runtime'da DEBUG açılamasın

Geçici DEBUG Gerekirse (Production İncident):

  1. Change Management ticket zorunlu
  2. Süre sınırlı (max 2 saat, otomatik geri dönüş)
  3. Bu süredeki loglar ayrı, kısıtlı erişimli, kısa retention’lı store’a
  4. Açma/kapama olayı AUDIT loglanır: LOG_LEVEL_CHANGED
  5. Sonrasında maskeleme doğrulaması yapılır

4.28. Üç Katmanlı Maskeleme ve Log Toplama Mimarisi

┌─ Uygulama (Java/Python/.NET) ────────────────────────────┐
│  Logback/Serilog/logging → JSON (maskelenmiş)            │
│  MDC: correlation_id, user_id, session_id, trace_id      │
│  immediateFlush=true (buffer'da kalmaz)                  │
│  🔴 MASKELEME KATMANI 1                                  │
└──────────────┬───────────────────────────────────────────┘
               │ stdout (K8s) veya /var/log/payment/*.json
               ▼
┌─ Filebeat / Fluent Bit (sidecar veya DaemonSet) ─────────┐
│  🔴 MASKELEME KATMANI 2 (defense in depth)               │
│  - Kubernetes metadata enrich (pod, ns, node)            │
│  - mTLS ile gönderim + disk-backed queue                 │
└──────────────┬───────────────────────────────────────────┘
               │ TLS 1.2+
               ▼
┌─ Logstash / QRadar Event Collector ──────────────────────┐
│  🔴 MASKELEME KATMANI 3 (son savunma)                    │
│  - Normalizasyon (ECS / QRadar DSM)                      │
│  - Korelasyon + enrichment (GeoIP, threat intel, CMDB)   │
└──────────────┬───────────────────────────────────────────┘
               ▼
        ┌──────┴──────┐
        ▼             ▼
   ┌─────────┐   ┌────────────────────────┐
   │ HOT     │   │ Real-time Alerting     │
   │ SIEM    │   │ (SOC / QRadar Offense) │
   │ 0-12 ay │   └────────────────────────┘
   └────┬────┘
        │ günlük export + SHA-256 hash-chain + NEZD damgası
        ▼
   ┌──────────────────────────────────────┐
   │ WARM - Searchable (1-3 yıl)          │
   └────┬─────────────────────────────────┘
        ▼
   ┌──────────────────────────────────────┐
   │ 🔒 COLD - WORM / Immutable (3-10 yıl)│
   │ MinIO Object Lock COMPLIANCE Mode    │
   │ (yurt içi - TCMB veri lokasyonu)     │
   │ SİLME TEKNİK OLARAK İMKÂNSIZ         │
   └────┬─────────────────────────────────┘
        ▼
   Kontrollü İmha (İmha Komisyonu + tutanak - 10 yıl sonra ZORUNLU)

Filebeat – İkinci Maskeleme Katmanı:

processors:
  - script:
      lang: javascript
      source: >
        function process(event) {
          var msg = event.Get("message");
          if (msg) {
            msg = msg.replace(/\b(\d{6})[- ]?\d{3,9}[- ]?(\d{4})\b/g, "$1******$2");
            msg = msg.replace(/(?i)(cvv2?|cvc|pin|password|secret|token)["'\s:=]+[^\s,"'}]+/g,
                              "$1=***REDACTED***");
            event.Put("message", msg);
          }
        }
  - drop_fields:
      fields: ["http.request.body.content", "http.response.body.content",
               "http.request.headers.authorization", "card.cvv", "card.pan_full"]
      ignore_missing: true 

Üç katmanlı maskeleme (uygulama → shipper → SIEM) tesadüf değildir. Tek katman yeterli değil; birinde açık kalırsa diğeri yakalar. QSA bunu “defense in depth” olarak olumlu değerlendirir.

Kubernetes / RKE2 – Konteyner Log Kuralları

Kural Neden
Uygulama stdout/stderr‘e yazar 12-factor; dosya yazımı pod restart’ta kaybolur
🔴 Pod restart olursa log kaybolmamalı Filebeat DaemonSet + disk queue; terminationGracePeriodSeconds yeterli olmalı
Log yazılamıyorsa pod hazır sayılmamalı Readiness probe log shipper’a bağlı
kubectl logs audit kaydı üretmeli RKE2 audit policy: pods/logRequestResponse
Secret/ConfigMap log’a basılmamalı Env var dump’ı yasak
emptyDir log dizini yasak Kalıcı değil
# RKE2 audit-policy.yaml - ek kural
- level: RequestResponse
  resources:
    - group: ""
      resources: ["pods/log"]     # kubectl logs = CHD erişimi olabilir/code>

4.29. Uygulama Logu – SIEM Korelasyon Kuralları

🔴 P1

Kural A1 – Loglarda Açık Kart Verisi Tespit Edildi 🔴🔴

  message MATCHES "\b4\d{15}\b|\b5[1-5]\d{14}\b|\b3[47]\d{13}\b"   # Luhn ön filtre
  OR message MATCHES "(?i)(cvv|cvc|pin)[\"'\s:=]+\d{3,4}"
  → P1 KRİTİK: PCI DSS 3.3/3.4 İHLALİ
  → Aksiyon: Log kaynağını izole et; maskeleme bozulmuş
  → ⚠️ Bu log SİLİNEMEZ (silme yasağı) - ama erişimi kısıtla,
     olay kaydı aç, QSA'ya bildirim değerlendir

Kural A2 – Ayrıcalıklı Rol Verildi (Uygulama İçi)

  event_action = "ROLE_GRANTED"
  AND target.new_value IN ("SUPER_ADMIN","PAYMENT_ADMIN","SYS_ADMIN","AUDITOR")
  → CyberArk oturumu + Change Ticket ile eşleştir. Yoksa → P1

Kural A3 – Silme Girişimi Engellendi

  event_action = "DELETE_ATTEMPT_BLOCKED"
  → P1: Kod veya operasyonel ihlal. Kim, hangi kaydı silmeye çalıştı?
  → 6493/TCMB - silme YASAK

Kural A4 – Audit Log Zinciri Kırıldı

  integrity_check = FAILED
  → P1 KRİTİK: Tahrifat. Adli inceleme başlat.
  → Hash-chain ile hangi kaydın değiştirildiği tam olarak tespit edilir

Kural A5 – Production’da DEBUG Açıldı

  event_action = "LOG_LEVEL_CHANGED" AND new_level IN ("DEBUG","TRACE")
  AND environment = "PROD"
  → Change Ticket yoksa → P1

Kural A6 – Toplu CHD Erişimi (Data Exfiltration)

  event_action = "CHD_ACCESSED"
  COUNT > 100 in 5 dakika BY same actor.user_id
  → PCI DSS 10.2.1.1 - anormal erişim
  → MITRE: T1213

Kural A7 – MASAK Yaptırım Listesi Eşleşmesi

  event_action = "SANCTION_HIT"
  → P1: İşlem bloke, ŞİB süreci başlat

🟠 P2

Kural A8 – Yüksek Tutarlı İşlem + Yeni Alıcı + Mesai Dışı

  TXN_INITIATED AND amount > threshold
  AND beneficiary_first_seen < 24h
  AND (saat < 08:00 OR saat > 20:00)
  → Fraud incelemesi

Kural A10 – Correlation ID Zinciri Kopuk

  TXN_INITIATED var ama TXN_AUTHORIZED / TXN_DECLINED yok in 5 dk
  → İşlem kaydı eksik = TCMB izlenebilirlik ihlali

Kural A11 – Servis Hesabı İnsan Gibi Davranıyor

  actor.is_service_account = true
  AND source.channel = "WEB_ADMIN"
  → Kimlik bilgisi çalınmış olabilir

4.30. Uygulama Logu – Denetim Kanıt Listesi ve Sık Bulgular

Kanıt Listesi

  • Log Sınıflandırma Matrisi (hangi log tipi → hangi retention → hangi store)
  • Maskeleme kodu – 3 katman (app, shipper, SIEM) – kaynak kod + unit test
  • Negatif test kanıtı: kasten PAN/CVV içeren istek → log’da maskelenmiş görünüyor
  • Prod log level kanıtı – DEBUG/TRACE kapalı, Actuator loggers endpoint disabled
  • Audit log şeması – tüm zorunlu alanlar dolu (örnek kayıt)
  • Paylaşılan hesap yok kanıtı – SELECT DISTINCT actor.user_id FROM audit_logadmin / system yok
  • Hash-chain doğrulama – verifyChain() günlük çalışıyor, rapor var
  • NEZD zaman damgası – günlük log paketi damgalanmış
  • DELETE yetkisi yok – DB grant listesi + ORM interceptor kodu + trigger
  • Silme girişimi loglanıyor – DELETE_ATTEMPT_BLOCKED örneği
  • Correlation ID uçtan uca – bir işlemin tüm adımları tek trace ile izlenebiliyor
  • SIEM’e ulaştığı kanıtı – uçtan uca test
  • Log kaybı olmadığı kanıtı – pod restart / network kesintisi testi
  • 10 yıl arşiv restore testi – WORM’dan geri getirme tutanağı

En Sık Denetim Bulguları

Bulgu Sonuç
🔴 Stack trace’te açık PAN PCI DSS 3.3/3.4 fail – en yaygın bulgu
🔴 Prod’da DEBUG açık Tüm HTTP body loglanıyor → CHD sızıntısı
🔴 Audit log’da user_id = "system" PCI DSS 8.2.1 – paylaşılan hesap
🔴 Hibernate TRACE açık SQL parametrelerinde PAN
🔴 Audit log UPDATE edilebiliyor 10.3.2 – değiştirilebilir log
🔴 DELETE FROM transactions mümkün 6493 ihlali – 10 yıl saklama
🔴 Correlation ID yok Uçtan uca izlenebilirlik yok – TCMB bulgusu
🔴 Log sadece local’de, SIEM’e gitmiyor 10.3.3 – merkezileştirme yok
🔴 Pod restart’ta log kayboldu Bütünlük ihlali
🔴 Exception mesajı kullanıcıya aynen dönüyor 6.5.5 – bilgi ifşası
🟠 Retention 12 ay PCI için yeterli, 6493 için değil – 10 yıl gerekli
🟠 Timestamp’te TZ veya ms yok Korelasyon bozulur
🟠 NEZD zaman damgası yok 5651 ihlali

Uygulama Logu – Üç Cümlelik Ana İlke:

  1. Loglanmayacak veri loglanmaz (CVV, PIN, parola, token → asla; PAN → maskeli).
  2. Loglanan veri değiştirilemez ve silinemez (append-only, hash-chain, WORM, 10 yıl).
  3. Her kayıt bir gerçek kişiye ve bir işleme bağlanabilir (user_id + correlation_id).

4.31. Veritabanı Log Tipleri – Regülatif Değerlendirme

Tip Regülatif Değer Retention Kritik Not
Transaction Log (WAL/Redo) 🟠 Orta – recovery + forensic Backup zinciri boyunca ⚠️ PAN içerir! Şifrelenmeli. Denetim izi değildir
Error Log 🟡 Düşük – Orta 1 – 2 yıl Availability, DoS, corruption tespiti
Audit Log 🔴 En yüksek – PCI 10.2, HIPAA §164.312(b), 6493 10 yıl Denetimin ana kanıtı. Zorunlu
General Query Log 🔴 Risk – PAN sızıntısı Prod’da KAPALI MySQL’de tüm sorguyu düz metin basar
Slow Query Log 🟡 Düşük 90 gün – 1 yıl ⚠️ Sorgu metninde parametre → PAN sızabilir
Connection Log 🔴 Yüksek – PCI 10.2.1.1 10 yıl Kim, nereden, ne zaman bağlandı
DDL / Schema Change 🔴 Yüksek – PCI 10.2.1.7 10 yıl Change Management ile eşleşmeli
Replication Log 🟡 Kısa HA sağlığı

🔴 En kritik yanılgı: “Transaction log tutuyoruz, PCI DSS 10 karşılandı.” Yanlış. Transaction log recovery içindir; kim neyi okudu bilgisini içermez. SELECT işlemi WAL’a yazılmaz. PCI DSS 10.2.1.1 yalnızca native audit ile karşılanır.

4.32. Windows Event ID ↔ Veritabanı Karşılığı

Windows Anlam PostgreSQL SQL Server MySQL
4624 Başarılı login log_connections=onconnection authorized LOGIN_SUCCESS · Event 18453/18454 Connect (audit)
4625 Başarısız login password authentication failed LOGIN_FAILED · Event 18456 Connect + errno 1045
4672 Special privileges ALTER ROLE ... SUPERUSER · pgaudit ROLE SERVER_ROLE_MEMBER_CHANGE (sysadmin) SUPER privilege
4688 Process/komut pgaudit READ / WRITE / DDL SQL_STATEMENT (audit) Query (audit)
4720 Kullanıcı oluşturuldu CREATE ROLE CREATE LOGIN / CREATE USER CREATE USER
4726 Kullanıcı silindi DROP ROLE DROP LOGIN / DROP USER DROP USER
4722/4725 Enable / Disable ALTER ROLE ... LOGIN/NOLOGIN ALTER LOGIN ... ENABLE/DISABLE ALTER USER ... ACCOUNT LOCK
4724 Parola sıfırlama (admin) ALTER ROLE ... PASSWORD ALTER LOGIN ... WITH PASSWORD ALTER USER ... IDENTIFIED BY
4728/4732/4756 ⭐ Gruba/role üye eklendi 🔴 GRANT role TO user · ALTER ROLE ... SUPERUSER 🔴 ALTER SERVER ROLE sysadmin ADD MEMBER 🔴 GRANT ALL PRIVILEGES · GRANT SUPER
4729/4733/4757 Rolden çıkarıldı REVOKE role FROM user ALTER SERVER ROLE ... DROP MEMBER REVOKE ... FROM
4670 İzin (ACL) değişti GRANT / REVOKE ON TABLE · RLS policy SCHEMA_OBJECT_PERMISSION_CHANGE_GROUP GRANT / REVOKE ON db.table
4719 ⚠️ Audit policy değişti 🔴 ALTER SYSTEM SET pgaudit.log 🔴 ALTER SERVER AUDIT ... STATE = OFF 🔴 UNINSTALL PLUGIN audit_log
1102 ⚠️ Log temizlendi 🔴🔴 Log dosyası silme · pg_rotate_logfile + rm 🔴🔴 sp_cycle_errorlog · DROP SERVER AUDIT 🔴🔴 TRUNCATE mysql.general_log
4663 Nesne erişimi pgaudit READ on CHD table SELECT on CHD table (audit spec) audit Query SELECT

4.33. PostgreSQL – PCI DSS Konfigürasyonu

postgresql.conf</code:

#=== LOGGING - PCI DSS Req. 10 ===
logging_collector = on
log_destination = 'csvlog,syslog'          # csvlog: parse kolay | syslog: SIEM
log_file_mode = 0600                       # 🔴 Sadece postgres okuyabilir
log_rotation_age = 1d
log_truncate_on_rotation = off             # 🔴🔴 SİLME/ÜZERİNE YAZMA YASAK (6493)

#--- 10.2.1.1 / 10.2.1.4 - Bağlantı ve kimlik doğrulama
log_connections = on                       # ← Windows 4624
log_disconnections = on                    # ← Windows 4634

#--- 10.2.2 - Her kayıtta zorunlu alanlar
log_line_prefix = '%m [%p] %q%u@%d/%a from %h app=%a xid=%x vxid=%v line=%l '
# %m=timestamp(ms) %u=user(ZORUNLU) %d=database %h=client host(ZORUNLU) %x=xid

#--- 10.2.1.7 - DDL
log_statement = 'ddl'                      # Minimum: ddl | CHD şeması için: 'all'
log_min_duration_statement = 1000          # Slow query: 1sn üzeri
log_lock_waits = on                        # Deadlock / DoS göstergesi
log_replication_commands = on              # 🔴 Replikasyon = veri kopyalama
log_timezone = 'Europe/Istanbul'

#=== pgaudit - 🔴 PCI DSS için ZORUNLU (log_statement yetmez) ===
shared_preload_libraries = 'pgaudit,pg_stat_statements'

pgaudit.log = 'read,write,ddl,role,function,misc'
# read  → SELECT, COPY FROM         ← 10.2.1.1 (CHD ERİŞİMİ) 🔴
# write → INSERT,UPDATE,DELETE,TRUNCATE
# ddl   → CREATE/ALTER/DROP         ← 10.2.1.7
# role  → GRANT/REVOKE/CREATE ROLE  ← 10.2.1.5 (Windows 4728!) 🔴

pgaudit.log_catalog = off                  # pg_catalog gürültüsünü kes
pgaudit.log_parameter = on                 # 🔴 DİKKAT: parametrelerde PAN olabilir!
pgaudit.log_relation = on                  # Her tablo ayrı satır
pgaudit.log_rows = off                     # ON → satır sayısı (exfil tespiti)

#=== GÜVENLİK ===
ssl = on
ssl_min_protocol_version = 'TLSv1.2'
password_encryption = scram-sha-256        # 🔴 md5 YASAK (PCI 8.3.2)
row_security = on                          # RLS aktif
statement_timeout = 30000                  # DoS koruması

⚠️ pgaudit.log_parameter = on çelişkisi: Denetim için parametre gerekir, ama parametrede açık PAN olabilir. Çözüm sırasıyla: (1) Tokenizasyon – DB’ye hiç açık PAN girmez (en iyi), (2) log_parameter = off + uygulama katmanında maskeli audit log, (3) PostgreSQL log’unu rsyslog’da maskele.

pg_hba.conf – Bağlantı Kontrolü:

# TYPE     DATABASE   USER          ADDRESS          METHOD
hostssl    payment    app_user      10.20.10.0/24    scram-sha-256
hostssl    payment    readonly_rpt  10.20.20.0/24    scram-sha-256
hostssl    all        dba_user      10.20.5.0/24     cert clientcert=verify-full  # 🔴 DBA: mTLS
hostssl    replication repl_user    10.20.30.0/24    scram-sha-256

# 🔴 REDDEDİLENLER - hepsi loglanır
host       all        postgres      0.0.0.0/0        reject   # superuser uzaktan YASAK
host       all        all           0.0.0.0/0        reject   # Varsayılan deny

Silme Yasağı – PostgreSQL:

-- 1) Uygulama hesabından DELETE/TRUNCATE yetkisini kaldır
REVOKE DELETE, TRUNCATE ON ALL TABLES IN SCHEMA payment FROM app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA payment
  REVOKE DELETE, TRUNCATE ON TABLES FROM app_user;

-- 2) Trigger ile engelle + girişimi audit'e yaz
CREATE OR REPLACE FUNCTION compliance.prevent_delete()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO audit.violation_log(event, tbl, usr, ts, query)
  VALUES ('DELETE_ATTEMPT_BLOCKED', TG_TABLE_NAME, current_user,
          clock_timestamp(), current_query());
  RAISE EXCEPTION
    'DELETE/TRUNCATE yasak: % - 6493/TCMB 10 yil saklama zorunlu.', TG_TABLE_NAME;
END; $$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER no_delete_txn
  BEFORE DELETE OR TRUNCATE ON payment.transactions
  FOR EACH STATEMENT EXECUTE FUNCTION compliance.prevent_delete();

-- 3) Audit log UPDATE de yasak (immutable - PCI DSS 10.3.2)
CREATE TRIGGER no_update_audit
  BEFORE UPDATE ON audit.audit_log
  FOR EACH ROW EXECUTE FUNCTION compliance.prevent_update();

-- 4) 🔴 Event Trigger - koruma trigger'larının DROP edilmesini engelle
CREATE OR REPLACE FUNCTION compliance.protect_triggers()
RETURNS event_trigger AS $$
DECLARE r RECORD;
BEGIN
  FOR r IN SELECT * FROM pg_event_trigger_dropped_objects() LOOP
    IF r.object_identity LIKE '%no_delete%' OR r.object_identity LIKE '%no_update%' THEN
      RAISE EXCEPTION 'Uyumluluk trigger''i kaldirilamaz: %', r.object_identity;
    END IF;
  END LOOP;
END; $$ LANGUAGE plpgsql;

CREATE EVENT TRIGGER protect_compliance_triggers
  ON sql_drop EXECUTE FUNCTION compliance.protect_triggers();

CHD Tablosuna Özel Audit + RLS:

CREATE EXTENSION IF NOT EXISTS pgaudit;

CREATE ROLE auditor_chd NOLOGIN NOINHERIT;
GRANT SELECT, INSERT, UPDATE ON payment.cardholder_data TO auditor_chd;
ALTER SYSTEM SET pgaudit.role = 'auditor_chd';

-- RLS ile satır bazlı erişim kontrolü (KVKK + PCI 7.2)
ALTER TABLE payment.cardholder_data ENABLE ROW LEVEL SECURITY;
CREATE POLICY chd_access ON payment.cardholder_data
  FOR SELECT TO app_user
  USING (merchant_id = current_setting('app.merchant_id')::int);

Örnek Log Çıktıları:

# Bağlantı (Windows 4624)
2026-07-13 09:14:22.847 +03 [24518] baki@payment/psql from 10.20.5.44
  LOG:  connection authorized: user=baki database=payment SSL enabled

# ⭐ Rol verildi (Windows 4728 karşılığı) 🔴
2026-07-13 10:03:12.004 +03 [25022] baki@payment from 10.20.5.44
  AUDIT: SESSION,7,1,ROLE,GRANT,,,GRANT payment_admin TO svc_backdoor;,<not logged>

# CHD erişimi (PCI 10.2.1.1)
2026-07-13 11:20:05.339 +03 [25100] app_user@payment from 10.20.10.15
  AUDIT: OBJECT,12,1,READ,SELECT,TABLE,payment.cardholder_data,
  "SELECT pan_masked, exp_month FROM payment.cardholder_data WHERE merchant_id=$1",1

# 🔴 Silme girişimi engellendi
2026-07-13 12:00:00.001 +03 [25200] app_user@payment
  ERROR:  DELETE/TRUNCATE yasak: transactions - 6493/TCMB 10 yil saklama zorunlu.

WAL Arşivleme – WORM’a:

archive_mode = on
archive_command = '/usr/local/bin/wal-archive.sh %p %f'
archive_timeout = 300
wal_level = replica
#!/bin/bash
# WAL'ı şifreli + WORM'a arşivle (WAL açık PAN içerir!)
WAL_PATH=$1; WAL_FILE=$2
gpg --encrypt --recipient [email protected] -o /tmp/${WAL_FILE}.gpg ${WAL_PATH}
sha256sum /tmp/${WAL_FILE}.gpg >> /var/log/wal-hashes.txt
mc cp /tmp/${WAL_FILE}.gpg minio/pg-wal-archive/ \
   --attr "x-amz-object-lock-mode=COMPLIANCE"
rm -f /tmp/${WAL_FILE}.gpg

4.34. Microsoft SQL Server – PCI DSS Konfigürasyonu

SQL Server Audit Zorunludur (C2/Trace Deprecated ve Yetersiz).

------------------------------------------------------------
-- 1) SERVER AUDIT - hedef: dosya (WORM'a gidecek)
------------------------------------------------------------
CREATE SERVER AUDIT PCI_Audit
TO FILE (
    FILEPATH = 'E:\SQLAudit\',           -- 🔴 Ayrı disk, sınırlı erişim
    MAXSIZE = 500 MB,
    MAX_ROLLOVER_FILES = 0,              -- 🔴🔴 0 = SINIRSIZ, ÜZERİNE YAZMA YOK
    RESERVE_DISK_SPACE = ON
)
WITH (
    QUEUE_DELAY = 0,                     -- 🔴 SYNCHRONOUS - log yazılmadan işlem bitmez
    ON_FAILURE = SHUTDOWN                -- 🔴🔴 Log yazılamıyorsa SQL Server DURUR
);
ALTER SERVER AUDIT PCI_Audit WITH (STATE = ON);
GO

------------------------------------------------------------
-- 2) SERVER AUDIT SPECIFICATION - Instance seviyesi
------------------------------------------------------------
CREATE SERVER AUDIT SPECIFICATION PCI_Server_Spec
FOR SERVER AUDIT PCI_Audit
    -- 10.2.1.1 / 10.2.1.4 - Login (Windows 4624 / 4625)
    ADD (SUCCESSFUL_LOGIN_GROUP),
    ADD (FAILED_LOGIN_GROUP),
    ADD (LOGIN_CHANGE_PASSWORD_GROUP),        -- 4723/4724

    -- 10.2.1.5 - Kimlik/yetki değişimi
    ADD (SERVER_PRINCIPAL_CHANGE_GROUP),      -- 4720/4725/4726/4738
    ADD (DATABASE_PRINCIPAL_CHANGE_GROUP),
    ADD (SERVER_ROLE_MEMBER_CHANGE_GROUP),    -- 🔴🔴 4728! sysadmin'e üye ekleme
    ADD (DATABASE_ROLE_MEMBER_CHANGE_GROUP),  -- 🔴 db_owner'a üye ekleme

    -- 10.2.1.2 - Ayrıcalıklı işlemler (Windows 4672)
    ADD (SERVER_PERMISSION_CHANGE_GROUP),
    ADD (DATABASE_PERMISSION_CHANGE_GROUP),
    ADD (SCHEMA_OBJECT_PERMISSION_CHANGE_GROUP),  -- 4670

    -- 10.2.1.7 - DDL / nesne değişimi
    ADD (SCHEMA_OBJECT_CHANGE_GROUP),
    ADD (DATABASE_CHANGE_GROUP),

    -- 10.2.1.6 / 10.2.1.3 - 🔴 AUDIT'İN KENDİSİ (Windows 4719 / 1102)
    ADD (AUDIT_CHANGE_GROUP),

    -- Riskli operasyonlar
    ADD (SERVER_OPERATION_GROUP),             -- sp_configure, xp_cmdshell
    ADD (DBCC_GROUP),                         -- 🔴 DBCC (bellek okuma / bypass)
    ADD (BACKUP_RESTORE_GROUP),               -- 🔴 Veri kopyalama = exfil riski
    ADD (TRACE_CHANGE_GROUP),
    ADD (SCHEMA_OBJECT_ACCESS_GROUP);
ALTER SERVER AUDIT SPECIFICATION PCI_Server_Spec WITH (STATE = ON);
GO

------------------------------------------------------------
-- 3) DATABASE AUDIT SPEC - CHD tablosuna HER erişim (10.2.1.1)
------------------------------------------------------------
USE PaymentDB;
CREATE DATABASE AUDIT SPECIFICATION PCI_CHD_Spec
FOR SERVER AUDIT PCI_Audit
    ADD (SELECT, INSERT, UPDATE, DELETE, REFERENCES
         ON SCHEMA::payment BY public),
    ADD (SELECT, INSERT, UPDATE, DELETE
         ON SCHEMA::audit BY public),        -- 🔴 Audit tablosuna erişim (10.2.1.3)
    ADD (SCHEMA_OBJECT_CHANGE_GROUP),
    ADD (DATABASE_ROLE_MEMBER_CHANGE_GROUP);
ALTER DATABASE AUDIT SPECIFICATION PCI_CHD_Spec WITH (STATE = ON);
GO

🔴 ON_FAILURE = SHUTDOWN – ödeme kuruluşu için doğru ayar. Linux’taki disk_full_action = HALT ile aynı prensip: “Loglayamıyorsam işlem yapmam.”
🔴 MAX_ROLLOVER_FILES = 0 – sınırsız dosya, hiçbiri üzerine yazılmaz. Silme yasağının teknik karşılığı.

Silme Yasağı – SQL Server:

-- 1) DELETE/ALTER yetkisini kaldır
DENY DELETE, ALTER ON SCHEMA::payment TO app_user;
DENY DELETE ON audit.AuditLog TO public;

-- 2) INSTEAD OF trigger ile engelle + girişimi kaydet
CREATE TRIGGER trg_NoDelete_Transactions
ON payment.Transactions
INSTEAD OF DELETE
AS
BEGIN
    INSERT INTO audit.ViolationLog(EventType, TableName, LoginName, HostName, EventTime)
    SELECT 'DELETE_ATTEMPT_BLOCKED', 'payment.Transactions',
           ORIGINAL_LOGIN(), HOST_NAME(), SYSDATETIMEOFFSET();
    RAISERROR('DELETE yasak - 6493/TCMB 10 yil saklama zorunlu.', 16, 1);
    ROLLBACK TRANSACTION;
END;
GO

-- 3) 🔴 Temporal Table - otomatik değişiklik geçmişi (2016+)
CREATE TABLE payment.Transactions (
    TxnId      BIGINT NOT NULL PRIMARY KEY,
    Amount     DECIMAL(19,4) NOT NULL,
    Status     VARCHAR(20) NOT NULL,
    IsDeleted  BIT NOT NULL DEFAULT 0,          -- 🔴 Soft delete
    ValidFrom  DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
    ValidTo    DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = payment.TransactionsHistory));

DENY DELETE, UPDATE, ALTER ON payment.TransactionsHistory TO public;
GO

-- 4) 🔴🔴 Ledger Table - SQL Server 2022+ (KRİPTOGRAFİK BÜTÜNLÜK)
CREATE TABLE audit.AuditLog (
    AuditId       BIGINT IDENTITY PRIMARY KEY,
    EventTime     DATETIMEOFFSET NOT NULL,
    UserId        NVARCHAR(128) NOT NULL,
    EventAction   NVARCHAR(64) NOT NULL,
    TargetId      NVARCHAR(128) NULL,
    SourceIp      NVARCHAR(45) NULL,
    CorrelationId NVARCHAR(64) NULL
)
WITH (LEDGER = ON (APPEND_ONLY = ON));   -- 🔴🔴 UPDATE/DELETE İMKÂNSIZ
GO

-- 🔴 Digest'i ÜRET ve YURT İÇİNDE sakla (Azure/S3 otomatik storage yerine)
--    Üretilen digest değeri on-prem MinIO / yurt içi WORM'a yazılır (TCMB veri lokasyonu)
EXEC sys.sp_generate_database_ledger_digest;   -- döndürülen JSON digest yurt içinde arşivlenir
GO

-- Günlük bütünlük doğrulama - yurt içinde saklanan digest'e karşı
EXEC sys.sp_verify_database_ledger @digest = N'';
GO

🔴 SQL Server 2022 Ledger Table = ödeme kuruluşu için altın standart. APPEND_ONLY = ON ile sysadmin dahil hiç kimse kaydı değiştiremez veya silemez. QSA denetiminde en güçlü kanıttır.
⚠️ Digest’in konumu – TCMB kısıtı: Ledger’ın kriptografik digest’i (bütünlük mührü) varsayılan olarak Azure Blob veya S3’e yazılır; Microsoft dokümantasyonu bunu örnekler. Ancak ödeme kuruluşunda digest de bir denetim kaydıdır ve yurt dışı bulutta tutulamaz (veri lokasyonu). İki geçerli seçenek:

  1. Manuel/otomatik digest – sys.sp_generate_database_ledger_digest ile digest üretilip yurt içinde saklanır (on-prem S3-uyumlu Object Lock, yurt içi bulut veya WORM tape). Doğrulama sys.sp_verify_database_ledger ile bu yurt içi digest’e karşı yapılır.
  2. S3-uyumlu yurt içi endpoint – Otomatik digest storage kullanılacaksa, hedef Azure/AWS yerine yurt içindeki S3-uyumlu bir object storage (Ceph, StorageGRID, SeaweedFS, ticari appliance veya yurt içi bulut) olarak yapılandırılır konfigürasyon farkı yalnızca endpoint adresidir, digest Türkiye’de kalır.

Kısacası Ledger’ın kendisi mükemmeldir; dikkat edilecek tek nokta digest hedefinin yurt içi olmasıdır.

Kritik SQL Server Event ID’leri (Windows Application Log):

Event ID Anlam Kritiklik
18453 / 18454 / 18455 Başarılı login 🟡
18456 🔴 Başarısız login + State kodu 🟠
18456 State 5 Geçersiz kullanıcı adı 🟠 Enumeration
18456 State 8 Yanlış parola 🟠 Brute force
33205 SQL Audit olayı yazıldı
33204 🔴🔴 SQL Audit YAZAMADI (ON_FAILURE tetiklendi) 🔴 P1
825 🔴 Disk I/O retry (veri bozulması) 🔴
9002 Transaction log dolu 🔴
17063 🔴 xp_cmdshell etkinleştirildi 🔴 P1

Transaction Log – Kritik Ayarlar:

-- 🔴 FULL recovery model ZORUNLU (PITR + forensic)
ALTER DATABASE PaymentDB SET RECOVERY FULL;

-- Log backup - şifreleme ZORUNLU (tran log AÇIK PAN İÇERİR - PCI 3.5)
BACKUP LOG PaymentDB TO DISK = 'E:\Backup\PaymentDB_log.trn'
WITH COMPRESSION, CHECKSUM,
     ENCRYPTION (ALGORITHM = AES_256, SERVER CERTIFICATE = BackupCert);

-- TDE - at-rest şifreleme (PCI DSS 3.5.1)
ALTER DATABASE PaymentDB SET ENCRYPTION ON;

-- 🔴 ASLA: BACKUP LOG ... WITH TRUNCATE_ONLY
-- 🔴 ASLA: ALTER DATABASE ... SET RECOVERY SIMPLE  (log zinciri kırılır!)

4.35. MySQL / MariaDB – PCI DSS Konfigürasyonu

[mysqld]
#=== 🔴 GENERAL QUERY LOG - PRODUCTION'DA KAPALI ===
general_log = OFF          # 🔴🔴 AÇIK PAN BASAR - PCI DSS 3.4 İHLALİ

#=== SLOW QUERY LOG ===
slow_query_log = ON
long_query_time = 1
# ⚠️ Sorgu metninde parametre → PAN sızabilir. 0600, SIEM'de maskele.

#=== 🔴 AUDIT LOG - PCI DSS için ZORUNLU ===
plugin-load-add = server_audit.so
server_audit = FORCE_PLUS_PERMANENT   # 🔴 Plugin KALDIRILAMAZ
server_audit_logging = ON
server_audit_events = CONNECT,QUERY,TABLE,QUERY_DDL,QUERY_DML,QUERY_DCL
#   CONNECT   → 4624/4625
#   QUERY_DCL → GRANT/REVOKE  ← 4728! 🔴
#   TABLE     → tablo erişimi ← 10.2.1.1 (CHD)
server_audit_file_rotations = 0        # 🔴 0 = SINIRSIZ, silme yok
server_audit_excl_users =              # 🔴 BOŞ - kimse muaf değil!

#=== BINARY LOG ===
log_bin = /var/log/mysql/binlog
binlog_format = ROW                    # 🔴 ROW: her satır değişimi (forensic)
sync_binlog = 1
binlog_encryption = ON                 # 🔴 binlog AÇIK PAN İÇERİR (8.0.14+)

#=== GÜVENLİK ===
require_secure_transport = ON          # 🔴 TLS zorunlu
local_infile = OFF                     # 🔴 LOAD DATA LOCAL - exfil vektörü
secure_file_priv = /var/lib/mysql-files   # 🔴 SELECT ... INTO OUTFILE kısıtla
default_authentication_plugin = caching_sha2_password

Silme Yasağı – MySQL:

REVOKE DELETE, DROP ON payment.* FROM 'app_user'@'10.20.10.%';
REVOKE DELETE, DROP, ALTER ON audit.* FROM 'app_user'@'10.20.10.%';

DELIMITER $$
CREATE TRIGGER trg_no_delete_txn
BEFORE DELETE ON payment.transactions
FOR EACH ROW
BEGIN
    INSERT INTO audit.violation_log(event, tbl, usr, ts)
    VALUES('DELETE_ATTEMPT_BLOCKED', 'transactions', USER(), NOW(6));
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'DELETE yasak - 6493/TCMB 10 yil saklama zorunlu';
END$$
DELIMITER ;

⚠️ MySQL’de TRUNCATE trigger’ı atlar. Bu yüzden DROP ve ALTER yetkisi de kaldırılmalıdır trigger tek başına yeterli değildir.

4.36. Veritabanı – SIEM Korelasyon Kuralları

🔴 P1 – Anında Alarm

Kural DB1 – superuser / sysadmin Yetkisi Verildi (Windows 4728 Muadili)

  PostgreSQL: pgaudit CLASS=ROLE AND statement MATCHES
              "GRANT.*(superuser|pg_read_all_data)|ALTER ROLE.*SUPERUSER"
  MSSQL:      audit_action_id = 'SRMC' (SERVER_ROLE_MEMBER_CHANGE)
              AND target_server_principal_name = 'sysadmin'
  MySQL:      audit QUERY MATCHES "GRANT (ALL|SUPER|SYSTEM_USER).*"
  → P1: SOC + DBA Müdürü. CyberArk oturumu + Change Ticket ile eşleştir.
  → MITRE: T1098 (Account Manipulation)

Kural DB2 – Veritabanı Audit KAPATILDI 🔴🔴 (Windows 4719/1102 Muadili)

  PostgreSQL: "ALTER SYSTEM SET (pgaudit|logging_collector)" OR "DROP EXTENSION pgaudit"
  MSSQL:      audit_action_id = 'AUSC' (AUDIT_CHANGE_GROUP)
              OR "ALTER SERVER AUDIT.*STATE\s*=\s*OFF|DROP SERVER AUDIT"
              OR EventID = 33204   (audit yazamadı)
  MySQL:      "UNINSTALL PLUGIN server_audit" OR "SET GLOBAL server_audit_logging=OFF"
  → P1 KRİTİK: PCI DSS 10.7 - Kritik güvenlik kontrolü arızası
  → MITRE: T1562.001 (Impair Defenses)

Kural DB3 – DELETE / TRUNCATE Girişimi (Silme Yasağı İhlali) 🔴🔴

  event = "DELETE_ATTEMPT_BLOCKED"
  OR statement MATCHES "(?i)(DELETE FROM|TRUNCATE TABLE|DROP TABLE)\s+(payment|audit)\."
  → P1: 6493/TCMB İHLALİ GİRİŞİMİ
  → Kim, hangi tablo, hangi WHERE koşulu? Kasıt mı, hata mı?
  → MITRE: T1485 (Data Destruction)

Kural DB4 – Toplu CHD Çekimi (Data Exfiltration) 🔴

  pgaudit CLASS=READ AND object_name = "payment.cardholder_data" AND rows > 1000
  OR MSSQL: SELECT on payment schema AND affected_rows > 1000
  OR MySQL: "SELECT.*INTO OUTFILE"
  AND (saat < 08:00 OR saat > 20:00 OR hafta_sonu)
  → PCI DSS 10.2.1.1 - anormal kart verisi erişimi
  → MITRE: T1213

Kural DB5 – SQL Injection Göstergesi

  statement MATCHES "(?i)(union\s+(all\s+)?select|'\s*or\s*'?1'?\s*=\s*'?1|
                     ;\s*(drop|delete|update)|xp_cmdshell|sp_oacreate|
                     information_schema\.(tables|columns)|pg_sleep\(|benchmark\(|
                     waitfor\s+delay|load_file\(|into\s+(out|dump)file)"
  OR error_code IN (mysql:1064, mssql:105/102, pg:42601) AND COUNT > 5 in 1 dk
  → P1: WAF logu ile korele et.
  → MITRE: T1190

Kural DB6 – OS Komut Çalıştırma (DB → OS Pivot)

  MSSQL:      "xp_cmdshell" OR "sp_configure.*xp_cmdshell.*1" OR "sp_OACreate"
              OR EventID = 17063
  PostgreSQL: "COPY.*FROM PROGRAM" OR "CREATE.*LANGUAGE.*plpythonu"
              OR "pg_read_file" OR "pg_ls_dir"
  MySQL:      "LOAD_FILE" OR "INTO OUTFILE.*\.php" OR "sys_exec"
  → P1 KRİTİK: Veritabanından işletim sistemine geçiş
  → MITRE: T1059

Kural DB7 – Backup / Veri Kopyalama (Exfil) 🔴

  MSSQL:      audit_action = BACKUP_RESTORE_GROUP AND destination NOT IN (approved_paths)
  PostgreSQL: "pg_dump" OR "COPY.*TO" OR yetkisiz replication slot
  MySQL:      "mysqldump" OR yetkisiz "FLUSH TABLES WITH READ LOCK"
  → MITRE: T1005 / T1030

🟠 P2 – Aynı Gün

Kural DB8 – Veritabanı Brute Force

  Failed login COUNT >= 10 in 5 dk BY same src_ip
  MSSQL: EventID 18456 (State 8) · PG: "password authentication failed" · MySQL: errno 1045
  Password spray varyantı: COUNT DISTINCT username >= 10 in 15 dk BY same src_ip
  → MITRE: T1110

Kural DB9 – Yetkisiz Kaynaktan DB Bağlantısı

  connection_authorized
  AND src_ip NOT IN (app_subnet, dba_subnet, replication_subnet, monitoring_subnet)
  → PCI DSS 1.3 - ağ segmentasyonu ihlali

Kural DB10 – Şema Değişikliği (DDL), Change Ticket Yok

  pgaudit CLASS=DDL OR MSSQL SCHEMA_OBJECT_CHANGE_GROUP OR MySQL QUERY_DDL
  AND NO matching change_ticket in CMDB within ±4 saat
  → Onaysız değişiklik = uyumsuzluk bulgusu (PCI 6.5.1)

Kural DB12 – Servis Hesabı Anormal Davranış

  actor = app_user (servis hesabı)
  AND client_app IN ("SQL Server Management Studio","DBeaver","pgAdmin","HeidiSQL")
  → Servis hesabı kimlik bilgisi çalınmış olabilir

Kural DB15 – Recovery Model Değişti (Log Zinciri Kırma) 🔴

  MSSQL: "ALTER DATABASE.*SET RECOVERY SIMPLE" OR "BACKUP LOG.*WITH TRUNCATE_ONLY"
  → Transaction log zincirini kırma = anti-forensics
  → MITRE: T1070

4.37. 🔴 DB Katmanında PAN Sızıntısı – Özel Risk

Bu, uygulamadan daha tehlikelidir, çünkü DB logları sorgu metnini ham basar.

Risk Kaynağı Nerede Çözüm
pgaudit.log_parameter = on PostgreSQL Tokenizasyon veya off + uygulama audit’i
log_statement = 'all' PostgreSQL Sadece ddl ; all gerekiyorsa maskele
Slow query log Hepsi Sorgu metni içinde literal PAN
general_log = ON MySQL 🔴 Prod’da ASLA açma
Extended Events sql_text SQL Server XEL erişimi kısıtla, SIEM’de maskele
Error log – constraint violation Hepsi 🔴 DETAIL: Key (pan)=(4111111111111111) already exists
WAL / binlog / tran log Hepsi Şifreleme zorunlu (TDE, binlog_encryption)
Deadlock report SQL Server Sorgu parametresi içerir

rsyslog ile Son Savunma Katmanı:

# /etc/rsyslog.d/50-db-mask.conf
if ($programname == 'postgres' or $programname == 'mysqld') then {
    set $!msg = replace($msg, "([0-9]{6})[0-9]{3,9}([0-9]{4})", "$1******$2");
    action(type="omfwd" target="siem.example.local" port="6514"
           protocol="tcp" StreamDriver="gtls" StreamDriverMode="1")
}

Ama asıl çözüm: DB’ye hiç açık PAN girmemesi. Tokenizasyon (Vault Transit veya dedicated token vault) ile PAN veritabanına hiç ulaşmaz → log sızıntısı riski kökten kalkar, PCI DSS scope küçülür, denetim kolaylaşır.

4.38. Veritabanı Karşılaştırması ve Denetim Kanıtları

Hangi DB Neyi Sağlıyor?

Yetenek PostgreSQL SQL Server MySQL / MariaDB
Native audit pgaudit (extension) ✅ SQL Server Audit (native) Enterprise Audit (ticari) / MariaDB plugin
Log yazamazsa dur ⚠️ Yok (workaround) ON_FAILURE = SHUTDOWN ⚠️ Yok
Append-only / immutable ⚠️ Trigger + REVOKE Ledger Table (2022+) 🏆 ⚠️ Trigger (TRUNCATE bypass eder)
Otomatik geçmiş ⚠️ Trigger ile ✅ Temporal Table ⚠️ Manuel
Kriptografik bütünlük ⚠️ Harici hash-chain Ledger digest 🏆
Satır bazlı güvenlik ✅ RLS ✅ Row-Level Security ⚠️ View ile
At-rest şifreleme ⚠️ Disk/FS seviyesi ✅ TDE ✅ TDE (8.0)
Superuser bypass ⚠️ Superuser her şeyi yapar Ledger’ı sysadmin bile bozamaz 🏆 ⚠️

Ödeme kuruluşu için değerlendirme:

  • SQL Server 2022 Ledger = kriptografik bütünlük + ON_FAILURE=SHUTDOWN → denetim açısından en güçlü
  • PostgreSQL = açık kaynak, esnek; ancak immutability manuel kurgulanmalı (trigger + REVOKE + WORM arşiv + harici hash-chain)
  • MySQL = TRUNCATE trigger’ı atladığı için ekstra dikkat; audit plugin ticari

Denetim Kanıt Listesi – Veritabanı

  • PostgreSQL: SHOW shared_preload_libraries; → pgaudit var
  • PostgreSQL: SHOW pgaudit.log;read,write,ddl,role var
  • PostgreSQL: SHOW log_truncate_on_rotation;off
  • MSSQL: SELECT * FROM sys.server_auditsis_state_enabled=1 , on_failure_desc='SHUTDOWN' , max_rollover_files=0
  • MSSQL: sys.sp_verify_database_ledger → yurt içinde saklanan digest’e karşı bütünlük OK (digest hedefi Azure/S3 değil, on-prem/yurt içi)
  • MySQL: SHOW VARIABLES LIKE 'general_log';OFF (prod)
  • MySQL: SHOW PLUGINS;server_audit ACTIVE FORCE_PLUS_PERMANENT
  • DELETE yetkisi yok kanıtı: PG \dp payment.transactions · MSSQL sys.database_permissions · MySQL SHOW GRANTS
  • Silme girişimi engellendi örneği – audit.violation_log kaydı
  • Superuser/sysadmin listesi – ≤5, tümü named, paylaşılan hesap yok
  • TDE / at-rest şifreleme + backup encryption aktif kanıtı
  • PAN sızıntısı testi: kasten PAN içeren sorgu → log’da maskelenmiş/tokenize
  • PITR restore testi tutanağı (yılda min. 1)
  • DB logu SIEM’de görünüyor – uçtan uca test kanıtı

En Sık Denetim Bulguları – Veritabanı

Bulgu Sonuç
🔴 pgaudit / SQL Audit yok, sadece log_statement var PCI 10.2.1.1 fail – SELECT loglanmıyor
🔴 “Transaction log tutuyoruz” savunması Transaction log ≠ audit log. Fail
🔴 MySQL general_log = ON prod’da Açık PAN log’da → PCI 3.4 fail
🔴 Slow query log’da açık PAN PCI 3.4 fail
🔴 sa / postgres / root paylaşımlı kullanılıyor PCI 8.2.1 fail
🔴 Uygulama hesabında DELETE yetkisi var 6493 – 10 yıl saklama riski
🔴 ON_FAILURE = CONTINUE (MSSQL) Log yazamasa da çalışır → izlenebilirlik yok
🔴 MAX_ROLLOVER_FILES > 0 Eski audit dosyaları üzerine yazılıyor = silme
🔴 RECOVERY SIMPLE (MSSQL prod) PITR yok, forensic yok
🔴 TDE yok, backup şifresiz WAL/backup’ta açık PAN – PCI 3.5 fail
🟠 Audit dosyası local’de, SIEM’e gitmiyor 10.3.3 – merkezileştirme yok
🟠 md5 password_encryption (PG) PCI 8.3.2 – scram-sha-256 gerekli
🟠 DBA hesabı MFA’sız PCI 8.4.2

Veritabanı – Beş Maddelik Ana İlke:

  1. Transaction log ≠ Audit log. Recovery ve denetim ayrı şeylerdir; ikisi de gerekir.
  2. CHD’ye her SELECT loglanmalı – bunu yalnızca native audit yapar (pgaudit / SQL Server Audit / MySQL Audit).
  3. DB’ye açık PAN girmesin (tokenizasyon) – o zaman log sızıntısı riski kökten biter.
  4. DELETE teknik olarak imkansız olmalı – REVOKE + trigger + Ledger/Temporal + WORM arşiv (dört katman).
  5. Log yazılamıyorsa veritabanı durmalı (ON_FAILURE=SHUTDOWN / disk_full_action=HALT).

 

 

Bir yanıt yazın

Başa Dön