You've already forked Atomcms-edit
Initial commit
This commit is contained in:
+221
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user