From e4cf4702ebb0c36d9677f5cf0ddfb0461232bdf1 Mon Sep 17 00:00:00 2001 From: carnivuth Date: Tue, 27 Jan 2026 00:09:27 +0100 Subject: [PATCH 1/5] added api endpoint to list public malicious ips --- src/config.py | 3 ++ src/handler.py | 116 ++++++++++++++++++++++--------------------------- 2 files changed, 55 insertions(+), 64 deletions(-) diff --git a/src/config.py b/src/config.py index 71cef0e..97b4d70 100644 --- a/src/config.py +++ b/src/config.py @@ -43,6 +43,7 @@ class Config: # Database settings database_path: str = "data/krawl.db" database_retention_days: int = 30 + exports_path: str = "data/exports" # Analyzer settings http_risky_methods_threshold: float = None @@ -153,6 +154,7 @@ class Config: canary = data.get("canary", {}) dashboard = data.get("dashboard", {}) api = data.get("api", {}) + exports = data.get("exports", {}) database = data.get("database", {}) behavior = data.get("behavior", {}) analyzer = data.get("analyzer") or {} @@ -191,6 +193,7 @@ class Config: api_server_port=api.get("server_port", 8080), api_server_path=api.get("server_path", "/api/v2/users"), probability_error_codes=behavior.get("probability_error_codes", 0), + exports_path = exports.get("path"), database_path=database.get("path", "data/krawl.db"), database_retention_days=database.get("retention_days", 30), http_risky_methods_threshold=analyzer.get( diff --git a/src/handler.py b/src/handler.py index b3c76e7..ab1f715 100644 --- a/src/handler.py +++ b/src/handler.py @@ -7,8 +7,11 @@ from datetime import datetime from http.server import BaseHTTPRequestHandler from typing import Optional, List from urllib.parse import urlparse, parse_qs +import json +import os -from config import Config +from database import get_database +from config import Config,get_config from tracker import AccessTracker from analyzer import Analyzer from templates import html_templates @@ -26,6 +29,9 @@ from wordlists import get_wordlists from sql_errors import generate_sql_error_response, get_sql_response_with_data from xss_detector import detect_xss_pattern, generate_xss_response from server_errors import generate_server_error +from models import AccessLog +from ip_utils import is_valid_public_ip +from sqlalchemy import distinct class Handler(BaseHTTPRequestHandler): @@ -58,10 +64,6 @@ class Handler(BaseHTTPRequestHandler): # Fallback to direct connection IP return self.client_address[0] - def _get_user_agent(self) -> str: - """Extract user agent from request""" - return self.headers.get("User-Agent", "") - def _get_category_by_ip(self, client_ip: str) -> str: """Get the category of an IP from the database""" return self.tracker.get_category_by_ip(client_ip) @@ -92,10 +94,6 @@ class Handler(BaseHTTPRequestHandler): error_codes = [400, 401, 403, 404, 500, 502, 503] return random.choice(error_codes) - def _parse_query_string(self) -> str: - """Extract query string from the request path""" - parsed = urlparse(self.path) - return parsed.query def _handle_sql_endpoint(self, path: str) -> bool: """ @@ -111,21 +109,20 @@ class Handler(BaseHTTPRequestHandler): try: # Get query parameters - query_string = self._parse_query_string() # Log SQL injection attempt client_ip = self._get_client_ip() - user_agent = self._get_user_agent() + user_agent = self.headers.get("User-Agent", "") # Always check for SQL injection patterns error_msg, content_type, status_code = generate_sql_error_response( - query_string or "" + request_query or "" ) if error_msg: # SQL injection detected - log and return error self.access_logger.warning( - f"[SQL INJECTION DETECTED] {client_ip} - {base_path} - Query: {query_string[:100] if query_string else 'empty'}" + f"[SQL INJECTION DETECTED] {client_ip} - {base_path} - Query: {request_query[:100] if request_query else 'empty'}" ) self.send_response(status_code) self.send_header("Content-type", content_type) @@ -134,13 +131,13 @@ class Handler(BaseHTTPRequestHandler): else: # No injection detected - return fake data self.access_logger.info( - f"[SQL ENDPOINT] {client_ip} - {base_path} - Query: {query_string[:100] if query_string else 'empty'}" + f"[SQL ENDPOINT] {client_ip} - {base_path} - Query: {request_query[:100] if request_query else 'empty'}" ) self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() response_data = get_sql_response_with_data( - base_path, query_string or "" + base_path, request_query or "" ) self.wfile.write(response_data.encode()) @@ -239,10 +236,9 @@ class Handler(BaseHTTPRequestHandler): def do_POST(self): """Handle POST requests (mainly login attempts)""" client_ip = self._get_client_ip() - user_agent = self._get_user_agent() + user_agent = self.headers.get("User-Agent", "") post_data = "" - from urllib.parse import urlparse base_path = urlparse(self.path).path @@ -293,7 +289,6 @@ class Handler(BaseHTTPRequestHandler): for pair in post_data.split("&"): if "=" in pair: key, value = pair.split("=", 1) - from urllib.parse import unquote_plus parsed_data[unquote_plus(key)] = unquote_plus(value) @@ -486,12 +481,25 @@ class Handler(BaseHTTPRequestHandler): def do_GET(self): """Responds to webpage requests""" + client_ip = self._get_client_ip() + + # respond with HTTP error code if client is banned if self.tracker.is_banned_ip(client_ip): self.send_response(500) self.end_headers() return - user_agent = self._get_user_agent() + + # get request data + user_agent = self.headers.get("User-Agent", "") + request_path = urlparse(self.path).path + self.app_logger.info(f"request_query: {request_path}") + query_params = parse_qs(urlparse(self.path).query) + self.app_logger.info(f"query_params: {query_params}") + + # get database reference + db = get_database() + session = db.session if ( self.config.dashboard_secret_path @@ -502,8 +510,7 @@ class Handler(BaseHTTPRequestHandler): self.end_headers() try: stats = self.tracker.get_stats() - dashboard_path = self.config.dashboard_secret_path - self.wfile.write(generate_dashboard(stats, dashboard_path).encode()) + self.wfile.write(generate_dashboard(stats, self.config.dashboard_secret_path).encode()) except BrokenPipeError: pass except Exception as e: @@ -525,10 +532,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - db = get_database() ip_stats_list = db.get_ip_stats(limit=500) self.wfile.write(json.dumps({"ips": ip_stats_list}).encode()) except BrokenPipeError: @@ -552,15 +556,8 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - from urllib.parse import urlparse, parse_qs - db = get_database() - # Parse query parameters - parsed_url = urlparse(self.path) - query_params = parse_qs(parsed_url.query) page = int(query_params.get("page", ["1"])[0]) page_size = int(query_params.get("page_size", ["25"])[0]) sort_by = query_params.get("sort_by", ["total_requests"])[0] @@ -598,11 +595,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - from urllib.parse import urlparse, parse_qs - db = get_database() # Parse query parameters parsed_url = urlparse(self.path) @@ -648,10 +641,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - db = get_database() ip_stats = db.get_ip_stats_by_ip(ip_address) if ip_stats: self.wfile.write(json.dumps(ip_stats).encode()) @@ -678,11 +668,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - from urllib.parse import urlparse, parse_qs - db = get_database() parsed_url = urlparse(self.path) query_params = parse_qs(parsed_url.query) page = int(query_params.get("page", ["1"])[0]) @@ -721,11 +707,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - from urllib.parse import urlparse, parse_qs - db = get_database() parsed_url = urlparse(self.path) query_params = parse_qs(parsed_url.query) page = int(query_params.get("page", ["1"])[0]) @@ -764,11 +746,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - from urllib.parse import urlparse, parse_qs - db = get_database() parsed_url = urlparse(self.path) query_params = parse_qs(parsed_url.query) page = int(query_params.get("page", ["1"])[0]) @@ -782,7 +760,7 @@ class Handler(BaseHTTPRequestHandler): result = db.get_top_ips_paginated( page=page, page_size=page_size, - sort_by=sort_by, +pathsort_by=sort_by, sort_order=sort_order, ) self.wfile.write(json.dumps(result).encode()) @@ -807,11 +785,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - from urllib.parse import urlparse, parse_qs - db = get_database() parsed_url = urlparse(self.path) query_params = parse_qs(parsed_url.query) page = int(query_params.get("page", ["1"])[0]) @@ -850,11 +824,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - from urllib.parse import urlparse, parse_qs - db = get_database() parsed_url = urlparse(self.path) query_params = parse_qs(parsed_url.query) page = int(query_params.get("page", ["1"])[0]) @@ -893,11 +863,7 @@ class Handler(BaseHTTPRequestHandler): self.send_header("Expires", "0") self.end_headers() try: - from database import get_database - import json - from urllib.parse import urlparse, parse_qs - db = get_database() parsed_url = urlparse(self.path) query_params = parse_qs(parsed_url.query) page = int(query_params.get("page", ["1"])[0]) @@ -922,13 +888,35 @@ class Handler(BaseHTTPRequestHandler): self.wfile.write(json.dumps({"error": str(e)}).encode()) return + # API endpoint for downloading malicious IPs blocklist file + if ( + self.config.dashboard_secret_path and + request_path == f"{self.config.dashboard_secret_path}/api/get_banlist" + ): + + + fwtype = query_params.get("fwtype",["iptables"])[0] + # Query distinct suspicious IPs + results = ( + session.query(distinct(AccessLog.ip)) + .filter(AccessLog.is_suspicious == True) + .all() + ) + + # Filter out local/private IPs and the server's own IP + config = get_config() + server_ip = config.get_server_ip() + + public_ips = [ip for (ip,) in results if is_valid_public_ip(ip, server_ip)] + self.wfile.write(f"asdasdd {fwtype} {public_ips}".encode()) + return + # API endpoint for downloading malicious IPs file if ( self.config.dashboard_secret_path and self.path == f"{self.config.dashboard_secret_path}/api/download/malicious_ips.txt" ): - import os file_path = os.path.join( os.path.dirname(__file__), "exports", "malicious_ips.txt" From ace04c1f5ee208fdd4d86defdb5cd4c174c6f889 Mon Sep 17 00:00:00 2001 From: carnivuth Date: Tue, 27 Jan 2026 17:41:41 +0100 Subject: [PATCH 2/5] added firewall strategy pattern --- src/firewall/fwtype.py | 40 +++++++++++++++++++++++++++++++++++++ src/firewall/iptables.py | 43 ++++++++++++++++++++++++++++++++++++++++ src/firewall/raw.py | 20 +++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 src/firewall/fwtype.py create mode 100644 src/firewall/iptables.py create mode 100644 src/firewall/raw.py diff --git a/src/firewall/fwtype.py b/src/firewall/fwtype.py new file mode 100644 index 0000000..2995489 --- /dev/null +++ b/src/firewall/fwtype.py @@ -0,0 +1,40 @@ +from abc import ABC, abstractmethod +from typing import Dict, Type + + +class FWType(ABC): + """Abstract base class for firewall types.""" + + # Registry to store child classes + _registry: Dict[str, Type['FWType']] = {} + + def __init_subclass__(cls, **kwargs): + """Automatically register subclasses with their class name.""" + super().__init_subclass__(**kwargs) + cls._registry[cls.__name__.lower()] = cls + + @classmethod + def create(cls, fw_type: str, **kwargs) -> 'FWType': + """ + Factory method to create instances of child classes. + + Args: + fw_type: String name of the firewall type class to instantiate + **kwargs: Arguments to pass to the child class constructor + + Returns: + Instance of the requested child class + + Raises: + ValueError: If fw_type is not registered + """ + fw_type = fw_type.lower() + if fw_type not in cls._registry: + available = ', '.join(cls._registry.keys()) + raise ValueError(f"Unknown firewall type: '{fw_type}'. Available: {available}") + + return cls._registry[fw_type](**kwargs) + + @abstractmethod + def getBanlist(self,ips): + """Return the ruleset for the specific server""" diff --git a/src/firewall/iptables.py b/src/firewall/iptables.py new file mode 100644 index 0000000..73ac623 --- /dev/null +++ b/src/firewall/iptables.py @@ -0,0 +1,43 @@ +from typing_extensions import override +from firewall.fwtype import FWType + +class Iptables(FWType): + + @override + def getBanlist(self,ips) -> str: + """ + Generate iptables ban rules from an array of IP addresses. + + Args: + ips: List of IP addresses to ban + + Returns: + String containing iptables commands, one per line + """ + if not ips: + return "" + + rules = [] + chain = "INPUT" + target = "DROP" + rules.append("#!/bin/bash") + rules.append("# iptables ban rules") + rules.append("") + + for ip in ips: + + ip = ip.strip() + + # Build the iptables command + rule_parts = [ + "iptables", + "-A", chain, + "-s", ip + ] + + # Add target + rule_parts.extend(["-j", target]) + + rules.append(" ".join(rule_parts)) + + return "\n".join(rules) diff --git a/src/firewall/raw.py b/src/firewall/raw.py new file mode 100644 index 0000000..1e29e75 --- /dev/null +++ b/src/firewall/raw.py @@ -0,0 +1,20 @@ +from typing_extensions import override +from firewall.fwtype import FWType + +class Raw(FWType): + + @override + def getBanlist(self,ips) -> str: + """ + Generate raw list of bad IP addresses. + + Args: + ips: List of IP addresses to ban + + Returns: + String containing raw ips, one per line + """ + if not ips: + return "" + + return "\n".join(ips) From 630293d55ce7dab90551c9eb9555d2d4b97e2b33 Mon Sep 17 00:00:00 2001 From: carnivuth Date: Tue, 27 Jan 2026 17:42:07 +0100 Subject: [PATCH 3/5] added endpoint for blocklist download api --- src/handler.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/handler.py b/src/handler.py index ab1f715..3c9f6f6 100644 --- a/src/handler.py +++ b/src/handler.py @@ -12,6 +12,12 @@ import os from database import get_database from config import Config,get_config +from firewall.fwtype import FWType + +# imports for the __init_subclass__ method, do not remove pls +from firewall.iptables import Iptables +from firewall.raw import Raw + from tracker import AccessTracker from analyzer import Analyzer from templates import html_templates @@ -894,8 +900,9 @@ pathsort_by=sort_by, request_path == f"{self.config.dashboard_secret_path}/api/get_banlist" ): - + # get fwtype from request params fwtype = query_params.get("fwtype",["iptables"])[0] + # Query distinct suspicious IPs results = ( session.query(distinct(AccessLog.ip)) @@ -906,9 +913,18 @@ pathsort_by=sort_by, # Filter out local/private IPs and the server's own IP config = get_config() server_ip = config.get_server_ip() - public_ips = [ip for (ip,) in results if is_valid_public_ip(ip, server_ip)] - self.wfile.write(f"asdasdd {fwtype} {public_ips}".encode()) + + # get specific fwtype based on query parameter + fwtype_parser = FWType.create(fwtype) + banlist = fwtype_parser.getBanlist(public_ips) + + self.send_response(200) + self.send_header("Content-type", "text/plain") + self.send_header("Content-Disposition", f'attachment; filename="{fwtype}.txt"',) + self.send_header("Content-Length", str(len(banlist))) + self.end_headers() + self.wfile.write(banlist.encode()) return # API endpoint for downloading malicious IPs file From 2d27b02bc83a68706e484a15a8fd93b5c04ecaac Mon Sep 17 00:00:00 2001 From: carnivuth Date: Tue, 27 Jan 2026 17:42:20 +0100 Subject: [PATCH 4/5] refactor form for blocklist download --- src/templates/dashboard_template.py | 85 ++++++++++++++++------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/src/templates/dashboard_template.py b/src/templates/dashboard_template.py index 3ef693f..a90fa10 100644 --- a/src/templates/dashboard_template.py +++ b/src/templates/dashboard_template.py @@ -9,6 +9,11 @@ import html from datetime import datetime from zoneinfo import ZoneInfo +# imports for the __init_subclass__ method, do not remove pls +from firewall import fwtype +from firewall.iptables import Iptables +from firewall.raw import Raw + def _escape(value) -> str: """Escape HTML special characters to prevent XSS attacks.""" @@ -590,11 +595,13 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str:
- +
+ + +

