-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscanner.py
More file actions
executable file
·178 lines (147 loc) · 6.29 KB
/
Copy pathscanner.py
File metadata and controls
executable file
·178 lines (147 loc) · 6.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import requests, concurrent.futures, json, time, hashlib
from pathlib import Path
from config import config
from utils import timestamp
# Variables globales (se reinician en ejecutar_scan)
found_urls = []
sizes_seen = set()
visited_dirs = set()
falsos_positivos = []
falsos_filtrados = 0 # contador dinámico de falsos positivos descartados
def cargar_wordlist():
rutas = []
if isinstance(config["wordlist"], list):
for wl in config["wordlist"]:
ruta = Path(wl)
if not ruta.exists():
ruta = Path("./wordlist") / wl
rutas.append(ruta)
elif isinstance(config["wordlist"], str):
ruta = Path(config["wordlist"])
if not ruta.exists():
ruta = Path("./wordlist") / config["wordlist"]
rutas = [ruta]
else:
return []
words = []
for ruta in rutas:
if ruta.exists():
with open(ruta, "r", errors="ignore") as f:
words.extend([line.strip() for line in f if line.strip()])
return words
# 🚨 Detectar respuestas genéricas (falsos positivos)
def detectar_respuesta_generica(base_url):
resultados = []
for i in range(2):
fake_url = f"{base_url}/__budir_fake_{i}.txt"
try:
r = requests.get(fake_url, timeout=5, headers=config["headers"])
h = hashlib.md5(r.content).hexdigest()
resultados.append((r.status_code, len(r.content), h))
except:
pass
return resultados
def es_falso_positivo(r):
global falsos_filtrados
h = hashlib.md5(r.content).hexdigest()
if (r.status_code, len(r.content), h) in falsos_positivos:
falsos_filtrados += 1
return True
return False
def scan_url(base_url, path, output_txt, fallos_txt, depth, max_depth, tasks):
if config["port"] != 80 and f":{config['port']}" not in base_url:
base_url = f"{base_url}:{config['port']}"
urls = []
if "." in path:
urls.append(f"{base_url}/{path}")
else:
urls.append(f"{base_url}/{path}")
if config["extensions"]:
urls += [f"{base_url}/{path}.{ext}" for ext in config["extensions"]]
for url in urls:
try:
r = requests.get(url, timeout=5, allow_redirects=True, headers=config["headers"])
size = len(r.content)
# Guardar todo en fallos
with open(fallos_txt, "a") as f:
f.write(f"{url} (code: {r.status_code}, size: {size})\n")
# 🚨 Filtrar falsos positivos
if es_falso_positivo(r):
continue
if size in sizes_seen:
continue
if r.status_code in config["status_codes"]:
msg = f"{url} (code: {r.status_code}, size: {size})"
found_urls.append({"url": url, "code": r.status_code, "size": size})
with open(output_txt, "a") as f:
f.write(msg + "\n")
if r.status_code == 200:
print(f"\033[92m[200]\033[0m {msg}")
# 🚀 Crawling si aplica
if config["recurse"] and depth < max_depth:
if url.endswith("/") or r.status_code in [301, 302, 403]:
if url not in visited_dirs:
visited_dirs.add(url)
tasks.append((url, depth + 1))
except requests.RequestException as e:
with open(fallos_txt, "a") as f:
f.write(f"{url} (ERROR: {str(e)})\n")
def ejecutar_scan():
global falsos_positivos, falsos_filtrados, found_urls, sizes_seen, visited_dirs
# 🔄 Reiniciar estados para cada escaneo
found_urls = []
sizes_seen = set()
visited_dirs = set()
falsos_positivos = []
falsos_filtrados = 0
if not config["url"] or not config["wordlist"]:
print("\033[91m[!] Define objetivo y wordlist primero.\033[0m")
return []
url = config["url"].strip()
if not url.startswith("http://") and not url.startswith("https://"):
url = "http://" + url
# 🚨 Detectamos respuestas falsas al inicio
falsos_positivos = detectar_respuesta_generica(url)
if falsos_positivos:
print(f"\033[93m[!] Detectadas {len(falsos_positivos)} respuestas genéricas -> se filtrarán falsos positivos\033[0m")
report_dir = Path("./reportes")
report_dir.mkdir(exist_ok=True)
ts = timestamp()
output_txt = report_dir / f"budir_{ts}.txt"
output_json = report_dir / f"budir_{ts}.json"
fallos_txt = report_dir / f"budir_fallos_{ts}.txt"
paths = cargar_wordlist()
total = len(paths)
print(f"\033[96m[*] Iniciando escaneo sobre {url} con {total} rutas...\033[0m")
start_time = time.time()
tasks = [(url, 1)]
try:
while tasks:
current_url, depth = tasks.pop(0)
print(f"\033[94m[~] Escaneando {current_url} (depth={depth})...\033[0m")
with concurrent.futures.ThreadPoolExecutor(max_workers=config["threads"]) as executor:
futures = []
for path in paths:
futures.append(executor.submit(scan_url, current_url, path, output_txt, fallos_txt, depth, config["depth"], tasks))
concurrent.futures.wait(futures)
except KeyboardInterrupt:
print("\n\033[91m[!] Escaneo interrumpido por el usuario.\033[0m")
# Guardar resultados aunque se haya cancelado
with open(output_json, "w") as jf:
json.dump(found_urls, jf, indent=4)
elapsed = time.time() - start_time
print("\n\033[95m=== RESUMEN ===\033[0m")
print(f"Total de rutas probadas: {total}")
print(f"Hallazgos válidos (sin falsos positivos): {len(found_urls)}")
print(f"Falsos positivos detectados al inicio: {len(falsos_positivos)}")
print(f"Respuestas filtradas como falsos positivos: {falsos_filtrados}")
print(f"Tiempo total: {elapsed:.2f} segundos")
print(f"\033[96m[+] Resultados guardados en:\n - {output_txt}\n - {output_json}\n - {fallos_txt}\033[0m")
only_200 = [f for f in found_urls if f["code"] == 200]
if only_200:
print("\n\033[92m=== HALLAZGOS ENCONTRADOS (200 OK) ===\033[0m")
for item in only_200:
print(f"{item['url']} (code: 200, size: {item['size']})")
else:
print("\033[91m\n[-] No se encontraron rutas con código 200 válidas.\033[0m")
return only_200