Python для пентестера: пишем Web Directory Brute Forcer с нуля - находим скрытые пути как Gobuster
✍️ Автор : Mikhail Tarasov | Timcore
📅 Дата : Март 2026
⚡ Уровень : ██░░░░░░░░ Beginner / Junior
🐍 Язык : Python 3.10+
🏷️ Теги : pentest, recon, bruteforce, ethical hacking, python
⚠️ Дисклеймер: только с разрешения владельца!
🎯 Уровень сложности скрипта
|
Критерий |
Оценка |
|
Синтаксис Python |
⭐☆☆☆☆ Базовый |
|
Сетевое программирование |
⭐⭐☆☆☆ Начальный |
|
Многопоточность |
⭐⭐⭐☆☆ Средний |
|
Общий уровень |
Beginner+ |
Web Directory Brute Forcer - это инструмент второй фазы разведки. Мы уже знаем что сервер живёт на 80 порту, теперь брутим пути (/admin, /backup, /config) - ищем то, что не должно быть публичным. Именно так находят /phpmyadmin, незащищённые панели и забытые .zip-архивы с исходниками.
💣 Код: dir_bruter.py
#!/usr/bin/env python3
"""
[ Web Directory Brute Forcer ]
Мини-Gobuster на Python - ищем скрытые пути
⚠️ Только на своих ресурсах или с разрешения!
"""
import urllib.request
import urllib.error
import threading
from queue import Queue
from datetime import datetime
# ============ ЦВЕТА ============
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
CYAN = "\033[96m"
BLUE = "\033[94m"
RESET = "\033[0m"
BOLD = "\033[1m"
# ============ ВСТРОЕННЫЙ ВОРДЛИСТ ============
# Самые частые находки на реальных серверах
BUILT_IN_WORDLIST = [
# Админки и панели
"admin", "administrator", "panel", "dashboard", "manage",
"wp-admin", "wp-login.php", "cpanel", "phpmyadmin", "adminer.php",
# Конфиги и бэкапы — джекпот!
"backup", "backups", "backup.zip", "backup.sql", "db.sql",
".env", "config.php", "config.bak", "web.config", "settings.php",
# Общие пути
"login", "register", "api", "api/v1", "api/v2",
"uploads", "upload", "files", "static", "assets",
# Инфа о сервере
"server-status", "server-info", "robots.txt", "sitemap.xml",
".git", ".git/config", ".htaccess", ".htpasswd",
# Всякое вкусное
"test", "dev", "old", "temp", "tmp", "log", "logs",
"shell.php", "cmd.php", "info.php", "phpinfo.php",
]
# Статус-коды и их смысл для пентестера
STATUS_MAP = {
200: (GREEN, "OK ← НАЙДЕНО!"),
201: (GREEN, "CREATED ← Интересно"),
204: (CYAN, "NO CONTENT"),
301: (YELLOW, "REDIRECT ← Куда редиректит?"),
302: (YELLOW, "REDIRECT ← Куда редиректит?"),
401: (BLUE, "AUTH REQ ← Закрыто, но есть!"),
403: (BLUE, "FORBIDDEN ← Закрыто, но ЕСТЬ!"),
500: (RED, "ERROR ← Сервер сломался"),
}
# Глобальный список находок (thread-safe через lock)
found_paths = []
found_lock = threading.Lock()
def check_path(base_url: str, path: str, timeout: int = 5) -> tuple[int, int]:
"""
Проверяем один путь. Возвращаем (status_code, content_length).
Не кидаем исключений — возвращаем 0 при ошибке.
"""
url = f"{base_url.rstrip('/')}/{path}"
try:
req = urllib.request.Request(
url,
headers={
# Маскируемся под браузер — некоторые серверы блокируют Python-агенты
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0",
}
)
with urllib.request.urlopen(req, timeout=timeout) as resp:
content_length = len(resp.read())
return resp.status, content_length
except urllib.error.HTTPError as e:
# HTTPError — это тоже ответ сервера (403, 401 и т.д.)
return e.code, 0
except Exception:
return 0, 0
def worker(base_url: str, queue: Queue, show_all: bool):
"""
Воркер для потока. Берёт пути из очереди, проверяет, выводит.
"""
while not queue.empty():
path = queue.get()
status, size = check_path(base_url, path)
if status == 0:
queue.task_done()
continue
# Показывать ли этот статус?
if status in STATUS_MAP:
color, label = STATUS_MAP[status]
# thread-safe вывод через lock
with found_lock:
print(f" {color}[{status}]{RESET} /{path:<35} {color}{label}{RESET}"
+ (f" ({size} bytes)" if size > 0 else ""))
if status in (200, 201, 401, 403):
found_paths.append((status, path, size))
elif show_all:
with found_lock:
print(f" {RESET}[{status}] /{path}")
queue.task_done()
def load_wordlist(filepath: str) -> list[str]:
"""Загружаем вордлист из файла, по одному слову на строку."""
try:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
words = [line.strip() for line in f if line.strip()
and not line.startswith("#")]
return words
except FileNotFoundError:
print(f"{RED}[!] Файл вордлиста не найден: {filepath}{RESET}")
return []
def run_bruter(base_url: str, wordlist: list[str],
threads: int = 20, show_all: bool = False):
"""Основная функция: заполняем очередь, запускаем потоки."""
queue = Queue()
for word in wordlist:
queue.put(word)
total = queue.qsize()
print(f"\n{BOLD}{CYAN}{'═'*58}{RESET}")
print(f"{BOLD} 🌐 Цель : {base_url}{RESET}")
print(f"{BOLD} 📋 Путей : {total}{RESET}")
print(f"{BOLD} ⚡ Потоков : {threads}{RESET}")
print(f"{BOLD} 🕐 Старт : {datetime.now().strftime('%H:%M:%S')}{RESET}")
print(f"{BOLD}{CYAN}{'═'*58}{RESET}\n")
thread_list = []
for _ in range(min(threads, total)):
t = threading.Thread(
target=worker,
args=(base_url, queue, show_all),
daemon=True
)
t.start()
thread_list.append(t)
# Ждём пока все потоки закончат
for t in thread_list:
t.join()
# ====== ИТОГОВЫЙ ОТЧЁТ ======
print(f"\n{BOLD}{CYAN}{'═'*58}{RESET}")
print(f" ⏱️ Финиш : {datetime.now().strftime('%H:%M:%S')}")
print(f" 🎯 Находки : {GREEN}{len(found_paths)}{RESET}\n")
if found_paths:
print(f" {BOLD}TOP HITS:{RESET}")
for status, path, size in sorted(found_paths, key=lambda x: x[0]):
color, _ = STATUS_MAP.get(status, (RESET, ""))
print(f" {color}[{status}]{RESET} {base_url.rstrip('/')}/{path}")
print(f"{BOLD}{CYAN}{'═'*58}{RESET}\n")
# ============ ТОЧКА ВХОДА ============
if __name__ == "__main__":
print(f"\n{BOLD}{RED}")
print(" ╔══════════════════════════════════════════╗")
print(" ║ 🔍 WEB DIRECTORY BRUTE FORCER v1.0 ║")
print(" ║ Только с разрешения владельца! ║")
print(" ╚══════════════════════════════════════════╝")
print(RESET)
# Ввод цели
url = input(" 🌐 URL цели (например http://192.168.1.1): ").strip()
if not url.startswith("http"):
url = "http://" + url
# Вордлист
wl_path = input(" 📄 Путь к вордлисту (Enter = встроенный): ").strip()
if wl_path:
wordlist = load_wordlist(wl_path)
if not wordlist:
print(f" {YELLOW}[*] Использую встроенный вордлист{RESET}")
wordlist = BUILT_IN_WORDLIST
else:
wordlist = BUILT_IN_WORDLIST
# Кол-во потоков
try:
t_count = int(input(" ⚡ Потоков [20]: ") or 20)
except ValueError:
t_count = 20
run_bruter(url, wordlist, threads=t_count)
🔍 Разбор ключевых концепций
Многопоточность через Queue
Queue - это потокобезопасная очередь. Каждый поток берёт задачу из очереди, обрабатывает, берёт следующую. 20 потоков вместо 1 = в ~15-18 раз быстрее за счёт параллельных сетевых запросов.
User-Agent маскировка
"User-Agent": "Mozilla/5.0 ... Chrome/120.0.0.0"
Без этого сервер видит Python-urllib/3.x и может блокировать. Маскируемся под обычный браузер.
Почему 403 - это находка?
|
Код |
Значение для пентестера |
|
200 |
Путь существует и открыт — читаем |
|
403 |
Путь существует, но закрыт — ищем обход |
|
401 |
Требует авторизацию — брутим кредсы |
|
301/302 |
Редирект — смотрим куда ведёт |
|
500 |
Сервер упал — возможна уязвимость |
💡 Апгрейды следующего уровня
# Реальный вордлист от SecLists — 4.7 млн путей
/usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
# Если нашёл /.git — тащи репозиторий целиком:
git-dumper http://target.com/.git ./dumped_repo
# Внутри — исходники, конфиги, пароли в коммитах 😈
⚠️ Тренируйся на DVWA (Damn Vulnerable Web App) или HackTheBox - там этот скрипт найдёт всё что нужно без правовых последствий 🔐😎