Krawl Dashboard

@@ -1040,7 +1047,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: if (stats.category_history && stats.category_history.length > 0) {{ html += '
'; html += '
'; - + // Timeline column html += '
'; html += '
Behavior Timeline
'; @@ -1051,18 +1058,18 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: const timestamp = formatTimestamp(change.timestamp); const oldClass = change.old_category ? 'category-' + change.old_category.toLowerCase().replace('_', '-') : ''; const newClass = 'category-' + categoryClass; - + html += '
'; html += `
`; html += '
'; - + if (change.old_category) {{ html += `${{change.old_category}}`; html += ''; }} else {{ html += 'Initial:'; }} - + html += `${{change.new_category}}`; html += `
${{timestamp}}
`; html += '
'; @@ -1071,14 +1078,14 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: html += '
'; html += '
'; - + // Reputation column html += '
'; - + if (stats.list_on && Object.keys(stats.list_on).length > 0) {{ html += '
Listed On
'; const sortedSources = Object.entries(stats.list_on).sort((a, b) => a[0].localeCompare(b[0])); - + sortedSources.forEach(([source, url]) => {{ if (url && url !== 'N/A') {{ html += `${{source}}`; @@ -1090,7 +1097,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: html += '
Reputation
'; html += '✓ Clean'; }} - + html += '
'; html += '
'; html += '
'; @@ -1203,23 +1210,23 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: document.querySelectorAll('.tab-content').forEach(tab => {{ tab.classList.remove('active'); }}); - + // Remove active class from all buttons document.querySelectorAll('.tab-button').forEach(btn => {{ btn.classList.remove('active'); }}); - + // Show selected tab const selectedTab = document.getElementById(tabName); const selectedButton = document.querySelector(`.tab-button[href="#${{tabName}}"]`); - + if (selectedTab) {{ selectedTab.classList.add('active'); }} if (selectedButton) {{ selectedButton.classList.add('active'); }} - + // Load data for this tab if (tabName === 'ip-stats') {{ loadIpStatistics(1); @@ -1261,7 +1268,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: if (e.target.classList.contains('sortable') && e.target.closest('#ip-stats-tbody')) {{ return; // Don't sort when inside tbody }} - + const sortHeader = e.target.closest('th.sortable'); if (!sortHeader) return; @@ -1269,7 +1276,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: if (!table || !table.classList.contains('ip-stats-table')) return; const sortField = sortHeader.getAttribute('data-sort'); - + // Toggle sort order if clicking the same field if (currentSortBy === sortField) {{ currentSortOrder = currentSortOrder === 'desc' ? 'asc' : 'desc'; @@ -1300,9 +1307,9 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: console.error('IP stats tbody not found'); return; }} - + tbody.innerHTML = 'Loading...'; - + try {{ console.log('Fetching attackers from page:', page, 'sort:', currentSortBy, currentSortOrder); const response = await fetch(DASHBOARD_PATH + '/api/attackers?page=' + page + '&page_size=' + PAGE_SIZE + '&sort_by=' + currentSortBy + '&sort_order=' + currentSortOrder, {{ @@ -1312,14 +1319,14 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: 'Pragma': 'no-cache' }} }}); - + console.log('Response status:', response.status); - + if (!response.ok) throw new Error(`HTTP ${{response.status}}`); - + const data = await response.json(); console.log('Received data:', data); - + if (!data.attackers || data.attackers.length === 0) {{ tbody.innerHTML = 'No attackers on this page.'; currentPage = page; @@ -1327,7 +1334,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: updatePaginationControls(); return; }} - + // Update pagination info currentPage = data.pagination.page; totalPages = data.pagination.total_pages; @@ -1335,7 +1342,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: document.getElementById('total-pages').textContent = totalPages; document.getElementById('total-attackers').textContent = data.pagination.total_attackers; updatePaginationControls(); - + let html = ''; data.attackers.forEach((attacker, index) => {{ const rank = (currentPage - 1) * PAGE_SIZE + index + 1; @@ -1355,10 +1362,10 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: `; }}); - + tbody.innerHTML = html; console.log('Populated', data.attackers.length, 'attacker records'); - + // Re-attach click listeners for expandable rows attachAttackerClickListeners(); }} catch (err) {{ @@ -1370,7 +1377,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: function updatePaginationControls() {{ const prevBtn = document.getElementById('prev-page-btn'); const nextBtn = document.getElementById('next-page-btn'); - + if (prevBtn) prevBtn.disabled = currentPage <= 1; if (nextBtn) nextBtn.disabled = currentPage >= totalPages; }} @@ -1642,7 +1649,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: async function loadOverviewTable(tableId) {{ const config = tableConfig[tableId]; if (!config) return; - + const state = overviewState[tableId]; const tbody = document.getElementById(tableId + '-tbody'); if (!tbody) return; @@ -1676,7 +1683,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: let html = ''; items.forEach((item, index) => {{ const rank = (state.currentPage - 1) * 5 + index + 1; - + if (tableId === 'honeypot') {{ html += `${{rank}}${{item.ip}}${{item.paths.join(', ')}}${{item.count}}`; html += ` @@ -1822,12 +1829,12 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: async function showIpDetail(ip) {{ const modal = document.getElementById('ip-detail-modal'); const bodyDiv = document.getElementById('ip-detail-body'); - + if (!modal || !bodyDiv) return; - + bodyDiv.innerHTML = '
Loading IP details...
'; modal.classList.add('show'); - + try {{ const response = await fetch(`${{DASHBOARD_PATH}}/api/ip-stats/${{ip}}`, {{ cache: 'no-store', @@ -1836,9 +1843,9 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: 'Pragma': 'no-cache' }} }}); - + if (!response.ok) throw new Error(`HTTP ${{response.status}}`); - + const stats = await response.json(); bodyDiv.innerHTML = '

