#!/usr/bin/env python3 # -*- coding: utf-8 -*- """web_login_profiler.py - Detect login forms and auth controls on web endpoints (no exploitation).""" 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 b_timeout = 300 b_max_retries = 2 b_stealth_level = 5 b_risk_level = "low" b_tags = ["web", "login", "auth", "profiler"] b_category = "recon" b_name = "Web Login Profiler" b_description = "Detects login forms and auth controls on web endpoints." b_author = "Bjorn Team" b_version = "2.0.0" b_icon = "WebLoginProfiler.png" # 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 = "