eb8dbf66a1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
3.4 KiB
Python
92 lines
3.4 KiB
Python
"""Comprueba la existencia de un username en sitios publicos (sherlock ligero).
|
|
|
|
OSINT pasivo: para un username dado, consulta la URL de perfil de una lista
|
|
de sitios conocidos y deduce si la cuenta existe por el codigo de estado
|
|
HTTP (200 = existe, 404 = no existe). Cada sitio se consulta de forma
|
|
aislada: un fallo (timeout, error de red) no aborta el resto.
|
|
"""
|
|
|
|
import requests
|
|
|
|
# Cada entrada describe un sitio: como construir la URL de perfil a partir
|
|
# del username. La deteccion es por codigo de estado (200 existe / 404 no).
|
|
DEFAULT_SITES = [
|
|
{"site": "github", "url": "https://github.com/{u}"},
|
|
{"site": "twitter", "url": "https://x.com/{u}"},
|
|
{"site": "instagram", "url": "https://www.instagram.com/{u}/"},
|
|
{"site": "tiktok", "url": "https://www.tiktok.com/@{u}"},
|
|
{"site": "reddit", "url": "https://www.reddit.com/user/{u}"},
|
|
{"site": "gitlab", "url": "https://gitlab.com/{u}"},
|
|
{"site": "keybase", "url": "https://keybase.io/{u}"},
|
|
{"site": "medium", "url": "https://medium.com/@{u}"},
|
|
{"site": "telegram", "url": "https://t.me/{u}"},
|
|
{"site": "youtube", "url": "https://www.youtube.com/@{u}"},
|
|
{"site": "pinterest", "url": "https://www.pinterest.com/{u}/"},
|
|
{"site": "about_me", "url": "https://about.me/{u}"},
|
|
]
|
|
|
|
_BROWSER_UA = (
|
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
|
|
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
|
|
)
|
|
|
|
|
|
def enumerate_username_sites(
|
|
username: str,
|
|
timeout_s: float = 8.0,
|
|
sites: list | None = None,
|
|
) -> list:
|
|
"""Comprueba si un username existe en una lista de sitios publicos.
|
|
|
|
Para cada sitio construye la URL de perfil con el username y hace un
|
|
GET con User-Agent de navegador siguiendo redirecciones. Interpreta el
|
|
codigo de estado final: 200 -> existe, 404 -> no existe, cualquier otro
|
|
-> indeterminado (exists=None). Los errores de red por sitio (timeout,
|
|
conexion) se capturan y se reportan con status=None y exists=None sin
|
|
interrumpir la enumeracion del resto.
|
|
|
|
Args:
|
|
username: nombre de usuario a buscar (sin arroba ni URL).
|
|
timeout_s: timeout en segundos por peticion. Default 8.0.
|
|
sites: lista opcional de dicts {"site", "url"} donde "url" contiene
|
|
el placeholder "{u}". Si es None se usa DEFAULT_SITES.
|
|
|
|
Returns:
|
|
lista de dicts {"site", "url", "exists", "status"} en el mismo orden
|
|
que la lista de sitios. "exists" es True/False/None y "status" es el
|
|
codigo HTTP (int) o None si la peticion fallo.
|
|
"""
|
|
targets = sites if sites is not None else DEFAULT_SITES
|
|
headers = {"User-Agent": _BROWSER_UA}
|
|
results = []
|
|
|
|
for entry in targets:
|
|
site = entry.get("site", "")
|
|
url = entry["url"].format(u=username)
|
|
status = None
|
|
exists = None
|
|
try:
|
|
resp = requests.get(
|
|
url,
|
|
headers=headers,
|
|
timeout=timeout_s,
|
|
allow_redirects=True,
|
|
)
|
|
status = resp.status_code
|
|
if status == 200:
|
|
exists = True
|
|
elif status == 404:
|
|
exists = False
|
|
else:
|
|
exists = None
|
|
except requests.RequestException:
|
|
# Timeout, error de conexion, demasiados redirects, etc.
|
|
status = None
|
|
exists = None
|
|
|
|
results.append(
|
|
{"site": site, "url": url, "exists": exists, "status": status}
|
|
)
|
|
|
|
return results
|