From 4f42b946f34772a7f40a6728cc075eb6ce90982b Mon Sep 17 00:00:00 2001 From: Leonardo Bambini Date: Wed, 7 Jan 2026 22:56:01 +0100 Subject: [PATCH 1/3] added ip rep fetch + bug fix --- src/analyzer.py | 36 ++++++++++++++++++++++++++++++++++++ src/database.py | 27 ++++++++++++++++++++++++++- src/handler.py | 1 + src/models.py | 33 ++------------------------------- src/sanitizer.py | 5 ++++- 5 files changed, 69 insertions(+), 33 deletions(-) diff --git a/src/analyzer.py b/src/analyzer.py index a745813..85ce529 100644 --- a/src/analyzer.py +++ b/src/analyzer.py @@ -8,6 +8,9 @@ from datetime import datetime, timedelta import re from wordlists import get_wordlists from config import get_config +import requests +from sanitizer import sanitize_for_storage, sanitize_dict + """ Functions for user activity analysis """ @@ -228,6 +231,10 @@ class Analyzer: for name, pattern in wl.attack_urls.items(): if re.search(pattern, queried_path, re.IGNORECASE): attack_urls_found_list.append(pattern) + + #remove duplicates + attack_urls_found_list = set(attack_urls_found_list) + attack_urls_found_list = list(attack_urls_found_list) if len(attack_urls_found_list) > attack_urls_threshold: score["attacker"]["attack_url"] = True @@ -281,3 +288,32 @@ class Analyzer: self._db_manager.update_ip_stats_analysis(ip, analyzed_metrics, category, category_scores, last_analysis) return 0 + + def update_ip_rep_infos(self, ip: str) -> list[str]: + api_url = "https://iprep.lcrawl.com/api/iprep/" + params = { + "cidr": ip + } + headers = { + "Content-Type": "application/json" + } + + response = requests.get(api_url, headers=headers, params=params) + payload = response.json() + + if payload["results"]: + data = payload["results"][0] + + country_iso_code = data["geoip_data"]["country_iso_code"] + asn = data["geoip_data"]["asn_autonomous_system_number"] + asn_org = data["geoip_data"]["asn_autonomous_system_organization"] + list_on = data["list_on"] + + sanitized_country_iso_code = sanitize_for_storage(country_iso_code, 3) + sanitized_asn = sanitize_for_storage(asn, 100) + sanitized_asn_org = sanitize_for_storage(asn_org, 100) + sanitized_list_on = sanitize_dict(list_on, 100000) + + self._db_manager.update_ip_rep_infos(ip, sanitized_country_iso_code, sanitized_asn, sanitized_asn_org, sanitized_list_on) + + return \ No newline at end of file diff --git a/src/database.py b/src/database.py index 9d8e444..b5622db 100644 --- a/src/database.py +++ b/src/database.py @@ -246,7 +246,7 @@ class DatabaseManager: ip_stats.category_scores = category_scores ip_stats.last_analysis = last_analysis - def manual_update_category(self, ip: str, category: str) -> None: + def manual_update_category(self, ip: str, category: str) -> None: """ Update IP category as a result of a manual intervention by an admin @@ -257,11 +257,36 @@ class DatabaseManager: """ session = self.session + sanitized_ip = sanitize_ip(ip) ip_stats = session.query(IpStats).filter(IpStats.ip == sanitized_ip).first() + ip_stats.category = category ip_stats.manual_category = True + def update_ip_rep_infos(self, ip: str, country_code: str, asn: str, asn_org: str, list_on: Dict[str,str]) -> None: + """ + Update IP rep stats + + Args: + ip: IP address + country_code: IP address country code + asn: IP address ASN + asn_org: IP address ASN ORG + list_on: public lists containing the IP address + + """ + session = self.session + + sanitized_ip = sanitize_ip(ip) + ip_stats = session.query(IpStats).filter(IpStats.ip == sanitized_ip).first() + + ip_stats.country_code = country_code + ip_stats.asn = asn + ip_stats.asn_org = asn_org + ip_stats.list_on = list_on + + def get_access_logs( self, limit: int = 100, diff --git a/src/handler.py b/src/handler.py index eef528d..00238e7 100644 --- a/src/handler.py +++ b/src/handler.py @@ -417,6 +417,7 @@ class Handler(BaseHTTPRequestHandler): self.tracker.record_access(client_ip, self.path, user_agent, method='GET') self.analyzer.infer_user_category(client_ip) + self.analyzer.update_ip_rep_infos(client_ip) if self.tracker.is_suspicious_user_agent(user_agent): self.access_logger.warning(f"[SUSPICIOUS] {client_ip} - {user_agent[:50]} - {self.path}") diff --git a/src/models.py b/src/models.py index 190ef26..5e5cd2c 100644 --- a/src/models.py +++ b/src/models.py @@ -134,6 +134,7 @@ class IpStats(Base): city: Mapped[Optional[str]] = mapped_column(String(MAX_CITY_LENGTH), nullable=True) asn: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) asn_org: Mapped[Optional[str]] = mapped_column(String(MAX_ASN_ORG_LENGTH), nullable=True) + list_on: Mapped[Optional[Dict[str,str]]] = mapped_column(JSON, nullable=True) # Reputation fields (populated by future enrichment) reputation_score: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) @@ -149,34 +150,4 @@ class IpStats(Base): def __repr__(self) -> str: - return f"" - -# class IpLog(Base): -# """ -# Records all IPs that have accessed the honeypot, along with aggregated stats and inferred user category. -# """ -# __tablename__ = 'ip_logs' - -# id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) -# ip: Mapped[str] = mapped_column(String(MAX_IP_LENGTH), nullable=False, index=True) -# stats: Mapped[List[str]] = mapped_column(String(MAX_PATH_LENGTH)) -# category: Mapped[str] = mapped_column(String(15)) -# manual_category: Mapped[bool] = mapped_column(Boolean, default=False) -# last_analysis: Mapped[datetime] = mapped_column(DateTime, index=True), - -# # Relationship to attack detections -# access_logs: Mapped[List["AccessLog"]] = relationship( -# "AccessLog", -# back_populates="ip", -# cascade="all, delete-orphan" -# ) - -# # Indexes for common queries -# __table_args__ = ( -# Index('ix_access_logs_ip_timestamp', 'ip', 'timestamp'), -# Index('ix_access_logs_is_suspicious', 'is_suspicious'), -# Index('ix_access_logs_is_honeypot_trigger', 'is_honeypot_trigger'), -# ) - -# def __repr__(self) -> str: -# return f"" \ No newline at end of file + return f"" \ No newline at end of file diff --git a/src/sanitizer.py b/src/sanitizer.py index f783129..a04d0c0 100644 --- a/src/sanitizer.py +++ b/src/sanitizer.py @@ -7,7 +7,7 @@ Protects against SQL injection payloads, XSS, and storage exhaustion attacks. import html import re -from typing import Optional +from typing import Optional, Dict # Field length limits for database storage @@ -111,3 +111,6 @@ def escape_html_truncated(value: Optional[str], max_display_length: int) -> str: value_str = value_str[:max_display_length] + "..." return html.escape(value_str) + +def sanitize_dict(value: Optional[Dict[str,str]], max_display_length): + return {k: sanitize_for_storage(v, max_display_length) for k, v in value.items()} \ No newline at end of file From c2dbcf588c0bb25734ccea7e03dfb5687aa9aeb9 Mon Sep 17 00:00:00 2001 From: Patrick Di Fazio Date: Sat, 10 Jan 2026 20:00:33 +0100 Subject: [PATCH 2/3] added iprep to the dashboard, fixed bugs --- .gitignore | 2 +- config.yaml | 4 +- docker-compose.yaml | 6 -- src/database.py | 1 + src/exports/malicious_ips.txt | 1 - src/templates/dashboard_template.py | 153 ++++++++++++++++++++-------- 6 files changed, 112 insertions(+), 55 deletions(-) delete mode 100644 src/exports/malicious_ips.txt diff --git a/.gitignore b/.gitignore index 63ae0e9..ecc3154 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,4 @@ data/ personal-values.yaml #exports dir (keeping .gitkeep so we have the dir) -/exports/* \ No newline at end of file +/exports/* diff --git a/config.yaml b/config.yaml index 52daa09..6c45617 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,7 @@ # Krawl Honeypot Configuration server: - port: 5000 + port: 1234 delay: 100 # Response delay in milliseconds timezone: null # e.g., "America/New_York", "Europe/Paris" or null for system default @@ -23,7 +23,7 @@ canary: dashboard: # if set to "null" this will Auto-generates random path if not set # can be set to "/dashboard" or similar <-- note this MUST include a forward slash - secret_path: dashboard + secret_path: super-secret-dashboard-path api: server_url: null diff --git a/docker-compose.yaml b/docker-compose.yaml index 08bcec9..d8ea198 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -16,9 +16,3 @@ services: environment: - CONFIG_LOCATION=config.yaml restart: unless-stopped - healthcheck: - test: ["CMD", "python3", "-c", "import requests; requests.get('http://localhost:5000')"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s diff --git a/src/database.py b/src/database.py index 5c96828..59d7072 100644 --- a/src/database.py +++ b/src/database.py @@ -578,6 +578,7 @@ class DatabaseManager: 'city': stat.city, 'asn': stat.asn, 'asn_org': stat.asn_org, + 'list_on': stat.list_on or {}, 'reputation_score': stat.reputation_score, 'reputation_source': stat.reputation_source, 'analyzed_metrics': stat.analyzed_metrics or {}, diff --git a/src/exports/malicious_ips.txt b/src/exports/malicious_ips.txt deleted file mode 100644 index 7b9ad53..0000000 --- a/src/exports/malicious_ips.txt +++ /dev/null @@ -1 +0,0 @@ -127.0.0.1 diff --git a/src/templates/dashboard_template.py b/src/templates/dashboard_template.py index 4e7005c..4c5a77a 100644 --- a/src/templates/dashboard_template.py +++ b/src/templates/dashboard_template.py @@ -410,6 +410,12 @@ def generate_dashboard(stats: dict, timezone: str = 'UTC', dashboard_path: str = color: #58a6ff; font-size: 13px; font-weight: 600; + }} + .timeline-header {{ + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; margin-bottom: 10px; }} .timeline {{ @@ -470,6 +476,56 @@ def generate_dashboard(stats: dict, timezone: str = 'UTC', dashboard_path: str = color: #8b949e; margin: 0 7px; }} + .reputation-container {{ + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #30363d; + }} + .reputation-title {{ + color: #58a6ff; + font-size: 13px; + font-weight: 600; + }} + .reputation-badges {{ + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; + }} + .reputation-badge {{ + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + background: #161b22; + border: 1px solid #f851494d; + border-radius: 4px; + font-size: 11px; + color: #f85149; + text-decoration: none; + transition: all 0.2s; + }} + .reputation-badge:hover {{ + background: #1c2128; + border-color: #f85149; + }} + .reputation-badge-icon {{ + font-size: 12px; + }} + .reputation-clean {{ + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: #161b22; + border: 1px solid #3fb9504d; + border-radius: 4px; + font-size: 11px; + color: #3fb950; + }} + .reputation-clean-icon {{ + font-size: 13px; + }} @@ -627,11 +683,9 @@ def generate_dashboard(stats: dict, timezone: str = 'UTC', dashboard_path: str =