diff --git a/src/app.py b/src/app.py index 4e5ee9d..8d6f485 100644 --- a/src/app.py +++ b/src/app.py @@ -58,7 +58,9 @@ async def lifespan(app: FastAPI): ) webpages = None except IOError: - app_logger.warning("Can't read webpages file. Using randomly generated links.") + app_logger.warning( + "Can't read webpages file. Using randomly generated links." + ) app.state.webpages = webpages # Initialize canary counter @@ -82,9 +84,7 @@ DASHBOARD AVAILABLE AT f"Canary token will appear after {config.canary_token_tries} tries" ) else: - app_logger.info( - "No canary token configured (set CANARY_TOKEN_URL to enable)" - ) + app_logger.info("No canary token configured (set CANARY_TOKEN_URL to enable)") yield @@ -146,4 +146,4 @@ def create_app() -> FastAPI: return application -app = create_app() \ No newline at end of file +app = create_app() diff --git a/src/dependencies.py b/src/dependencies.py index d5d8bf1..774d9dd 100644 --- a/src/dependencies.py +++ b/src/dependencies.py @@ -24,9 +24,7 @@ def get_templates() -> Jinja2Templates: """Get shared Jinja2Templates instance with custom filters.""" global _templates if _templates is None: - templates_dir = os.path.join( - os.path.dirname(__file__), "templates", "jinja2" - ) + templates_dir = os.path.join(os.path.dirname(__file__), "templates", "jinja2") _templates = Jinja2Templates(directory=templates_dir) _templates.env.filters["format_ts"] = _format_ts return _templates @@ -92,4 +90,4 @@ def build_raw_request(request: Request, body: str = "") -> str: return raw except Exception as e: - return f"{request.method} {request.url.path} (error building full request: {str(e)})" \ No newline at end of file + return f"{request.method} {request.url.path} (error building full request: {str(e)})" diff --git a/src/middleware/__init__.py b/src/middleware/__init__.py index f7bb692..be27011 100644 --- a/src/middleware/__init__.py +++ b/src/middleware/__init__.py @@ -2,4 +2,4 @@ """ FastAPI middleware package for the Krawl honeypot. -""" \ No newline at end of file +""" diff --git a/src/middleware/ban_check.py b/src/middleware/ban_check.py index fc9ffa0..a3be689 100644 --- a/src/middleware/ban_check.py +++ b/src/middleware/ban_check.py @@ -26,4 +26,4 @@ class BanCheckMiddleware(BaseHTTPMiddleware): return Response(status_code=500) response = await call_next(request) - return response \ No newline at end of file + return response diff --git a/src/middleware/deception.py b/src/middleware/deception.py index aa1af13..6070a14 100644 --- a/src/middleware/deception.py +++ b/src/middleware/deception.py @@ -59,8 +59,20 @@ class DeceptionMiddleware(BaseHTTPMiddleware): elif any( pattern in full_input for pattern in [ - "cmd=", "exec=", "command=", "execute=", "system=", - ";", "|", "&&", "whoami", "id", "uname", "cat", "ls", "pwd", + "cmd=", + "exec=", + "command=", + "execute=", + "system=", + ";", + "|", + "&&", + "whoami", + "id", + "uname", + "cat", + "ls", + "pwd", ] ): attack_type_log = "COMMAND_INJECTION" @@ -87,4 +99,4 @@ class DeceptionMiddleware(BaseHTTPMiddleware): ) response = await call_next(request) - return response \ No newline at end of file + return response diff --git a/src/routes/__init__.py b/src/routes/__init__.py index d21b80e..01413b0 100644 --- a/src/routes/__init__.py +++ b/src/routes/__init__.py @@ -2,4 +2,4 @@ """ FastAPI routes package for the Krawl honeypot. -""" \ No newline at end of file +""" diff --git a/src/routes/api.py b/src/routes/api.py index f98f4c2..b28fcd4 100644 --- a/src/routes/api.py +++ b/src/routes/api.py @@ -258,9 +258,7 @@ async def raw_request(log_id: int, request: Request): return JSONResponse( content={"error": "Raw request not found"}, status_code=404 ) - return JSONResponse( - content={"raw_request": raw}, headers=_no_cache_headers() - ) + return JSONResponse(content={"raw_request": raw}, headers=_no_cache_headers()) except Exception as e: get_app_logger().error(f"Error fetching raw request: {e}") return JSONResponse(content={"error": str(e)}, status_code=500) diff --git a/src/routes/honeypot.py b/src/routes/honeypot.py index 9d93be7..44c38a5 100644 --- a/src/routes/honeypot.py +++ b/src/routes/honeypot.py @@ -46,6 +46,7 @@ router = APIRouter() # --- Helper functions --- + def _should_return_error(config: Config) -> bool: if config.probability_error_codes <= 0: return False @@ -62,6 +63,7 @@ def _get_random_error_code() -> int: # --- HEAD --- + @router.head("/{path:path}") async def handle_head(path: str): return Response(status_code=200, headers={"Content-Type": "text/html"}) @@ -69,6 +71,7 @@ async def handle_head(path: str): # --- POST routes --- + @router.post("/api/search") @router.post("/api/sql") @router.post("/api/database") @@ -90,10 +93,14 @@ async def sql_endpoint_post(request: Request): access_logger.warning( f"[SQL INJECTION DETECTED POST] {client_ip} - {base_path}" ) - return Response(content=error_msg, status_code=status_code, media_type=content_type) + return Response( + content=error_msg, status_code=status_code, media_type=content_type + ) else: response_data = get_sql_response_with_data(base_path, post_data) - return Response(content=response_data, status_code=200, media_type="application/json") + return Response( + content=response_data, status_code=200, media_type="application/json" + ) @router.post("/api/contact") @@ -119,9 +126,7 @@ async def contact_post(request: Request): f"[XSS ATTEMPT DETECTED] {client_ip} - {request.url.path} - Data: {post_data[:200]}" ) else: - access_logger.info( - f"[XSS ENDPOINT POST] {client_ip} - {request.url.path}" - ) + access_logger.info(f"[XSS ENDPOINT POST] {client_ip} - {request.url.path}") tracker.record_access( ip=client_ip, @@ -186,6 +191,7 @@ async def credential_capture_post(request: Request, path: str): # --- GET special paths --- + @router.get("/robots.txt") async def robots_txt(): return PlainTextResponse(html_templates.robots_txt()) @@ -209,18 +215,26 @@ async def fake_users_json(): @router.get("/api_keys.json") async def fake_api_keys(): - return Response(content=api_keys_json(), status_code=200, media_type="application/json") + return Response( + content=api_keys_json(), status_code=200, media_type="application/json" + ) @router.get("/config.json") async def fake_config_json(): - return Response(content=api_response("/api/config"), status_code=200, media_type="application/json") + return Response( + content=api_response("/api/config"), + status_code=200, + media_type="application/json", + ) # Override the generic /users.json to return actual content @router.get("/users.json", include_in_schema=False) async def fake_users_json_content(): - return Response(content=users_json(), status_code=200, media_type="application/json") + return Response( + content=users_json(), status_code=200, media_type="application/json" + ) @router.get("/admin") @@ -281,7 +295,9 @@ async def fake_phpmyadmin(path: str = ""): @router.get("/.env") async def fake_env(): - return Response(content=api_response("/.env"), status_code=200, media_type="application/json") + return Response( + content=api_response("/.env"), status_code=200, media_type="application/json" + ) @router.get("/backup/") @@ -295,6 +311,7 @@ async def fake_directory_listing(request: Request): # --- SQL injection honeypot GET endpoints --- + @router.get("/api/search") @router.get("/api/sql") @router.get("/api/database") @@ -312,26 +329,34 @@ async def sql_endpoint_get(request: Request): access_logger.warning( f"[SQL INJECTION DETECTED] {client_ip} - {base_path} - Query: {request_query[:100] if request_query else 'empty'}" ) - return Response(content=error_msg, status_code=status_code, media_type=content_type) + return Response( + content=error_msg, status_code=status_code, media_type=content_type + ) else: access_logger.info( f"[SQL ENDPOINT] {client_ip} - {base_path} - Query: {request_query[:100] if request_query else 'empty'}" ) response_data = get_sql_response_with_data(base_path, request_query) - return Response(content=response_data, status_code=200, media_type="application/json") + return Response( + content=response_data, status_code=200, media_type="application/json" + ) # --- Generic /api/* fake endpoints --- + @router.get("/api/{path:path}") async def fake_api_catchall(request: Request, path: str): full_path = f"/api/{path}" - return Response(content=api_response(full_path), status_code=200, media_type="application/json") + return Response( + content=api_response(full_path), status_code=200, media_type="application/json" + ) # --- Catch-all GET (trap pages with random links) --- # This MUST be registered last in the router + @router.get("/{path:path}") async def trap_page(request: Request, path: str): """Generate trap page with random links. This is the catch-all route.""" @@ -365,9 +390,7 @@ async def trap_page(request: Request, path: str): # Random error response if _should_return_error(config): error_code = _get_random_error_code() - access_logger.info( - f"Returning error {error_code} to {client_ip} - {full_path}" - ) + access_logger.info(f"Returning error {error_code} to {client_ip} - {full_path}") return Response(status_code=error_code) # Response delay @@ -447,4 +470,4 @@ def _generate_page(config, tracker, client_ip, seed, page_visit_count, app) -> s """ - return html_templates.main_page(app.state.counter, content) \ No newline at end of file + return html_templates.main_page(app.state.counter, content)