#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ web_login_profiler.py — Lightweight web login profiler (Pi Zero friendly). Goal: - Profile web endpoints to detect login surfaces and defensive controls (no password guessing). - Store findings into DB table `webenum` (tool='login_profiler') for community visibility. - Update EPD UI fields: bjorn_orch_status, bjorn_status_text2, comment_params, bjorn_progress. """ import json import logging import re import ssl import time from http.client import HTTPConnection, HTTPSConnection, RemoteDisconnected from typing import Dict, Optional, Tuple from logger import Logger from actions.bruteforce_common import ProgressTracker logger = Logger(name="web_login_profiler.py", level=logging.DEBUG) # -------------------- Action metadata (AST-friendly) -------------------- b_class = "WebLoginProfiler" b_module = "web_login_profiler" b_status = "WebLoginProfiler" b_port = 80 b_parent = None b_service = '["http","https"]' b_trigger = "on_web_service" b_priority = 55 b_action = "normal" b_cooldown = 1800 b_rate_limit = "6/86400" b_enabled = 1 # Small curated list, cheap but high signal. DEFAULT_PATHS = [ "/", "/login", "/signin", "/auth", "/admin", "/administrator", "/wp-login.php", "/user/login", "/robots.txt", ] ANSI_RE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]") def _scheme_for_port(port: int) -> str: https_ports = {443, 8443, 9443, 10443, 9444, 5000, 5001, 7080, 9080} return "https" if int(port) in https_ports else "http" def _first_hostname_from_row(row: Dict) -> str: try: hn = (row.get("Hostname") or row.get("hostname") or row.get("hostnames") or "").strip() if ";" in hn: hn = hn.split(";", 1)[0].strip() return hn except Exception: return "" def _detect_signals(status: int, headers: Dict[str, str], body_snippet: str) -> Dict[str, object]: h = {str(k).lower(): str(v) for k, v in (headers or {}).items()} www = h.get("www-authenticate", "") set_cookie = h.get("set-cookie", "") auth_type = None if status == 401 and "basic" in www.lower(): auth_type = "basic" elif status == 401 and "digest" in www.lower(): auth_type = "digest" # Very cheap login form heuristics snippet = (body_snippet or "").lower() has_form = "