Lucene search
+L

Search Engine Footprint

🗓️ 10 Jul 2026 11:31:42Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 3 Views

KittySploit Search Engine Footprint module performs passive dorking for OSINT.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Passive search-engine footprinting via HTML search results."""

from __future__ import annotations

import json
import re
from typing import Any, Dict, List, Set
from urllib.parse import parse_qs, unquote, urlparse

from bs4 import BeautifulSoup
from kittysploit import *
from lib.osint.web_surface import normalize_domain
from lib.protocols.http.http_client import Http_client


class Module(Auxiliary, Http_client):
    __info__ = {
        "name": "Search Engine Footprint",
        "author": ["KittySploit Team"],
        "description": (
            "Run passive search-engine dorks (site:, filetype:, inurl:) and collect "
            "indexed URLs, titles, and sensitive path hints."
        ),
        "tags": ["osint", "passive", "search", "dork", "footprint"],
        "agent": {
            "risk": "passive",
            "effects": ["osint_lookup"],
            "expected_requests": 6,
            "reversible": True,
            "approval_required": False,
            "produces": ["endpoints", "risk_signals"],
        },
    }

    target = OptString("", "Target domain (e.g. example.com)", required=True)
    max_results_per_query = OptString("8", "Maximum results to keep per dork query", required=False)
    timeout = OptString("15", "HTTP timeout in seconds", required=False)
    output_file = OptString("", "Optional JSON output file", required=False)

    DORK_TEMPLATES = (
        ("site:{domain}", "indexed_pages"),
        ("site:{domain} filetype:pdf", "pdf_documents"),
        ("site:{domain} inurl:admin", "admin_paths"),
        ("site:{domain} inurl:api", "api_paths"),
        ("site:{domain} (password OR secret OR token OR apikey)", "credential_hints"),
        ("site:{domain} (ext:env OR ext:bak OR ext:sql OR ext:log)", "sensitive_extensions"),
        ("site:{domain} inurl:login OR inurl:signin", "auth_pages"),
    )

    SENSITIVE_RX = re.compile(
        r"(admin|backup|config|\.env|\.git|password|secret|token|api[_-]?key|internal|staging)",
        re.I,
    )

    def _to_int(self, value, default_value: int) -> int:
        try:
            return max(1, int(str(value).strip()))
        except Exception:
            return default_value

    def _extract_ddg_url(self, href: str) -> str:
        if not href:
            return ""
        if href.startswith("//"):
            href = "https:" + href
        if "uddg=" in href:
            parsed = urlparse(href)
            qs = parse_qs(parsed.query)
            uddg = qs.get("uddg", [""])[0]
            return unquote(uddg)
        return href

    def _parse_ddg_results(self, html: str, domain: str, limit: int) -> List[Dict[str, str]]:
        soup = BeautifulSoup(html or "", "html.parser")
        rows: List[Dict[str, str]] = []
        seen: Set[str] = set()

        for result in soup.select("div.result, div.web-result, div.links_main"):
            link = result.select_one("a.result__a, a.result-link, h2 a")
            if not link:
                continue
            href = self._extract_ddg_url(link.get("href") or "")
            if not href or domain not in href.lower():
                continue
            if href in seen:
                continue
            seen.add(href)
            snippet_el = result.select_one("a.result__snippet, div.result__snippet, td.result__snippet")
            snippet = snippet_el.get_text(" ", strip=True) if snippet_el else ""
            rows.append({
                "url": href,
                "title": (link.get_text(" ", strip=True) or href)[:200],
                "snippet": snippet[:300],
            })
            if len(rows) >= limit:
                break
        return rows

    def _search_dork(self, query: str, domain: str, timeout: float, limit: int) -> List[Dict[str, str]]:
        parsed_host = "html.duckduckgo.com"
        old_target = self.target
        old_port = getattr(self, "port", 443)
        old_ssl = getattr(self, "ssl", True)
        try:
            self.target = parsed_host
            self.port = 443
            self.ssl = True
            post_resp = self.http_request(
                method="POST",
                path="/html/",
                data={"q": query, "b": "", "kl": ""},
                allow_redirects=True,
                timeout=timeout,
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            )
        except Exception:
            post_resp = None
        finally:
            self.target = old_target
            self.port = old_port
            self.ssl = old_ssl

        if not post_resp or post_resp.status_code != 200:
            return []
        return self._parse_ddg_results(post_resp.text or "", domain, limit)

    def run(self):
        domain = normalize_domain(self.target)
        if not domain:
            print_error("target must be a valid domain")
            return {"error": "invalid domain target"}

        timeout = float(self._to_int(self.timeout, 15))
        per_query = self._to_int(self.max_results_per_query, 8)

        print_info(f"Search engine footprint for {domain}")
        findings: List[Dict[str, Any]] = []
        all_urls: Set[str] = set()
        sensitive: List[Dict[str, str]] = []

        for template, category in self.DORK_TEMPLATES:
            query = template.format(domain=domain)
            print_status(f"Dork: {query}")
            results = self._search_dork(query, domain, timeout, per_query)
            findings.append({"query": query, "category": category, "results": results, "count": len(results)})
            for row in results:
                url = row.get("url", "")
                if url:
                    all_urls.add(url)
                blob = f"{row.get('title', '')} {row.get('snippet', '')} {url}"
                if self.SENSITIVE_RX.search(blob):
                    sensitive.append({"url": url, "category": category, "title": row.get("title", "")})

        result = {
            "target": domain,
            "findings": findings,
            "unique_urls": sorted(all_urls),
            "sensitive_hits": sensitive[:40],
            "stats": {
                "queries": len(findings),
                "unique_urls": len(all_urls),
                "sensitive_hits": len(sensitive),
            },
        }

        print_success(
            f"Footprint complete: {len(all_urls)} unique URLs, {len(sensitive)} sensitive hits"
        )

        if self.output_file:
            try:
                with open(str(self.output_file), "w") as fp:
                    json.dump(result, fp, indent=2)
                print_success(f"Results saved to {self.output_file}")
            except Exception as exc:
                print_error(f"Failed to save output: {exc}")

        return result

    def get_graph_nodes(self, data):
        if not isinstance(data, dict) or data.get("error"):
            return [], []

        target = data.get("target", self.target)
        nodes: List[Dict[str, Any]] = []
        edges: List[Dict[str, Any]] = []

        for hit in data.get("sensitive_hits", [])[:20]:
            url = hit.get("url") or ""
            if not url:
                continue
            nid = f"se_{hash(url) & 0xFFFFFFFF}"
            label = hit.get("title") or url
            nodes.append({
                "id": nid,
                "label": label[:90],
                "group": "endpoint",
                "icon": "🔎",
                "custom_info": url,
            })
            edges.append({"from": target, "to": nid, "label": hit.get("category", "search")})

        for url in data.get("unique_urls", [])[:12]:
            if any(url == h.get("url") for h in data.get("sensitive_hits", [])):
                continue
            nid = f"se_url_{hash(url) & 0xFFFFFFFF}"
            nodes.append({"id": nid, "label": url[:90], "group": "endpoint", "icon": "🔗"})
            edges.append({"from": target, "to": nid, "label": "indexed"})

        return nodes, edges

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

10 Jul 2026 11:31Current
5.8Medium risk
Vulners AI Score5.8
3