Semptom
Bir kurumun FortiGate-60E'si (FortiOS 6.2) SSL-VPN portalını internete açıyordu ve portal, internete açık her giriş ekranı gibi, gün boyu kaba kuvvet denemesi alıyordu. Duvarın syslog akışını kendi yazdığım bir boru hattında işliyordum: satır ayrıştırılıyor, kaynak IP'ye göre zenginleştiriliyor, sonra kara listeye, canlı akışa, alarma ve bir itibar servisine giden kötüye kullanım raporuna dağıtılıyordu.
Panoda iki tuhaflık vardı ve ikisi de yeşil görünüyordu.
Birincisi bir sayıydı. Pano "24 saatte 2.977 VPN denemesi" diyordu (27 Eyl 2026). Ama saldırgan listesinde, alarmlarda, canlı akışta ve dışa giden raporda tek bir VPN saldırganı yoktu. Sistem denemeleri sayabiliyor, kimin yaptığını söyleyemiyordu.
İkincisi bir rozetti. Bir gün önce (26 Eyl 2026) en ısrarcı iki adresi bir kara liste grubuna koyup grubu SSL-VPN ayarına bağlamıştım. Pano durumu "Engellendi" olarak gösterdi. Duvarın ham loglarında ise aynı iki adres aynı ritimle, aynı hata satırını üretmeye devam ediyordu.
Yanlış yollar
İki semptomun arkasında aynı türden bir varsayım vardı: bir sinyalin varlığını, işin yapıldığının kanıtı saymak.
-
"Sayaç çalışıyorsa ayrıştırıcı da çalışıyordur." Sayaç
actionalanına bakıyordu; bir satırı IP'si olmadan da sayabiliyordu. Sayı doğruydu. IP'ye bağlı her şey ise — zenginleştirme, kara liste, alarm, rapor — aynı satırı hiç görmüyordu. Eksik hata üretmediği için sessizce birikti. -
"FortiGate satırında kaynak
srcip'tedir." Trafik kayıtları için doğru, olay kayıtları için değil. Ayrıştırıcı bu varsayımla yazılmıştı ve VPN satırlarında IP alanını boş bırakıyordu. -
"Grup bir kurala bağlandıysa IP engellenmiştir." Pano rozeti grubun
başvuru sayısından türetiyordu: REST API'nin döndürdüğü
q_ref0 iken boşta, 1 olunca "Engellendi". Bu, yapılandırmanın durumunu söyler; trafiğe ne olduğunu söylemez.
Bir de bilerek almadığım kolay yol vardı: iki adresin /24 bloğunu topluca kapatmak. Hedefim saldıran adresti, blok değil. Bu karar sonradan ölçümün kendisine yaradı — engellenmeyen komşular kontrol grubu oldu.
Doğru test
İki soruyu birbirinden ayırdım ve ikisini de ölçerek sordum.
Birinci soru: VPN satırında saldırganın adresi var mı, varsa hangi alanda? Ham satırları yan yana koymak yetti.
# Trafik kaydı: kaynak srcip alanında
date=2026-09-27 time=03:12:44 … type="traffic" subtype="local" …
srcip=198.51.100.23 srcintf="wan1" dstip=192.0.2.1 dstport=23 … action="deny" …
# SSL-VPN olay kaydı: srcip YOK, karşı taraf remip alanında
date=2026-09-27 time=03:14:07 devname="FGT-ORNEK" logid="0101039426"
type="event" subtype="vpn" level="alert" vd="root" …
logdesc="SSL VPN login fail" action="ssl-login-fail" tunneltype="ssl-web"
tunnelid=0 remip=203.0.113.47 user="admin" group="N/A" dst_host="N/A"
reason="sslvpn_login_permission_denied" msg="SSL user failed to logged in"
Satırlar okunabilirlik için bölündü, alanlar kısaltıldı (…). IP, kullanıcı ve cihaz adı
örnektir. Olay kaydında srcip diye bir alan hiç yok.
Ayırt edici ölçü basitti: aynı zaman aralığındaki ssl-login-fail satırlarını iki
kez saydım — bir kez eylem alanına göre, bir kez kaynak IP'si dolu olanlar. Son 24 saatte ilki
2.977, ikincisi sıfırdı. Pencereyi 30 güne açınca IP'siz kalan VPN hatalı girişi 219 bine çıktı.
İkinci soru: kısıt denemeleri durdurdu mu? Burada tek gruplu bir "önce/sonra" karşılaştırması yanıltır. Saldırganların ritmi düzensizdir; bir adresin on beş dakika susması engelin kanıtı değil, mola olabilir. Bu yüzden ölçüye bir kontrol grubu ekledim: aynı /24'ten deneyen ama listeye almadığım komşular. Kısıt çalışıyorsa deney grubu düşer, kontrol grubu aynı kalır. İkisi birlikte düşerse saldırgan mola vermiştir. İkisi birlikte sürerse kısıt etkisizdir.
T0 kara liste grubu SSL-VPN ayarına bağlandı (26 Eyl 2026)
Deney gruptaki 2 adres 203.0.113.10, 203.0.113.11
Kontrol aynı /24'teki, engellenmeyen komşular
Ölçü adres başına ssl-login-fail satırı, T0 öncesi ve sonrası
Bekleme 46 dk (ritim ≈ 4 deneme/saat/adres → en az 45 dk)
Sonuç Deney ve kontrol, T0 sonrasında AYNI sıklıkta denemeye devam etti.
Satır biçimi değişmedi: reason="sslvpn_login_permission_denied"
tunneltype="ssl-web"
Engellenen adresler ile engellenmeyen komşuları arasında fark yok. Kısıt, trafiğe dokunmuyor.
Bekleme süresi ölçünün parçası. Bu adresler saatte yaklaşık dört kez deniyordu; on beş dakikalık bir pencerede hiç deneme görmemek olağandır. En az 45 dakika beklemeden sonuç okunmaz. 46 dakika boyunca iki grup da aynı sıklıkta denemeyi sürdürdü.
Kök neden
1 · Olay kaydının kendi şeması var
FortiOS'un iki log ailesi farklı şeyler anlatır. Trafik kayıtları (type=traffic)
bir oturumu tarif eder; kaynak ve hedef srcip/dstip alanlarındadır.
Olay kayıtları (type=event) bir alt sistemin ne yaptığını anlatır. SSL-VPN
olaylarında (subtype=vpn: ssl-login-fail, ssl-new-con,
ssl-alert, tunnel-up…) karşı tarafın adresi remip=
alanındadır ve srcip hiç yoktur.
Ayrıştırıcı kaynağı yalnız srcip'ten okuyordu. Bulamayınca hata vermedi; satırı
IP'siz yazdı, çünkü IP'siz satır geçerli bir satırdı. Sonraki her bileşenin anahtarı IP olduğu
için VPN satırları veritabanında duruyor ama savunmanın hiçbir yerine ulaşmıyordu. Onları
görebilen tek şey, eylem sayan sayaçtı.
Boş gelen bir alan hata üretmiyorsa eksik veri "veri yok" gibi görünür. Sayılabilen ama bir kimliğe bağlanamayan satırların oranını ayrı bir metrik olarak izle. Bu vakada VPN olaylarında o oran yüzde yüzdü.
2 · source-address girişi reddediyor, bağlantıyı düşürmüyor
Uyguladığım kısıt şuydu:
config vpn ssl settings
set source-address "KARA-LISTE"
set source-address-negate enable
end
source-address, SSL-VPN'e hangi adreslerden giriş yapılabileceğini belirler;
negate ile "bu gruptakiler hariç herkes" olur. Ölçümün gösterdiği şu: kısıt
bağlantıyı düşürmüyor. TCP ve TLS kuruluyor, istek SSL-VPN sürecine (sslvpnd)
ulaşıyor, giriş orada reddediliyor. Saldırganın gözünden hiçbir şey değişmiyor: önce de
başarısızdı, şimdi de. Log satırı da değişmiyor: aynı
reason="sslvpn_login_permission_denied", aynı tunneltype="ssl-web".
Kısıtın sağladığı şey, en iyi durumda, bu adreslerden başarılı bir girişin de reddedilmesi; bunu ayrıca sınamadım. Kesin olan, kaba kuvvet trafiğini durdurmadığı. Panonun "Engellendi" demesi ise bütünüyle yapılandırmadan türemişti: grup bir yere bağlanmıştı, o kadar.
Çözüm
1 · remip, yalnız saldırı eylemine
En kısa düzeltme "srcip yoksa remip'i al" olurdu. Tek satır — ve ikinci
bir hata. Bu kural tunnel-up dahil her VPN olayına IP yazar. Kurumun meşru bir
kullanıcısı tünelini cep telefonundan açıyor ve bir gün parolasını yanlış yazıyorsa o
ssl-login-fail satırı onun adresini taşır. Kural ayrım yapmazsa kullanıcının cep
telefonu IP'si kara listeye ve itibar servisine giden rapora düşer: kendi kullanıcını dünyaya
saldırgan diye bildirmiş olursun.
Uyguladığım kural iki şart koyuyor: remip yalnız ssl-login-fail
satırına yazılır ve tunnel-up ile tünel açtığı görülmüş hesapların hatalı
girişleri saldırı sayılmaz. Tünel açmış hesaplar açılışta geçmiş kayıtlardan yüklenir, sonra
olaylardan öğrenilir. Diğer VPN olayları IP'siz kalır.
IPV4 = re.compile(r"\d{1,3}(?:\.\d{1,3}){3}")
tunel_acmis = set() # tunnel-up görülmüş hesaplar; açılışta geçmişten de yüklenir
def kaynak_ip(f):
if f.get("srcip"): # trafik kayıtları
return f["srcip"]
if f.get("subtype") != "vpn":
return None
kullanici = f.get("user") or ""
if f.get("action") == "tunnel-up" and kullanici:
tunel_acmis.add(kullanici) # meşru hesap: sahibi tünel açtı
elif (f.get("action") == "ssl-login-fail"
and kullanici not in tunel_acmis
and IPV4.fullmatch(f.get("remip", ""))):
return f["remip"] # yalnız saldırı eylemi
return None # diğer VPN olayları IP'siz
Bu kuraldan sonra VPN hatalı girişleri zenginleştirmeye, kara listeye, alarma ve rapora adresleriyle ulaşıyor; tünel açmış hesapların yazım hataları dışarıda kalıyor.
Bu kuralın bilinen bir bedeli var: saldırgan gerçek bir hesap adını denerse o denemeler IP'siz kalır. Meşru kullanıcıyı ihbar etmektense bu boşluğu kabul ettim.
Geri doldurma (backfill) ayrı bir dikkat ister. Geçmiş 30 günü yeniden işlemek 219 bin satırı bir anda IP'li hale getirir ve bu satırlar dışa giden raporlayıcıya da akar. Geri doldurmadan önce raporlayıcının ne göndereceğine bakmak gerekir: kapsam birden genişliyorsa önce onay, sonra doğru kategori. VPN'e kaba kuvvet denemesi "port taraması" olarak değil, kaba kuvvet olarak bildirilmeli.
2 · source-address: sıradaki aday, doğrulanmadı
Dosyanın açık kalma nedeni bu. Sıradaki aday, wan1 üzerinde kara liste grubunu
kaynak alan bir deny local-in kuralı. Local-in kuralları duvarın kendisine
gelen trafiğe uygulanır; beklentim, bağlantının SSL-VPN sürecine varmadan düşmesi.
# ADAY — FortiOS 6.2'de SSL-VPN'i kestiği henüz DOĞRULANMADI
config firewall local-in-policy
edit 100
set intf "wan1"
set srcaddr "KARA-LISTE"
set dstaddr "all"
set action deny
set service "ALL"
set schedule "always"
next
end
Altını çizerek yazıyorum: bu kuralın FortiOS 6.2'de SSL-VPN trafiğini gerçekten kestiğini henüz doğrulamadım. Uyguladığımda ölçü aynı olacak: deney ve kontrol grubu, T0 öncesi ve sonrası, en az 45 dakika. Local-in de tutmazsa son seçenek yüzeyi kapatmak: portalı internete hiç açmamak, örneğin duvarın önündeki modemde port yönlendirmesini kaldırmak.
Kalıcı ders
- Yapılandırma durumu etki kanıtı değildir. "Kurala bağlı" ile "engellendi" arasındaki fark yalnız trafikte görünür. Pano rozeti yapılandırmadan değil, ölçümden türemeli.
- Engellemeyi kontrol grubuyla ölç. Engellenen adresleri, engellenmeyen benzerleriyle T0 öncesi ve sonrası karşılaştır; bekleme süresini saldırganın ritmine göre seç. Tek gruplu önce/sonra karşılaştırması molayı başarı sanar.
-
Her log türünün kendi şeması var.
srciptrafik kaydının alanıdır; olay kaydında karşı taraf başka bir adla gelir. Ayrıştırıcıya bir "IP bulunamadı" sayacı koy ve sıfırdan büyükse bak. - Sayım, kimlik değildir. "2.977 deneme" doğru bir sayıydı. Kimlik olmadan hiçbir savunma o sayının üzerine kurulamaz.
- Saldırganı ararken kendi kullanıcını ihbar etme. Dışa rapor gönderen her kuralın bir "bu bizim kullanıcımız mı?" süzgeci olmalı; geri doldurma bu süzgeci en çok zorlayan andır.