' + stats.ip + ' - Detailed Statistics

' + formatIpStats(stats); }} catch (err) {{ @@ -2138,7 +2145,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: // Initialize map when Attacks tab is opened const originalSwitchTab = window.switchTab; let attackTypesChartLoaded = false; - + window.switchTab = function(tabName) {{ originalSwitchTab(tabName); if (tabName === 'ip-stats') {{ @@ -2171,7 +2178,7 @@ def generate_dashboard(stats: dict, dashboard_path: str = "") -> str: }}); if (!response.ok) throw new Error('Failed to fetch attack types'); - + const data = await response.json(); const attacks = data.attacks || []; From 320f6ffd36c0026c934061545ae39c265bdf8013 Mon Sep 17 00:00:00 2001 From: carnivuth Date: Tue, 27 Jan 2026 17:53:11 +0100 Subject: [PATCH 5/5] linted code --- src/firewall/fwtype.py | 12 +++++++----- src/firewall/iptables.py | 9 +++------ src/firewall/raw.py | 3 ++- src/handler.py | 25 ++++++++++++++----------- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/firewall/fwtype.py b/src/firewall/fwtype.py index 2995489..0e0e421 100644 --- a/src/firewall/fwtype.py +++ b/src/firewall/fwtype.py @@ -6,7 +6,7 @@ class FWType(ABC): """Abstract base class for firewall types.""" # Registry to store child classes - _registry: Dict[str, Type['FWType']] = {} + _registry: Dict[str, Type["FWType"]] = {} def __init_subclass__(cls, **kwargs): """Automatically register subclasses with their class name.""" @@ -14,7 +14,7 @@ class FWType(ABC): cls._registry[cls.__name__.lower()] = cls @classmethod - def create(cls, fw_type: str, **kwargs) -> 'FWType': + def create(cls, fw_type: str, **kwargs) -> "FWType": """ Factory method to create instances of child classes. @@ -30,11 +30,13 @@ class FWType(ABC): """ fw_type = fw_type.lower() if fw_type not in cls._registry: - available = ', '.join(cls._registry.keys()) - raise ValueError(f"Unknown firewall type: '{fw_type}'. Available: {available}") + available = ", ".join(cls._registry.keys()) + raise ValueError( + f"Unknown firewall type: '{fw_type}'. Available: {available}" + ) return cls._registry[fw_type](**kwargs) @abstractmethod - def getBanlist(self,ips): + def getBanlist(self, ips): """Return the ruleset for the specific server""" diff --git a/src/firewall/iptables.py b/src/firewall/iptables.py index 73ac623..159171e 100644 --- a/src/firewall/iptables.py +++ b/src/firewall/iptables.py @@ -1,10 +1,11 @@ from typing_extensions import override from firewall.fwtype import FWType + class Iptables(FWType): @override - def getBanlist(self,ips) -> str: + def getBanlist(self, ips) -> str: """ Generate iptables ban rules from an array of IP addresses. @@ -29,11 +30,7 @@ class Iptables(FWType): ip = ip.strip() # Build the iptables command - rule_parts = [ - "iptables", - "-A", chain, - "-s", ip - ] + rule_parts = ["iptables", "-A", chain, "-s", ip] # Add target rule_parts.extend(["-j", target]) diff --git a/src/firewall/raw.py b/src/firewall/raw.py index 1e29e75..e0c82fe 100644 --- a/src/firewall/raw.py +++ b/src/firewall/raw.py @@ -1,10 +1,11 @@ from typing_extensions import override from firewall.fwtype import FWType + class Raw(FWType): @override - def getBanlist(self,ips) -> str: + def getBanlist(self, ips) -> str: """ Generate raw list of bad IP addresses. diff --git a/src/handler.py b/src/handler.py index 3c9f6f6..38a15d1 100644 --- a/src/handler.py +++ b/src/handler.py @@ -11,7 +11,7 @@ import json import os from database import get_database -from config import Config,get_config +from config import Config, get_config from firewall.fwtype import FWType # imports for the __init_subclass__ method, do not remove pls @@ -100,7 +100,6 @@ class Handler(BaseHTTPRequestHandler): error_codes = [400, 401, 403, 404, 500, 502, 503] return random.choice(error_codes) - def _handle_sql_endpoint(self, path: str) -> bool: """ Handle SQL injection honeypot endpoints. @@ -245,7 +244,6 @@ class Handler(BaseHTTPRequestHandler): user_agent = self.headers.get("User-Agent", "") post_data = "" - base_path = urlparse(self.path).path if base_path in ["/api/search", "/api/sql", "/api/database"]: @@ -516,7 +514,11 @@ class Handler(BaseHTTPRequestHandler): self.end_headers() try: stats = self.tracker.get_stats() - self.wfile.write(generate_dashboard(stats, self.config.dashboard_secret_path).encode()) + self.wfile.write( + generate_dashboard( + stats, self.config.dashboard_secret_path + ).encode() + ) except BrokenPipeError: pass except Exception as e: @@ -563,7 +565,6 @@ class Handler(BaseHTTPRequestHandler): self.end_headers() try: - page = int(query_params.get("page", ["1"])[0]) page_size = int(query_params.get("page_size", ["25"])[0]) sort_by = query_params.get("sort_by", ["total_requests"])[0] @@ -602,7 +603,6 @@ class Handler(BaseHTTPRequestHandler): self.end_headers() try: - # Parse query parameters parsed_url = urlparse(self.path) query_params = parse_qs(parsed_url.query) @@ -766,7 +766,7 @@ class Handler(BaseHTTPRequestHandler): result = db.get_top_ips_paginated( page=page, page_size=page_size, -pathsort_by=sort_by, + pathsort_by=sort_by, sort_order=sort_order, ) self.wfile.write(json.dumps(result).encode()) @@ -896,12 +896,12 @@ pathsort_by=sort_by, # API endpoint for downloading malicious IPs blocklist file if ( - self.config.dashboard_secret_path and - request_path == f"{self.config.dashboard_secret_path}/api/get_banlist" + self.config.dashboard_secret_path + and request_path == f"{self.config.dashboard_secret_path}/api/get_banlist" ): # get fwtype from request params - fwtype = query_params.get("fwtype",["iptables"])[0] + fwtype = query_params.get("fwtype", ["iptables"])[0] # Query distinct suspicious IPs results = ( @@ -921,7 +921,10 @@ pathsort_by=sort_by, self.send_response(200) self.send_header("Content-type", "text/plain") - self.send_header("Content-Disposition", f'attachment; filename="{fwtype}.txt"',) + self.send_header( + "Content-Disposition", + f'attachment; filename="{fwtype}.txt"', + ) self.send_header("Content-Length", str(len(banlist))) self.end_headers() self.wfile.write(banlist.encode())