added api endpoint to list public malicious ips
This commit is contained in:
@@ -40,6 +40,7 @@ class Config:
|
|||||||
# Database settings
|
# Database settings
|
||||||
database_path: str = "data/krawl.db"
|
database_path: str = "data/krawl.db"
|
||||||
database_retention_days: int = 30
|
database_retention_days: int = 30
|
||||||
|
exports_path: str = "data/exports"
|
||||||
|
|
||||||
# Analyzer settings
|
# Analyzer settings
|
||||||
http_risky_methods_threshold: float = None
|
http_risky_methods_threshold: float = None
|
||||||
@@ -150,6 +151,7 @@ class Config:
|
|||||||
canary = data.get("canary", {})
|
canary = data.get("canary", {})
|
||||||
dashboard = data.get("dashboard", {})
|
dashboard = data.get("dashboard", {})
|
||||||
api = data.get("api", {})
|
api = data.get("api", {})
|
||||||
|
exports = data.get("exports", {})
|
||||||
database = data.get("database", {})
|
database = data.get("database", {})
|
||||||
behavior = data.get("behavior", {})
|
behavior = data.get("behavior", {})
|
||||||
analyzer = data.get("analyzer") or {}
|
analyzer = data.get("analyzer") or {}
|
||||||
@@ -185,6 +187,7 @@ class Config:
|
|||||||
canary_token_tries=canary.get("token_tries", 10),
|
canary_token_tries=canary.get("token_tries", 10),
|
||||||
dashboard_secret_path=dashboard_path,
|
dashboard_secret_path=dashboard_path,
|
||||||
probability_error_codes=behavior.get("probability_error_codes", 0),
|
probability_error_codes=behavior.get("probability_error_codes", 0),
|
||||||
|
exports_path = exports.get("path"),
|
||||||
database_path=database.get("path", "data/krawl.db"),
|
database_path=database.get("path", "data/krawl.db"),
|
||||||
database_retention_days=database.get("retention_days", 30),
|
database_retention_days=database.get("retention_days", 30),
|
||||||
http_risky_methods_threshold=analyzer.get(
|
http_risky_methods_threshold=analyzer.get(
|
||||||
|
|||||||
116
src/handler.py
116
src/handler.py
@@ -7,8 +7,11 @@ from datetime import datetime
|
|||||||
from http.server import BaseHTTPRequestHandler
|
from http.server import BaseHTTPRequestHandler
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from urllib.parse import urlparse, parse_qs
|
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 tracker import AccessTracker
|
||||||
from analyzer import Analyzer
|
from analyzer import Analyzer
|
||||||
from templates import html_templates
|
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 sql_errors import generate_sql_error_response, get_sql_response_with_data
|
||||||
from xss_detector import detect_xss_pattern, generate_xss_response
|
from xss_detector import detect_xss_pattern, generate_xss_response
|
||||||
from server_errors import generate_server_error
|
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):
|
class Handler(BaseHTTPRequestHandler):
|
||||||
@@ -58,10 +64,6 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
# Fallback to direct connection IP
|
# Fallback to direct connection IP
|
||||||
return self.client_address[0]
|
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:
|
def _get_category_by_ip(self, client_ip: str) -> str:
|
||||||
"""Get the category of an IP from the database"""
|
"""Get the category of an IP from the database"""
|
||||||
return self.tracker.get_category_by_ip(client_ip)
|
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]
|
error_codes = [400, 401, 403, 404, 500, 502, 503]
|
||||||
return random.choice(error_codes)
|
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:
|
def _handle_sql_endpoint(self, path: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -111,21 +109,20 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Get query parameters
|
# Get query parameters
|
||||||
query_string = self._parse_query_string()
|
|
||||||
|
|
||||||
# Log SQL injection attempt
|
# Log SQL injection attempt
|
||||||
client_ip = self._get_client_ip()
|
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
|
# Always check for SQL injection patterns
|
||||||
error_msg, content_type, status_code = generate_sql_error_response(
|
error_msg, content_type, status_code = generate_sql_error_response(
|
||||||
query_string or ""
|
request_query or ""
|
||||||
)
|
)
|
||||||
|
|
||||||
if error_msg:
|
if error_msg:
|
||||||
# SQL injection detected - log and return error
|
# SQL injection detected - log and return error
|
||||||
self.access_logger.warning(
|
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_response(status_code)
|
||||||
self.send_header("Content-type", content_type)
|
self.send_header("Content-type", content_type)
|
||||||
@@ -134,13 +131,13 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
# No injection detected - return fake data
|
# No injection detected - return fake data
|
||||||
self.access_logger.info(
|
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_response(200)
|
||||||
self.send_header("Content-type", "application/json")
|
self.send_header("Content-type", "application/json")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
response_data = get_sql_response_with_data(
|
response_data = get_sql_response_with_data(
|
||||||
base_path, query_string or ""
|
base_path, request_query or ""
|
||||||
)
|
)
|
||||||
self.wfile.write(response_data.encode())
|
self.wfile.write(response_data.encode())
|
||||||
|
|
||||||
@@ -239,10 +236,9 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
"""Handle POST requests (mainly login attempts)"""
|
"""Handle POST requests (mainly login attempts)"""
|
||||||
client_ip = self._get_client_ip()
|
client_ip = self._get_client_ip()
|
||||||
user_agent = self._get_user_agent()
|
user_agent = self.headers.get("User-Agent", "")
|
||||||
post_data = ""
|
post_data = ""
|
||||||
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
base_path = urlparse(self.path).path
|
base_path = urlparse(self.path).path
|
||||||
|
|
||||||
@@ -293,7 +289,6 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
for pair in post_data.split("&"):
|
for pair in post_data.split("&"):
|
||||||
if "=" in pair:
|
if "=" in pair:
|
||||||
key, value = pair.split("=", 1)
|
key, value = pair.split("=", 1)
|
||||||
from urllib.parse import unquote_plus
|
|
||||||
|
|
||||||
parsed_data[unquote_plus(key)] = unquote_plus(value)
|
parsed_data[unquote_plus(key)] = unquote_plus(value)
|
||||||
|
|
||||||
@@ -486,12 +481,25 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
"""Responds to webpage requests"""
|
"""Responds to webpage requests"""
|
||||||
|
|
||||||
client_ip = self._get_client_ip()
|
client_ip = self._get_client_ip()
|
||||||
|
|
||||||
|
# respond with HTTP error code if client is banned
|
||||||
if self.tracker.is_banned_ip(client_ip):
|
if self.tracker.is_banned_ip(client_ip):
|
||||||
self.send_response(500)
|
self.send_response(500)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
return
|
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
|
||||||
|
|
||||||
# Handle static files for dashboard
|
# Handle static files for dashboard
|
||||||
if self.config.dashboard_secret_path and self.path.startswith(
|
if self.config.dashboard_secret_path and self.path.startswith(
|
||||||
@@ -543,8 +551,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
stats = self.tracker.get_stats()
|
stats = self.tracker.get_stats()
|
||||||
dashboard_path = self.config.dashboard_secret_path
|
self.wfile.write(generate_dashboard(stats, self.config.dashboard_secret_path).encode())
|
||||||
self.wfile.write(generate_dashboard(stats, dashboard_path).encode())
|
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -566,10 +573,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
ip_stats_list = db.get_ip_stats(limit=500)
|
ip_stats_list = db.get_ip_stats(limit=500)
|
||||||
self.wfile.write(json.dumps({"ips": ip_stats_list}).encode())
|
self.wfile.write(json.dumps({"ips": ip_stats_list}).encode())
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
@@ -593,15 +597,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
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 = int(query_params.get("page", ["1"])[0])
|
||||||
page_size = int(query_params.get("page_size", ["25"])[0])
|
page_size = int(query_params.get("page_size", ["25"])[0])
|
||||||
sort_by = query_params.get("sort_by", ["total_requests"])[0]
|
sort_by = query_params.get("sort_by", ["total_requests"])[0]
|
||||||
@@ -639,11 +636,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
|
|
||||||
# Parse query parameters
|
# Parse query parameters
|
||||||
parsed_url = urlparse(self.path)
|
parsed_url = urlparse(self.path)
|
||||||
@@ -689,10 +682,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
ip_stats = db.get_ip_stats_by_ip(ip_address)
|
ip_stats = db.get_ip_stats_by_ip(ip_address)
|
||||||
if ip_stats:
|
if ip_stats:
|
||||||
self.wfile.write(json.dumps(ip_stats).encode())
|
self.wfile.write(json.dumps(ip_stats).encode())
|
||||||
@@ -719,11 +709,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
parsed_url = urlparse(self.path)
|
parsed_url = urlparse(self.path)
|
||||||
query_params = parse_qs(parsed_url.query)
|
query_params = parse_qs(parsed_url.query)
|
||||||
page = int(query_params.get("page", ["1"])[0])
|
page = int(query_params.get("page", ["1"])[0])
|
||||||
@@ -762,11 +748,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
parsed_url = urlparse(self.path)
|
parsed_url = urlparse(self.path)
|
||||||
query_params = parse_qs(parsed_url.query)
|
query_params = parse_qs(parsed_url.query)
|
||||||
page = int(query_params.get("page", ["1"])[0])
|
page = int(query_params.get("page", ["1"])[0])
|
||||||
@@ -805,11 +787,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
parsed_url = urlparse(self.path)
|
parsed_url = urlparse(self.path)
|
||||||
query_params = parse_qs(parsed_url.query)
|
query_params = parse_qs(parsed_url.query)
|
||||||
page = int(query_params.get("page", ["1"])[0])
|
page = int(query_params.get("page", ["1"])[0])
|
||||||
@@ -823,7 +801,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
result = db.get_top_ips_paginated(
|
result = db.get_top_ips_paginated(
|
||||||
page=page,
|
page=page,
|
||||||
page_size=page_size,
|
page_size=page_size,
|
||||||
sort_by=sort_by,
|
pathsort_by=sort_by,
|
||||||
sort_order=sort_order,
|
sort_order=sort_order,
|
||||||
)
|
)
|
||||||
self.wfile.write(json.dumps(result).encode())
|
self.wfile.write(json.dumps(result).encode())
|
||||||
@@ -848,11 +826,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
parsed_url = urlparse(self.path)
|
parsed_url = urlparse(self.path)
|
||||||
query_params = parse_qs(parsed_url.query)
|
query_params = parse_qs(parsed_url.query)
|
||||||
page = int(query_params.get("page", ["1"])[0])
|
page = int(query_params.get("page", ["1"])[0])
|
||||||
@@ -891,11 +865,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
parsed_url = urlparse(self.path)
|
parsed_url = urlparse(self.path)
|
||||||
query_params = parse_qs(parsed_url.query)
|
query_params = parse_qs(parsed_url.query)
|
||||||
page = int(query_params.get("page", ["1"])[0])
|
page = int(query_params.get("page", ["1"])[0])
|
||||||
@@ -934,11 +904,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Expires", "0")
|
self.send_header("Expires", "0")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
try:
|
try:
|
||||||
from database import get_database
|
|
||||||
import json
|
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
|
|
||||||
db = get_database()
|
|
||||||
parsed_url = urlparse(self.path)
|
parsed_url = urlparse(self.path)
|
||||||
query_params = parse_qs(parsed_url.query)
|
query_params = parse_qs(parsed_url.query)
|
||||||
page = int(query_params.get("page", ["1"])[0])
|
page = int(query_params.get("page", ["1"])[0])
|
||||||
@@ -963,13 +929,35 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.wfile.write(json.dumps({"error": str(e)}).encode())
|
self.wfile.write(json.dumps({"error": str(e)}).encode())
|
||||||
return
|
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
|
# API endpoint for downloading malicious IPs file
|
||||||
if (
|
if (
|
||||||
self.config.dashboard_secret_path
|
self.config.dashboard_secret_path
|
||||||
and self.path
|
and self.path
|
||||||
== f"{self.config.dashboard_secret_path}/api/download/malicious_ips.txt"
|
== f"{self.config.dashboard_secret_path}/api/download/malicious_ips.txt"
|
||||||
):
|
):
|
||||||
import os
|
|
||||||
|
|
||||||
file_path = os.path.join(
|
file_path = os.path.join(
|
||||||
os.path.dirname(__file__), "exports", "malicious_ips.txt"
|
os.path.dirname(__file__), "exports", "malicious_ips.txt"
|
||||||
|
|||||||
Reference in New Issue
Block a user