Merhaba, bu yazımızda Linux camiasında dünyaca ünlü olan Fail2Ban uygulamasının alternatifini C# ile Windows ortamında yazıp çalıştıracağız.
Kuralları MailEnable Mail Server’ın log dosyasına göre düzenleyeceğiz. Sizler Apache, Ngix, Mysql, Bind9 gibi sunucularınıza göre değiştirerek bu sunucularınızda da kullanabilirsiniz. Tüm kodları aşağıdadır.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using RestSharp;
namespace Fail2Ban
{
class Program
{
static void Main(string[] args)
{
int maxHataliGiris = 2; // İzin verilen maksimum hatalı giriş sayısı
int engellemeSuresiSaniye = 18000; // Engelleme süresi (saniye cinsinden)
Dictionary<string, DateTime> engellenenIpler = new Dictionary<string, DateTime>();
Dictionary<string, int> hataliGirisSayilari = new Dictionary<string, int>();
long sonOkunanSatirIndeksi = 0;
while (true)
{
DateTime today = DateTime.Today;
int day = today.Day;
int month = today.Month;
int year = today.Year % 100; // Yılın son iki hanesini alıyoruz
string formattedDate = $"{year:D2}{month:D2}{day:D2}";
string logDosyaYolu = @"C:\Program Files (x86)\Mail Enable\Logging\SMTP\SMTP-Activity-" + formattedDate + ".log"; // İzlenecek log dosyasının yolu
string kopyalananLogDosyaYolu = "access.log"; //
File.Copy(logDosyaYolu, kopyalananLogDosyaYolu, true);
string[] logSatirlari = File.ReadAllLines(kopyalananLogDosyaYolu);
for (long i = sonOkunanSatirIndeksi; i < logSatirlari.Length; i++)
{
string satir = logSatirlari[i];
string ipAdresi = GetIPAdresi(satir);
if (ipAdresi != null)
{
if (engellenenIpler.ContainsKey(ipAdresi))
{
DateTime engellemeBitisZamani = engellenenIpler[ipAdresi];
if (DateTime.Now >= engellemeBitisZamani)
{
engellenenIpler.Remove(ipAdresi);
Console.WriteLine($"[{DateTime.Now}] {ipAdresi} IP adresinin engellemesi sona erdi.");
RemoveFirewallRule(ipAdresi);
}
}
else
{
if (hataliGirisSayilari.ContainsKey(ipAdresi))
{
hataliGirisSayilari[ipAdresi]++;
if (hataliGirisSayilari[ipAdresi] >= maxHataliGiris)
{
engellenenIpler[ipAdresi] = DateTime.Now.AddSeconds(engellemeSuresiSaniye);
Console.WriteLine($"[{DateTime.Now}] {ipAdresi} IP adresi engellendi.");
AddFirewallRule(ipAdresi);
hataliGirisSayilari.Remove(ipAdresi);
}
}
else
{
hataliGirisSayilari[ipAdresi] = 1;
}
}
}
}
sonOkunanSatirIndeksi = logSatirlari.Length;
// Engellenen IP adreslerini kontrol edin ve süresi dolanları kaldırın
List<string> engellenenIplerListesi = engellenenIpler.Keys.ToList();
foreach (string ipAdresi in engellenenIplerListesi)
{
if (DateTime.Now >= engellenenIpler[ipAdresi])
{
engellenenIpler.Remove(ipAdresi);
Console.WriteLine($"[{DateTime.Now}] {ipAdresi} IP adresinin engellemesi sona erdi.");
RemoveFirewallRule(ipAdresi);
}
}
// Belirli bir süre bekle
File.Delete(kopyalananLogDosyaYolu);
System.Threading.Thread.Sleep(10000);
}
}
static string GetIPAdresi(string logSatiri)
{
string pattern = @"^(?<tarih>\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2})\s+SMTP-IN\s+\w+\.\w+\s+\d+\s+(?<ipAdresi>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+AUTH\s+(?=.*535 Invalid Username or Password)";
Match match = Regex.Match(logSatiri, pattern);
if (match.Success)
{
if (match.Groups.Count > 1 && !string.IsNullOrWhiteSpace(match.Groups["ipAdresi"].Value))
{
return match.Groups["ipAdresi"].Value;
}
}
return null;
}
static void AddFirewallRule(string ipAdresi)
{
string ruleName = $"Fail2Ban_{ipAdresi}";
string command = $"netsh advfirewall firewall add rule name=\"{ruleName}\" dir=in interface=any action=block remoteip={ipAdresi}";
ExecuteCommand(command);
SendAbuseReportAsync(ipAdresi);
}
static void RemoveFirewallRule(string ipAdresi)
{
string ruleName = $"Fail2Ban_{ipAdresi}";
string command = $"netsh advfirewall firewall delete rule name=\"{ruleName}\"";
ExecuteCommand(command);
}
static void ExecuteCommand(string command)
{
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "cmd.exe";
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
process.StartInfo = startInfo;
process.Start();
using (StreamWriter sw = process.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
sw.WriteLine(command);
}
}
process.WaitForExit();
process.Close();
}
static async Task SendAbuseReportAsync(string ipAdresi)
{
string apiKey = "xxx";//abuseipdb api keyiniz
string ipAddress = ipAdresi;
string comment = "Smtp Brute Force Attack was Blocked. IP has been banned for 300 minutes " + DateTime.Now.ToString("dddd, dd MMMM yyyy hh:mm tt");
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Key", apiKey);
client.DefaultRequestHeaders.Add("Accept", "application/json");
string apiUrl = "https://api.abuseipdb.com/api/v2/report";
string requestBody = $"ip={ipAddress}&categories=18&comment={comment}";
HttpResponseMessage response = await client.PostAsync(apiUrl, new StringContent(requestBody, Encoding.UTF8, "application/x-www-form-urlencoded"));
if (response.IsSuccessStatusCode)
{
Console.WriteLine("AbuseIPDB'ye bildirim başarıyla gönderildi.");
}
else
{
Console.WriteLine("Bildirim gönderme başarısız oldu. Hata kodu: " + response.StatusCode);
}
}
}
catch (Exception ex)
{
Console.WriteLine("Bir hata oluştu: " + ex.Message);
}
}
}
}
Şimdi kodları açıklayalım.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using RestSharp;
Burada gerekli kütüphaneleri çağırdık.
int maxHataliGiris = 2; // İzin verilen maksimum hatalı giriş sayısı
int engellemeSuresiSaniye = 18000; // Engelleme süresi (saniye cinsinden)
Dictionary<string, DateTime> engellenenIpler = new Dictionary<string, DateTime>();
Dictionary<string, int> hataliGirisSayilari = new Dictionary<string, int>();
long sonOkunanSatirIndeksi = 0;
Burada max hatalı giriş sayımızı, engelleme süremizi ve dictionarylerimizi oluşturduk.
DateTime today = DateTime.Today;
int day = today.Day;
int month = today.Month;
int year = today.Year % 100; // Yılın son iki hanesini alıyoruz
string formattedDate = $"{year:D2}{month:D2}{day:D2}";
string logDosyaYolu = @"C:\Program Files (x86)\Mail Enable\Logging\SMTP\SMTP-Activity-" + formattedDate + ".log"; // İzlenecek log dosyasının yolu
string kopyalananLogDosyaYolu = "access.log"; //
File.Copy(logDosyaYolu, kopyalananLogDosyaYolu, true);
string[] logSatirlari = File.ReadAllLines(kopyalananLogDosyaYolu);
Burada şu anki tarih bilgisini yaratıp MailEnable üzerindeki bu tarih bilgisi ile başlayan log dosyasını bulup logu geçici olarak kopyalıyoruz.
for (long i = sonOkunanSatirIndeksi; i < logSatirlari.Length; i++)
{
string satir = logSatirlari[i];
string ipAdresi = GetIPAdresi(satir);
if (ipAdresi != null)
{
if (engellenenIpler.ContainsKey(ipAdresi))
{
DateTime engellemeBitisZamani = engellenenIpler[ipAdresi];
if (DateTime.Now >= engellemeBitisZamani)
{
engellenenIpler.Remove(ipAdresi);
Console.WriteLine($"[{DateTime.Now}] {ipAdresi} IP adresinin engellemesi sona erdi.");
RemoveFirewallRule(ipAdresi);
}
}
else
{
if (hataliGirisSayilari.ContainsKey(ipAdresi))
{
hataliGirisSayilari[ipAdresi]++;
if (hataliGirisSayilari[ipAdresi] >= maxHataliGiris)
{
engellenenIpler[ipAdresi] = DateTime.Now.AddSeconds(engellemeSuresiSaniye);
Console.WriteLine($"[{DateTime.Now}] {ipAdresi} IP adresi engellendi.");
AddFirewallRule(ipAdresi);
hataliGirisSayilari.Remove(ipAdresi);
}
}
else
{
hataliGirisSayilari[ipAdresi] = 1;
}
}
}
}
sonOkunanSatirIndeksi = logSatirlari.Length;
// Engellenen IP adreslerini kontrol edin ve süresi dolanları kaldırın
List<string> engellenenIplerListesi = engellenenIpler.Keys.ToList();
foreach (string ipAdresi in engellenenIplerListesi)
{
if (DateTime.Now >= engellenenIpler[ipAdresi])
{
engellenenIpler.Remove(ipAdresi);
Console.WriteLine($"[{DateTime.Now}] {ipAdresi} IP adresinin engellemesi sona erdi.");
RemoveFirewallRule(ipAdresi);
}
}
// Belirli bir süre bekle
File.Delete(kopyalananLogDosyaYolu);
System.Threading.Thread.Sleep(10000);
}
Burada kopyalanan loglarımızı kontrol ediyoruz eğer 2 kereden fazla hatalı giriş yapan ipleri ve daha önce engellenmiş mi kontrollerini yapıyoruz . Engellenmemişse dictionarye ekliyoruz engellenmişse üzerinden 18000saniye geçmişse sildiriyoruz.
static string GetIPAdresi(string logSatiri)
{
string pattern = @"^(?<tarih>\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2})\s+SMTP-IN\s+\w+\.\w+\s+\d+\s+(?<ipAdresi>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+AUTH\s+(?=.*535 Invalid Username or Password)";
Match match = Regex.Match(logSatiri, pattern);
if (match.Success)
{
if (match.Groups.Count > 1 && !string.IsNullOrWhiteSpace(match.Groups["ipAdresi"].Value))
{
return match.Groups["ipAdresi"].Value;
}
}
return null;
}
Bu fonksiyonumuz pattreni sağlayan satırdaki ipyi çekiyor.
static void AddFirewallRule(string ipAdresi)
{
string ruleName = $"Fail2Ban_{ipAdresi}";
string command = $"netsh advfirewall firewall add rule name=\"{ruleName}\" dir=in interface=any action=block remoteip={ipAdresi}";
ExecuteCommand(command);
SendAbuseReportAsync(ipAdresi);
}
static void RemoveFirewallRule(string ipAdresi)
{
string ruleName = $"Fail2Ban_{ipAdresi}";
string command = $"netsh advfirewall firewall delete rule name=\"{ruleName}\"";
ExecuteCommand(command);
}
Bu fonksiyonlarımız windows güvenlik kuralına belirtilen ipyi engelleme ve izin vermeyi sağlıyor.
static void ExecuteCommand(string command)
{
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "cmd.exe";
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
process.StartInfo = startInfo;
process.Start();
using (StreamWriter sw = process.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
sw.WriteLine(command);
}
}
process.WaitForExit();
process.Close();
}
Bu fonksiyonumuz firewall kuralı ekleyip silmek için gerekli cmd komut satırını başlatıyor.
static async Task SendAbuseReportAsync(string ipAdresi)
{
string apiKey = "xxx";//abuseipdb api keyiniz
string ipAddress = ipAdresi;
string comment = "Smtp Brute Force Attack was Blocked. IP has been banned for 300 minutes " + DateTime.Now.ToString("dddd, dd MMMM yyyy hh:mm tt");
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Key", apiKey);
client.DefaultRequestHeaders.Add("Accept", "application/json");
string apiUrl = "https://api.abuseipdb.com/api/v2/report";
string requestBody = $"ip={ipAddress}&categories=18&comment={comment}";
HttpResponseMessage response = await client.PostAsync(apiUrl, new StringContent(requestBody, Encoding.UTF8, "application/x-www-form-urlencoded"));
if (response.IsSuccessStatusCode)
{
Console.WriteLine("AbuseIPDB'ye bildirim başarıyla gönderildi.");
}
else
{
Console.WriteLine("Bildirim gönderme başarısız oldu. Hata kodu: " + response.StatusCode);
}
}
}
catch (Exception ex)
{
Console.WriteLine("Bir hata oluştu: " + ex.Message);
}
Bu fonksiyon ise tamamen kişiseldir. Sizlerde benim dünyada siber güvenliğe katkınız olması için zararlı ipleri Abuseipdb sitesine bildirmek için istiyorsanız kullanabilirsiniz. İstemiyorsanız koddaki SendAbuseReportAsync fonksiyonunu ve fonksiyona bağlantı gönderen satırları silerseniz bu adımı yapmadan programı kullanmaya devam edebilirsiniz.
Resimlerde gördüğünüz gibi otomatik olarak 2 kere hatalı giriş yapan ipleri engelleyip süresi biteni silip abuseipdbye zararlı ipleri raporlamaktadır. 2-3 aydır beri sorunsuz kullanmaktayım. Sizlerde siber güvenliğe yardım etmek için abusedbipye raporlama yapmanızı rica ediyorum.
İyi Çalışmalar Dilerim.