Merhaba
Seri: SOC Analistleri İçin Log İzleme Rehberi (Ödeme Kuruluşları)
- Log İzleme Temelleri
- Mevzuat, Retention ve Silme Yasağı
- Windows/AD ve Linux Hardening
- Uygulama ve Veritabanı Katmanı (Bu Yazı)
- Ağ Katmanı (Firewall/IDS-IPS/WAF/DDoS)
- Container ve Kubernetes
- Bulut (AWS/Azure/GCP)
- 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_idaslaadmin,systemveyaappolamaz. Paylaşılan hesap = PCI DSS 8.2.1 ihlali = doğrudan denetim bulgusu. Servis hesabı kullanılıyorsaon_behalf_ofalanı 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_idzorunlu 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):
- Change Management ticket zorunlu
- Süre sınırlı (max 2 saat, otomatik geri dönüş)
- Bu süredeki loglar ayrı, kısıtlı erişimli, kısa retention’lı store’a
- Açma/kapama olayı AUDIT loglanır:
LOG_LEVEL_CHANGED - 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/log → RequestResponse |
| 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
loggersendpoint 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_log→admin/systemyok - 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:
- Loglanmayacak veri loglanmaz (CVV, PIN, parola, token → asla; PAN → maskeli).
- Loglanan veri değiştirilemez ve silinemez (append-only, hash-chain, WORM, 10 yıl).
- 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.
SELECTiş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=on → connection 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 backup@bakicubuk.com -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’takidisk_full_action = HALTile 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 = ONile 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:
- Manuel/otomatik digest –
sys.sp_generate_database_ledger_digestile digest üretilip yurt içinde saklanır (on-prem S3-uyumlu Object Lock, yurt içi bulut veya WORM tape). Doğrulamasys.sp_verify_database_ledgerile bu yurt içi digest’e karşı yapılır.- 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
TRUNCATEtrigger’ı atlar. Bu yüzdenDROPveALTERyetkisi 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 =
TRUNCATEtrigger’ı 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,rolevar - PostgreSQL:
SHOW log_truncate_on_rotation;→off - MSSQL:
SELECT * FROM sys.server_audits→is_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_auditACTIVEFORCE_PLUS_PERMANENT - DELETE yetkisi yok kanıtı: PG
\dp payment.transactions· MSSQLsys.database_permissions· MySQLSHOW GRANTS - Silme girişimi engellendi örneği –
audit.violation_logkaydı - 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:
- Transaction log ≠ Audit log. Recovery ve denetim ayrı şeylerdir; ikisi de gerekir.
- CHD’ye her
SELECTloglanmalı – bunu yalnızca native audit yapar (pgaudit / SQL Server Audit / MySQL Audit).- DB’ye açık PAN girmesin (tokenizasyon) – o zaman log sızıntısı riski kökten biter.
- DELETE teknik olarak imkansız olmalı – REVOKE + trigger + Ledger/Temporal + WORM arşiv (dört katman).
- Log yazılamıyorsa veritabanı durmalı (
ON_FAILURE=SHUTDOWN/disk_full_action=HALT).

