jtl-wafi-agent.py aktualisiert
This commit is contained in:
@@ -37,7 +37,7 @@ from logging.handlers import RotatingFileHandler
|
||||
# =============================================================================
|
||||
# VERSION
|
||||
# =============================================================================
|
||||
VERSION = "2.3.0"
|
||||
VERSION = "2.4.0"
|
||||
|
||||
# =============================================================================
|
||||
# PFADE - AGENT
|
||||
@@ -1078,6 +1078,121 @@ if (rand(1, $cleanup_probability) === 1) {{
|
||||
return;
|
||||
'''
|
||||
|
||||
# =============================================================================
|
||||
# PHP TEMPLATES - BOT MONITOR-ONLY (No Blocking)
|
||||
# =============================================================================
|
||||
BOT_MONITOR_TEMPLATE = '''<?php
|
||||
/**
|
||||
* Bot Monitor-Only Script - Logging without Blocking
|
||||
* Valid until: {expiry_date}
|
||||
* Detects and logs bots/crawlers WITHOUT rate-limiting or blocking
|
||||
* All requests are ALLOWED through - monitoring only!
|
||||
*/
|
||||
|
||||
$expiry_date = strtotime('{expiry_timestamp}');
|
||||
if (time() > $expiry_date) return;
|
||||
|
||||
$log_file = __DIR__ . '/{log_file}';
|
||||
$stats_file = __DIR__ . '/{ratelimit_dir}/monitor_stats.json';
|
||||
$stats_dir = __DIR__ . '/{ratelimit_dir}';
|
||||
|
||||
$visitor_ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
|
||||
// Ensure stats directory exists
|
||||
if (!is_dir($stats_dir)) @mkdir($stats_dir, 0777, true);
|
||||
|
||||
// === IP-in-CIDR Check Function ===
|
||||
function ip_in_cidr($ip, $cidr) {{
|
||||
if (strpos($cidr, '/') === false) return false;
|
||||
list($subnet, $mask) = explode('/', $cidr);
|
||||
$ip_long = ip2long($ip);
|
||||
$subnet_long = ip2long($subnet);
|
||||
if ($ip_long === false || $subnet_long === false) return false;
|
||||
$mask_long = -1 << (32 - (int)$mask);
|
||||
return ($ip_long & $mask_long) === ($subnet_long & $mask_long);
|
||||
}}
|
||||
|
||||
// === Bot IP Ranges (für getarnte Bots) ===
|
||||
$bot_ip_ranges = [
|
||||
{bot_ip_ranges}
|
||||
];
|
||||
|
||||
// === Bot Detection Patterns (User-Agent) ===
|
||||
$bot_patterns = [
|
||||
{bot_patterns}
|
||||
];
|
||||
|
||||
$generic_patterns = [{generic_patterns}];
|
||||
|
||||
// === STEP 0: IP-basierte Bot-Erkennung (höchste Priorität) ===
|
||||
$detected_bot = null;
|
||||
|
||||
if (!empty($visitor_ip)) {{
|
||||
foreach ($bot_ip_ranges as $bot_name => $ip_ranges) {{
|
||||
foreach ($ip_ranges as $cidr) {{
|
||||
if (ip_in_cidr($visitor_ip, $cidr)) {{
|
||||
$detected_bot = $bot_name;
|
||||
break 2;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
// === STEP 1: User-Agent-basierte Erkennung (falls IP nicht erkannt) ===
|
||||
if ($detected_bot === null && !empty($user_agent)) {{
|
||||
foreach ($bot_patterns as $bot_name => $pattern) {{
|
||||
if (preg_match($pattern, $user_agent)) {{
|
||||
$detected_bot = $bot_name;
|
||||
break;
|
||||
}}
|
||||
}}
|
||||
|
||||
if ($detected_bot === null) {{
|
||||
$ua_lower = strtolower($user_agent);
|
||||
foreach ($generic_patterns as $pattern) {{
|
||||
if (strpos($ua_lower, $pattern) !== false) {{
|
||||
$detected_bot = "Bot ($pattern)";
|
||||
break;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
// Not a bot - allow through without logging
|
||||
if ($detected_bot === null) return;
|
||||
|
||||
// === STEP 2: Log the bot request (MONITOR ONLY - no blocking!) ===
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
@file_put_contents($log_file, "[$timestamp] MONITOR: $detected_bot | IP: $visitor_ip | URI: $uri\\n", FILE_APPEND | LOCK_EX);
|
||||
|
||||
// === STEP 3: Update simple stats for dashboard ===
|
||||
$stats = [];
|
||||
if (file_exists($stats_file)) {{
|
||||
$stats = @json_decode(@file_get_contents($stats_file), true) ?: [];
|
||||
}}
|
||||
|
||||
$minute_key = date('Y-m-d H:i');
|
||||
if (!isset($stats['by_minute'])) $stats['by_minute'] = [];
|
||||
if (!isset($stats['by_minute'][$minute_key])) $stats['by_minute'][$minute_key] = [];
|
||||
if (!isset($stats['by_minute'][$minute_key][$detected_bot])) $stats['by_minute'][$minute_key][$detected_bot] = 0;
|
||||
$stats['by_minute'][$minute_key][$detected_bot]++;
|
||||
|
||||
// Cleanup old minutes (keep last 60)
|
||||
$minutes = array_keys($stats['by_minute']);
|
||||
if (count($minutes) > 60) {{
|
||||
sort($minutes);
|
||||
$to_remove = array_slice($minutes, 0, count($minutes) - 60);
|
||||
foreach ($to_remove as $m) unset($stats['by_minute'][$m]);
|
||||
}}
|
||||
|
||||
@file_put_contents($stats_file, json_encode($stats), LOCK_EX);
|
||||
|
||||
// === ALLOW REQUEST THROUGH - NO BLOCKING ===
|
||||
return;
|
||||
'''
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CACHE VALIDATION FUNCTIONS
|
||||
@@ -1166,7 +1281,8 @@ else {{ echo "OK:" . count($data); }}
|
||||
# SHOP REGISTRY FUNCTIONS
|
||||
# =============================================================================
|
||||
def add_shop_to_active(shop: str, mode: str = "geoip", geo_region: str = "dach",
|
||||
rate_limit: int = None, ban_duration: int = None):
|
||||
rate_limit: int = None, ban_duration: int = None,
|
||||
bot_monitor_only: bool = False):
|
||||
"""Registriert einen Shop als aktiv."""
|
||||
os.makedirs(os.path.dirname(ACTIVE_SHOPS_FILE), exist_ok=True)
|
||||
shops = {}
|
||||
@@ -1187,6 +1303,8 @@ def add_shop_to_active(shop: str, mode: str = "geoip", geo_region: str = "dach",
|
||||
shop_data["rate_limit"] = rate_limit
|
||||
if ban_duration is not None:
|
||||
shop_data["ban_duration"] = ban_duration
|
||||
if bot_monitor_only:
|
||||
shop_data["bot_monitor_only"] = True
|
||||
|
||||
shops[shop] = shop_data
|
||||
with open(ACTIVE_SHOPS_FILE, 'w') as f:
|
||||
@@ -1242,6 +1360,17 @@ def get_shop_rate_limit_config(shop: str):
|
||||
return None, None
|
||||
|
||||
|
||||
def get_shop_monitor_mode(shop: str) -> bool:
|
||||
"""Gibt zurück ob ein Shop im Monitor-Only Modus ist."""
|
||||
if not os.path.isfile(ACTIVE_SHOPS_FILE):
|
||||
return False
|
||||
try:
|
||||
with open(ACTIVE_SHOPS_FILE, 'r') as f:
|
||||
return json.load(f).get(shop, {}).get("bot_monitor_only", False)
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def get_shop_activation_time(shop: str) -> Optional[datetime]:
|
||||
"""Gibt die Aktivierungszeit eines Shops zurück."""
|
||||
if not os.path.isfile(ACTIVE_SHOPS_FILE):
|
||||
@@ -1289,7 +1418,7 @@ def get_active_shops() -> List[str]:
|
||||
# =============================================================================
|
||||
def activate_blocking(shop: str, silent: bool = True, mode: str = "geoip",
|
||||
geo_region: str = "dach", rate_limit: int = None,
|
||||
ban_duration: int = None) -> bool:
|
||||
ban_duration: int = None, bot_monitor_only: bool = False) -> bool:
|
||||
"""
|
||||
Aktiviert Blocking für einen Shop.
|
||||
|
||||
@@ -1300,6 +1429,7 @@ def activate_blocking(shop: str, silent: bool = True, mode: str = "geoip",
|
||||
geo_region: "dach", "eurozone" oder "none"
|
||||
rate_limit: Requests pro Minute (nur bei bot-mode)
|
||||
ban_duration: Ban-Dauer in Sekunden (nur bei bot-mode)
|
||||
bot_monitor_only: Nur Monitoring, kein Blocking (nur bei bot-mode)
|
||||
|
||||
Returns:
|
||||
True wenn erfolgreich, False sonst
|
||||
@@ -1365,31 +1495,49 @@ def activate_blocking(shop: str, silent: bool = True, mode: str = "geoip",
|
||||
|
||||
# Step 2: Blocking-Script erstellen
|
||||
if bot_mode:
|
||||
# Rate-Limit Verzeichnisse erstellen
|
||||
os.makedirs(os.path.join(ratelimit_path, 'bans'), mode=0o777, exist_ok=True)
|
||||
os.makedirs(os.path.join(ratelimit_path, 'counts'), mode=0o777, exist_ok=True)
|
||||
# Verzeichnisse erstellen (für beide Modi)
|
||||
os.makedirs(ratelimit_path, mode=0o777, exist_ok=True)
|
||||
os.chmod(ratelimit_path, 0o777)
|
||||
os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777)
|
||||
os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777)
|
||||
set_owner(ratelimit_path, uid, gid, recursive=True)
|
||||
|
||||
if bot_monitor_only:
|
||||
# Monitor-Only Modus: Nur Logging, kein Blocking
|
||||
geoip_content = BOT_MONITOR_TEMPLATE.format(
|
||||
expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'),
|
||||
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
log_file=SHOP_LOG_FILE,
|
||||
ratelimit_dir=RATELIMIT_DIR,
|
||||
bot_patterns=generate_php_bot_patterns(),
|
||||
generic_patterns=generate_php_generic_patterns(),
|
||||
bot_ip_ranges=generate_php_bot_ip_ranges()
|
||||
)
|
||||
if not silent:
|
||||
logger.info(f"🔍 Monitor-Only Modus für {shop}")
|
||||
else:
|
||||
# Rate-Limit Modus: Bans/Counts Verzeichnisse erstellen
|
||||
os.makedirs(os.path.join(ratelimit_path, 'bans'), mode=0o777, exist_ok=True)
|
||||
os.makedirs(os.path.join(ratelimit_path, 'counts'), mode=0o777, exist_ok=True)
|
||||
os.chmod(os.path.join(ratelimit_path, 'bans'), 0o777)
|
||||
os.chmod(os.path.join(ratelimit_path, 'counts'), 0o777)
|
||||
set_owner(ratelimit_path, uid, gid, recursive=True)
|
||||
|
||||
if rate_limit is None:
|
||||
rate_limit = DEFAULT_RATE_LIMIT
|
||||
if ban_duration is None:
|
||||
ban_duration = DEFAULT_BAN_DURATION * 60
|
||||
if rate_limit is None:
|
||||
rate_limit = DEFAULT_RATE_LIMIT
|
||||
if ban_duration is None:
|
||||
ban_duration = DEFAULT_BAN_DURATION * 60
|
||||
|
||||
geoip_content = BOT_SCRIPT_TEMPLATE.format(
|
||||
expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'),
|
||||
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
log_file=SHOP_LOG_FILE,
|
||||
ratelimit_dir=RATELIMIT_DIR,
|
||||
bot_patterns=generate_php_bot_patterns(),
|
||||
generic_patterns=generate_php_generic_patterns(),
|
||||
bot_ip_ranges=generate_php_bot_ip_ranges(),
|
||||
rate_limit=rate_limit,
|
||||
ban_duration=ban_duration,
|
||||
ban_duration_min=ban_duration // 60
|
||||
)
|
||||
geoip_content = BOT_SCRIPT_TEMPLATE.format(
|
||||
expiry_date=expiry.strftime('%Y-%m-%d %H:%M:%S CET'),
|
||||
expiry_timestamp=expiry.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
log_file=SHOP_LOG_FILE,
|
||||
ratelimit_dir=RATELIMIT_DIR,
|
||||
bot_patterns=generate_php_bot_patterns(),
|
||||
generic_patterns=generate_php_generic_patterns(),
|
||||
bot_ip_ranges=generate_php_bot_ip_ranges(),
|
||||
rate_limit=rate_limit,
|
||||
ban_duration=ban_duration,
|
||||
ban_duration_min=ban_duration // 60
|
||||
)
|
||||
else:
|
||||
countries_array = generate_php_countries_array(geo_region)
|
||||
geoip_content = GEOIP_SCRIPT_TEMPLATE.format(
|
||||
@@ -1415,7 +1563,7 @@ def activate_blocking(shop: str, silent: bool = True, mode: str = "geoip",
|
||||
|
||||
# Step 4: Registrieren
|
||||
if bot_mode:
|
||||
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration)
|
||||
add_shop_to_active(shop, mode, geo_region, rate_limit, ban_duration, bot_monitor_only)
|
||||
else:
|
||||
add_shop_to_active(shop, mode, geo_region)
|
||||
|
||||
@@ -1538,6 +1686,12 @@ def get_shop_log_stats(shop: str) -> Dict[str, Any]:
|
||||
detected_bot = line.split('BOT: ')[1].split(' |')[0].strip()
|
||||
except:
|
||||
pass
|
||||
elif 'MONITOR: ' in line:
|
||||
# Monitor-Only Mode - gleiche Statistik wie BOT:
|
||||
try:
|
||||
detected_bot = line.split('MONITOR: ')[1].split(' |')[0].strip()
|
||||
except:
|
||||
pass
|
||||
elif 'BLOCKED (banned): ' in line:
|
||||
try:
|
||||
detected_bot = line.split('BLOCKED (banned): ')[1].split(' |')[0].strip()
|
||||
@@ -1873,11 +2027,13 @@ class JTLWAFiAgent:
|
||||
shop_geo = get_shop_geo_region(shop)
|
||||
rate_limit, ban_duration = get_shop_rate_limit_config(shop)
|
||||
activation_time = get_shop_activation_time(shop)
|
||||
monitor_only = get_shop_monitor_mode(shop)
|
||||
|
||||
shop_data["mode"] = shop_mode
|
||||
shop_data["geo_region"] = shop_geo
|
||||
shop_data["rate_limit"] = rate_limit
|
||||
shop_data["ban_duration"] = ban_duration
|
||||
shop_data["bot_monitor_only"] = monitor_only
|
||||
|
||||
if activation_time:
|
||||
shop_data["activated"] = activation_time.isoformat()
|
||||
@@ -2028,10 +2184,14 @@ class JTLWAFiAgent:
|
||||
geo_region = data.get('geo_region', 'dach')
|
||||
rate_limit = data.get('rate_limit')
|
||||
ban_duration = data.get('ban_duration')
|
||||
bot_monitor_only = data.get('bot_monitor_only', False)
|
||||
|
||||
# Korrektes Logging je nach Modus
|
||||
if mode == 'bot':
|
||||
logger.info(f"Aktiviere {shop} (mode=bot, rate_limit={rate_limit}/min, ban={ban_duration}s)")
|
||||
if bot_monitor_only:
|
||||
logger.info(f"Aktiviere {shop} (mode=bot, MONITOR-ONLY)")
|
||||
else:
|
||||
logger.info(f"Aktiviere {shop} (mode=bot, rate_limit={rate_limit}/min, ban={ban_duration}s)")
|
||||
else:
|
||||
logger.info(f"Aktiviere {shop} (mode=geoip, region={geo_region})")
|
||||
|
||||
@@ -2042,14 +2202,16 @@ class JTLWAFiAgent:
|
||||
mode=mode,
|
||||
geo_region=geo_region,
|
||||
rate_limit=rate_limit,
|
||||
ban_duration=ban_duration
|
||||
ban_duration=ban_duration,
|
||||
bot_monitor_only=bot_monitor_only
|
||||
)
|
||||
|
||||
if success:
|
||||
mode_desc = 'monitor' if bot_monitor_only else mode
|
||||
await self._send_event('command.result', {
|
||||
'command_id': command_id,
|
||||
'status': 'success',
|
||||
'message': f'Shop {shop} aktiviert ({mode})',
|
||||
'message': f'Shop {shop} aktiviert ({mode_desc})',
|
||||
'shop': shop
|
||||
})
|
||||
# Full Update senden
|
||||
|
||||
Reference in New Issue
Block a user