Загрузка данных
┌──(venv)─(danon㉿kali)-[~/testhttp]
└─$ cat test_http_methods.py
#!/usr/bin/env python3
"""
HTTP Methods Security Tester (WSTG-CONF-06)
Проверяет поддерживаемые HTTP-методы для заданного веб-приложения.
Автоматически логинится как обычный пользователь и как администратор (если доступны учётные данные).
Использование:
python test_http_methods.py [--url BASE_URL] [--login LOGIN] [--password PASSWORD] [--admin-login ADMIN_LOGIN] [--admin-password ADMIN_PASSWORD]
"""
import requests
import sys
import argparse
from urllib.parse import urljoin
from requests.exceptions import RequestException, Timeout, ConnectionError
import json
from typing import Dict, List, Tuple, Optional
# ---------- Конфигурация ----------
DEFAULT_URL = "http://10.0.20.5:5000"
# Список эндпоинтов для тестирования (можно расширить или получить динамически через /routes)
# Важно: учитываем CSRF-токены не нужны для простой проверки методов.
ENDPOINTS = [
"/",
"/register",
"/home",
"/home_admin",
"/add_card",
"/recharge_balance",
"/delete_card",
"/cart",
"/checkout",
"/like",
"/users",
"/logout",
"/news",
"/admin/news",
"/admin/news/add",
"/admin/news/edit/1", # пример с ID
"/admin/news/delete/1", # DELETE обычно, но у нас POST
"/edit_user/1",
"/delete_game/1", # GET на удаление
"/search_suggestions",
"/search_suggestions1",
"/add_game",
"/editor_game/1", # GET/POST
"/ban_user/1", # POST
"/add_to_cart/1",
"/remove_from_cart/1",
"/add_to_likes/1",
"/remove_from_likes/1",
]
# Методы для тестирования (включая потенциально опасные)
TEST_METHODS = [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"OPTIONS",
"HEAD",
"TRACE",
"CONNECT",
"FOO" # нестандартный метод
]
# Коды ответов, которые считаются признаком того, что метод поддерживается
ALLOWED_STATUS_CODES = {200, 201, 202, 204, 301, 302, 303, 304, 307, 308}
# Опасные методы, на которые стоит обратить особое внимание
DANGEROUS_METHODS = {"PUT", "DELETE", "TRACE", "CONNECT", "PATCH"}
# ---------- Вспомогательные функции ----------
def setup_session(base_url: str, login: Optional[str], password: Optional[str]) -> requests.Session:
"""Создаёт сессию requests и выполняет логин, если указаны учётные данные."""
session = requests.Session()
if login and password:
# Получаем CSRF-токен (если нужен) – в данном приложении Flask-WTF не используется,
# но для регистрации/логина требуется только email/password.
login_url = urljoin(base_url, "/")
try:
# Сначала GET запрос на /login, чтобы получить форму (необязательно, но может понадобиться для cookie)
session.get(login_url, timeout=5)
# Отправляем POST с данными
payload = {"email": login, "password": password, "submit": "Sign In"}
resp = session.post(login_url, data=payload, timeout=5)
if resp.status_code == 200 and "home" in resp.url:
print(f"[+] Успешный вход как {login}")
return session
else:
print(f"[-] Не удалось войти как {login} (статус {resp.status_code})")
except RequestException as e:
print(f"[-] Ошибка при логине: {e}")
return session
def test_methods_on_endpoint(session: requests.Session, base_url: str, endpoint: str, methods: List[str]) -> Dict[str, int]:
"""Отправляет запросы с разными методами на указанный endpoint и возвращает словарь {метод: статус-код}."""
url = urljoin(base_url, endpoint)
results = {}
for method in methods:
try:
# Для HEAD-запросов не следует читать тело ответа
if method == "HEAD":
resp = session.head(url, timeout=5, allow_redirects=False)
else:
resp = session.request(method, url, timeout=5, allow_redirects=False)
results[method] = resp.status_code
except (ConnectionError, Timeout) as e:
results[method] = f"Ошибка: {str(e)}"
except RequestException as e:
results[method] = f"Исключение: {str(e)}"
return results
def analyze_results(endpoint: str, results: Dict[str, int], authenticated: bool):
"""Анализирует результаты и выводит предупреждения."""
print(f"\n--- Эндпоинт: {endpoint} (Авторизация: {'Да' if authenticated else 'Нет'}) ---")
for method, status in results.items():
# Игнорируем ошибки соединения для краткости
if isinstance(status, str) and "Ошибка" in status:
print(f" {method:<8} -> {status}")
continue
is_allowed = status in ALLOWED_STATUS_CODES
symbol = "[+]" if is_allowed else "[-]"
extra = ""
if is_allowed and method in DANGEROUS_METHODS:
extra = " !!! ОПАСНЫЙ МЕТОД РАЗРЕШЁН !!!"
elif not is_allowed and status == 405:
extra = " (ожидаемый запрет)"
print(f" {method:<8} -> {status} {symbol}{extra}")
def generate_report(results_by_endpoint: Dict[str, Dict[str, Dict[str, int]]]):
"""Печатает общий отчёт с опасными методами."""
print("\n" + "="*80)
print("ИТОГОВЫЙ ОТЧЁТ ПО ОПАСНЫМ МЕТОДАМ")
print("="*80)
dangerous_found = False
for endpoint, auth_levels in results_by_endpoint.items():
for auth_name, results in auth_levels.items():
for method, status in results.items():
if method in DANGEROUS_METHODS and status in ALLOWED_STATUS_CODES:
print(f"[!] {endpoint} [{auth_name}]: метод {method} разрешён (код {status})")
dangerous_found = True
if not dangerous_found:
print("[+] Опасные методы (PUT, DELETE, TRACE, CONNECT, PATCH) нигде не разрешены.")
print("="*80)
# ---------- Основная функция ----------
def main():
parser = argparse.ArgumentParser(description="Тестирование HTTP-методов (WSTG-CONF-06)")
parser.add_argument("--url", default=DEFAULT_URL, help="Базовый URL приложения (по умолчанию {})".format(DEFAULT_URL))
parser.add_argument("--login", default=None, help="Email обычного пользователя для теста")
parser.add_argument("--password", default=None, help="Пароль обычного пользователя")
parser.add_argument("--admin-login", default=None, help="Email администратора")
parser.add_argument("--admin-password", default=None, help="Пароль администратора")
args = parser.parse_args()
base_url = args.url.rstrip("/")
print(f"[*] Начинаем тестирование HTTP-методов для {base_url}")
print(f"[*] Эндпоинтов для проверки: {len(ENDPOINTS)}")
# Результаты: для каждого эндпоинта храним результаты анонимно, от юзера, от админа
all_results = {}
# 1. Анонимные запросы (без сессии)
print("\n[1] ТЕСТИРОВАНИЕ БЕЗ АУТЕНТИФИКАЦИИ")
anon_session = requests.Session()
for endpoint in ENDPOINTS:
results = test_methods_on_endpoint(anon_session, base_url, endpoint, TEST_METHODS)
all_results.setdefault(endpoint, {})["anon"] = results
analyze_results(endpoint, results, authenticated=False)
# 2. Запросы от обычного пользователя (если указаны данные)
if args.login and args.password:
print("\n[2] ТЕСТИРОВАНИЕ ОТ ИМЕНИ ОБЫЧНОГО ПОЛЬЗОВАТЕЛЯ")
user_session = setup_session(base_url, args.login, args.password)
for endpoint in ENDPOINTS:
results = test_methods_on_endpoint(user_session, base_url, endpoint, TEST_METHODS)
all_results.setdefault(endpoint, {})["user"] = results
analyze_results(endpoint, results, authenticated=True)
else:
print("\n[2] Пропуск тестов от обычного пользователя (укажите --login и --password)")
# 3. Запросы от администратора
if args.admin_login and args.admin_password:
print("\n[3] ТЕСТИРОВАНИЕ ОТ ИМЕНИ АДМИНИСТРАТОРА")
admin_session = setup_session(base_url, args.admin_login, args.admin_password)
for endpoint in ENDPOINTS:
results = test_methods_on_endpoint(admin_session, base_url, endpoint, TEST_METHODS)
all_results.setdefault(endpoint, {})["admin"] = results
analyze_results(endpoint, results, authenticated=True)
else:
print("\n[3] Пропуск тестов от администратора (укажите --admin-login и --admin-password)")
# Итоговый отчёт
generate_report(all_results)
if __name__ == "__main__":
main()