← все скрипты

ssh-audit.py

python 3.9+ ~ 65 строк обновлён 5 марта 2026

Аудит конфигурации SSH-сервера: проверяет sshd_config на небезопасные настройки, слабые алгоритмы шифрования, устаревшие протоколы. Выдаёт отчёт с рекомендациями по hardening. Полезно после настройки нового сервера.

Зависимости

Использование

# Аудит конфига по умолчанию
sudo python3 ssh-audit.py

# Аудит произвольного файла
python3 ssh-audit.py --config /path/to/sshd_config

# Вывод только проблем (без OK)
sudo python3 ssh-audit.py --only-issues

Код

#!/usr/bin/env python3
"""ssh-audit.py — аудит конфигурации sshd_config."""

import argparse
import sys
from pathlib import Path

# Правила: (параметр, небезопасное_значение, рекомендация)
RULES = [
    ("PermitRootLogin", "yes", "Установите PermitRootLogin no или prohibit-password"),
    ("PasswordAuthentication", "yes", "Отключите пароли: PasswordAuthentication no"),
    ("PermitEmptyPasswords", "yes", "Установите PermitEmptyPasswords no"),
    ("X11Forwarding", "yes", "Отключите X11Forwarding, если не используется"),
    ("UsePAM", "no", "Рекомендуется UsePAM yes для корректной работы аутентификации"),
    ("MaxAuthTries", None, "Установите MaxAuthTries 3-5 для защиты от brute force"),
    ("LoginGraceTime", None, "Установите LoginGraceTime 30-60 секунд"),
    ("ClientAliveInterval", None, "Установите ClientAliveInterval 300 для отключения неактивных сессий"),
    ("AllowUsers", None, "Ограничьте доступ через AllowUsers или AllowGroups"),
]

WEAK_CIPHERS = ["3des-cbc", "aes128-cbc", "aes192-cbc", "aes256-cbc", "blowfish-cbc"]
WEAK_MACS = ["hmac-md5", "hmac-sha1", "umac-64"]

def parse_config(path: str) -> dict[str, str]:
    """Парсит sshd_config, возвращает {параметр: значение}."""
    config = {}
    for line in Path(path).read_text().splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        parts = line.split(None, 1)
        if len(parts) == 2:
            config[parts[0]] = parts[1]
    return config

def main():
    parser = argparse.ArgumentParser(description="Аудит sshd_config")
    parser.add_argument("--config", default="/etc/ssh/sshd_config", help="Путь к конфигу")
    parser.add_argument("--only-issues", action="store_true", help="Показать только проблемы")
    args = parser.parse_args()

    if not Path(args.config).exists():
        print(f"Ошибка: файл {args.config} не найден", file=sys.stderr)
        sys.exit(1)

    config = parse_config(args.config)
    issues = 0

    print(f"\n  ═══ SSH Audit: {args.config} ═══\n")

    for param, bad_value, recommendation in RULES:
        value = config.get(param)
        if bad_value and value and value.lower() == bad_value.lower():
            print(f"  ✗ {param} = {value}")
            print(f"    → {recommendation}\n")
            issues += 1
        elif bad_value is None and value is None:
            if not args.only_issues:
                print(f"  ⚠ {param} не задан")
                print(f"    → {recommendation}\n")
            issues += 1
        elif not args.only_issues:
            print(f"  ✓ {param} = {value or '(по умолчанию)'}")

    # Проверка шифров
    ciphers = config.get("Ciphers", "")
    for weak in WEAK_CIPHERS:
        if weak in ciphers.lower():
            print(f"  ✗ Слабый шифр: {weak}")
            issues += 1

    macs = config.get("MACs", "")
    for weak in WEAK_MACS:
        if weak in macs.lower():
            print(f"  ✗ Слабый MAC: {weak}")
            issues += 1

    print(f"\n  ───────────────────────")
    if issues == 0:
        print(f"  ✓ Проблем не обнаружено")
    else:
        print(f"  Найдено проблем: {issues}")

if __name__ == "__main__":
    main()