Initial commit

This commit is contained in:
root
2026-05-09 17:28:23 +02:00
commit 9d73f82529
5575 changed files with 281989 additions and 0 deletions
+221
View File
@@ -0,0 +1,221 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Services\AlertService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class DDoSDetectionCommand extends Command
{
#[\Override]
protected $signature = 'monitor:ddos
{--threshold=100 : Minimum requests per IP om als verdacht te markeren}
{--time-window=60 : Tijd window in seconden}
{--block : Automatisch IPs blokkeren na detectie}';
#[\Override]
protected $description = 'Detecteer mogelijke DDoS aanvallen en stuur alerts';
private const string CACHE_KEY_DDOS_TRACKING = 'ddos_tracking';
private const string CACHE_KEY_BLOCKED_IPS = 'ddos_blocked_ips';
private const int TRACKING_DURATION_SECONDS = 300;
public function handle(AlertService $alertService): int
{
$threshold = (int) $this->option('threshold');
$timeWindow = (int) $this->option('time-window');
$autoBlock = (bool) $this->option('block');
$this->info("DDoS detectie gestart (threshold: {$threshold} req/IP in {$timeWindow}s)");
$ddosData = $this->getDDoSData();
$suspiciousIps = $this->analyzeTraffic($ddosData, $threshold, $timeWindow);
if ($suspiciousIps === []) {
$this->line('Geen verdachte activiteit gedetecteerd.');
return Command::SUCCESS;
}
$this->handleSuspiciousIps($suspiciousIps, $alertService, $autoBlock);
return Command::SUCCESS;
}
private function getDDoSData(): array
{
$data = Cache::get(self::CACHE_KEY_DDOS_TRACKING, []);
if (empty($data)) {
$data = $this->fetchApacheTrafficData();
}
return $data;
}
private function fetchApacheTrafficData(): array
{
$this->warn('Gebruik handmatige IP tracking (geen server logs toegang).');
return $this->getManualTrackingData();
}
private function tailFile(string $path, int $lines = 100): array
{
if (! file_exists($path)) {
return [];
}
$file = new \SplFileObject($path, 'r');
$file->seek(PHP_INT_MAX);
$totalLines = $file->key() + 1;
$startLine = max(0, $totalLines - $lines);
$result = [];
$file->seek($startLine);
while (! $file->eof()) {
$result[] = rtrim($file->current(), "\r\n");
$file->next();
}
return $result;
}
private function getManualTrackingData(): array
{
$tracking = Cache::get('manual_ip_tracking', []);
foreach ($tracking as $ip => $data) {
$tracking[$ip]['requests'] = array_filter(
$data['requests'],
fn ($timestamp) => $timestamp > time() - self::TRACKING_DURATION_SECONDS,
);
$tracking[$ip]['count'] = count($tracking[$ip]['requests']);
if ($tracking[$ip]['count'] === 0) {
unset($tracking[$ip]);
}
}
Cache::put('manual_ip_tracking', $tracking, self::TRACKING_DURATION_SECONDS);
return $tracking;
}
private function analyzeTraffic(array $data, int $threshold, int $timeWindow): array
{
$suspicious = [];
$cutoffTime = time() - $timeWindow;
foreach ($data as $ip => $info) {
$recentRequests = array_filter(
$info['requests'],
fn ($timestamp) => $timestamp > $cutoffTime,
);
$requestCount = count($recentRequests);
if ($requestCount >= $threshold) {
$suspicious[$ip] = [
'count' => $requestCount,
'time_window' => $timeWindow,
'first_seen' => $recentRequests === [] ? time() : min($recentRequests),
'last_seen' => $recentRequests === [] ? time() : max($recentRequests),
];
}
}
return $suspicious;
}
private function handleSuspiciousIps(array $suspiciousIps, AlertService $alertService, bool $autoBlock): void
{
$blockedIps = Cache::get(self::CACHE_KEY_BLOCKED_IPS, []);
$newBlocks = [];
foreach ($suspiciousIps as $ip => $details) {
$this->error("VERDACHTE IP GEDETECTEERD: {$ip}");
$this->table(
['Metric', 'Value'],
[
['Requests', $details['count']],
['Time Window', $details['time_window'] . 's'],
['First Seen', date('H:i:s', $details['first_seen'])],
['Last Seen', date('H:i:s', $details['last_seen'])],
],
);
if ($autoBlock && ! in_array($ip, $blockedIps)) {
$this->blockIp($ip);
$blockedIps[] = $ip;
$newBlocks[] = $ip;
}
}
$totalRequests = array_sum(array_column($suspiciousIps, 'count'));
$uniqueIps = count($suspiciousIps);
$alertService->sendDDoSDetected([
'total_requests' => $totalRequests,
'unique_ips' => $uniqueIps,
'time_window' => array_first($suspiciousIps)['time_window'],
'suspicious_ips' => array_keys($suspiciousIps),
'auto_blocked' => $newBlocks,
]);
if ($newBlocks !== []) {
$this->info('Automatisch geblokkeerd: ' . implode(', ', $newBlocks));
}
Cache::put(self::CACHE_KEY_BLOCKED_IPS, $blockedIps, 3600);
}
private function blockIp(string $ip): void
{
try {
$escapedIp = escapeshellarg($ip);
exec("iptables -A INPUT -s {$escapedIp} -j DROP 2>/dev/null");
Log::warning("IP blocked due to DDoS detection: {$ip}");
$this->warn("IP {$ip} geblokkeerd via iptables.");
} catch (\Exception $e) {
Log::error("Failed to block IP {$ip}: " . $e->getMessage());
$this->error("Kon IP {$ip} niet blokkeren: " . $e->getMessage());
}
}
public static function trackRequest(string $ip): void
{
$tracking = Cache::get('manual_ip_tracking', []);
if (! isset($tracking[$ip])) {
$tracking[$ip] = ['requests' => [], 'count' => 0];
}
$tracking[$ip]['requests'][] = time();
$tracking[$ip]['count']++;
Cache::put('manual_ip_tracking', $tracking, self::TRACKING_DURATION_SECONDS);
}
public static function getBlockedIps(): array
{
return Cache::get(self::CACHE_KEY_BLOCKED_IPS, []);
}
public static function clearBlockedIp(string $ip): void
{
$blocked = Cache::get(self::CACHE_KEY_BLOCKED_IPS, []);
$blocked = array_filter($blocked, fn ($blockedIp) => $blockedIp !== $ip);
Cache::put(self::CACHE_KEY_BLOCKED_IPS, array_values($blocked), 3600);
$escapedIp = escapeshellarg($ip);
exec("iptables -D INPUT -s {$escapedIp} -j DROP 2>/dev/null");
}
}