Автоматизация сканирования открытых сетевых портов

Данная статья - лишь ознакомительный материал, созданный в образовательных целях. В ней отсутствуют призывы к каким-либо действиям. Автор предупреждает, что использование этих знаний в противоправных целях преследуется по закону. Статья создана исключительно с благой целью повышения понимания важности информационной безопасности

Данная статья — лишь ознакомительный материал, созданный в образовательных целях. В ней отсутствуют призывы к каким-либо действиям. Автор предупреждает, что использование этих знаний в противоправных целях преследуется по закону. Статья создана исключительно с благой целью повышения понимания важности информационной безопасности

С ростом числа кибератак и угроз безопасности информационных систем автоматизация процесса анализа уязвимостей становится критически важной задачей.

С ростом объемов обрабатываемых данных, расширения информационных систем, сервисов и приложений возникает все больше проблем с точки зрения информационной безопасности. Исходя из этого встает острый вопрос о том, как это все контролировать, и преждевременно предотвращать угрозы информации.

dcead9fc67af8f353c84bef7eb515568.png

В рамках данной статьи мы рассмотрим следующие пункты:

  • разработка скрипта для поиска поддоменов;

  • разработка скрипта для преобразования найденных поддоменов в IP-адреса;

  • разработка скрипта для сканирования внешней сети при помощи утилиты Nmap и сбора информации о доступных портах;

  • разработка телеграмм-бота для автоматизации.

Скрипт для поиска поддоменов

В самом начале стоит уделить вниманию поиску поддоменов. Это необходимо для отслеживанию открытых сервисов, которые доступны из сети.

Какого-то универсального и одного способа найти поддомены нет.

Для решения данной задачи возьмем популярные утилиты для демонстрации. Выбор пал на такие утилиты как: theHarvester, sublist3r, Amass, assfinder. Результаты каждой из утилит суммируем и получаем более полную картину, при желании можно еще больше утилит добавить.
Ниже представлен код для поиска:

#!/bin/bash

# исходный и результирующий файл
input_file="путь к файлу/domains.txt"
output_file="путь к файлу/domains_results.txt"
shared_folder="путь к папке/Domains/"

# Очистка файла domain_results.txt перед записью новых результатов
> "$output_file"

# Создание subdomains_all_unique_filtered.txt если его нет (пригодится в будущем)
if [[ ! -f "$shared_folder/subdomains_all_unique_filtered.txt" ]]; then
    touch "$shared_folder/subdomains_all_unique_filtered.txt"
fi

# Функция добавления результатов домена в выходной файл в указанном формате
append_domain_result() {
    local tool="$1"
    local domain="$2"
    local subdomains="${@:3}"

    echo "$tool:" >> "$output_file"
    echo "    Domain: $domain" >> "$output_file"
    echo "$tool:" >> "$shared_folder/subdomains_all_unique_filtered.txt"
    echo "    Domain: $domain" >> "$shared_folder/subdomains_all_unique_filtered.txt"

    # использование ассоциативного массива для отслеживания поддоменов
    declare -A seen_subdomains
    for subdomain in "${subdomains[@]}"; do
        # проверка на уникальность
        if [[ ! "${seen_subdomains[$domain-$subdomain]}" ]]; then
            seen_subdomains[$domain-$subdomain]=1
            echo "        $subdomain" >> "$output_file"
            echo "        $subdomain" >> "$shared_folder/subdomains_all_unique_filtered.txt"  # Append subdomain to shared file
        fi
    done
}

# Проверка, существует ли subdomains_all.txt
if [[ ! -f "$shared_folder/subdomains_all.txt" ]]; then
    touch "$shared_folder/subdomains_all.txt"
fi

# Проверка, является ли subdomains_all_unique_filtered.txt пустым или нет
if [[ -s "$shared_folder/subdomains_all_unique_filtered.txt" ]]; then
    # Если subdomains_all_unique_filtered.txt не пустой, проверка наличия новых доменов и добавление их в subdomains_all.txt 
    grep -oE [a-zA-Z0-9.-]+.$domain "$shared_folder/subdomains_all_unique_filtered.txt" | grep -Ev 'www.|92m' | sort -u > "$shared_folder/subdomains_all.txt" 
fi

# использование цикла для каждого домена из исходного файла
while IFS= read -r domain; do
    # theHarvester
    echo "theHarvester is scanning domain: $domain"
    echo "theHarvester:" >> "$output_file"
    theHarvester -d "$domain" --dns-server 8.8.8.8 -b urlscan | grep -Eo [a-zA-Z0-9.-]+."$domain" | grep -Ev 'www.|92m' | sort -u | \
    while IFS= read -r subdomain; do
        echo "        $subdomain" >> "$output_file"
    done

    # Sublist3r
    echo "Sublist3r is scanning domain: $domain"
    echo "Sublist3r:" >> "$output_file"
    sublist3r -d "$domain" | grep -Eo [a-zA-Z0-9.-]+."$domain" | sed s/^.*www.// | sed s/^.*92m// | sort -u | \
    while IFS= read -r subdomain; do
        echo "        $subdomain" >> "$output_file"
    done

    #amass
    echo "Amass is scanning domain: $domain"
    echo "Amass:" >> "$output_file"
    timeout 380s amass enum -d "$domain" | grep -Eo [a-zA-Z0-9.-]+."$domain" | grep -Ev 'www.|92m' | sort -u | \
    while IFS= read -r subdomain; do
        echo "        $subdomain" >> "$output_file"
    done

    # assetfinder
    echo "Assetfinder is scanning domain: $domain"
    echo "Assetfinder:" >> "$output_file"
    assetfinder "$domain" | grep -Eo [a-zA-Z0-9.-]+."$domain" | grep -Ev 'www.|92m' | sort -u | \
    while IFS= read -r subdomain; do
        echo "        $subdomain" >> "$output_file"
    done

    # Добавление поддоменов к общему файлу, если они новые
    grep -oE [a-zA-Z0-9.-]+.$domain "$output_file" | grep -Ev 'www.|92m' | sort -u | \
    while IFS= read -r new_domain; do
        if ! grep -q "$new_domain" "$shared_folder/subdomains_all_unique_filtered.txt"; then
            echo "$new_domain" >> "$shared_folder/subdomains_all_unique_filtered.txt"
            echo "$new_domain" >> "$shared_folder/subdomains_all.txt"
        fi
    done
done < "$input_file"

echo "Scan completed."

Для поиска мы создаем файл где указываем исходный файл с доменами 2 го уровня или ниже. Далее скрипт использует цикл для проверки каждого элемента массива через ранее упомянутые утилиты.
Так же необходимо чтобы выводились только найденные поддомены без лишней информации, поэтому используем функцию grep, так как на выводе могут появиться или дублирующие домены или с лишними символами.

результат сканирования домена nmap.org (его можно сканировать)

результат сканирования домена nmap.org (его можно сканировать)

Скрипт для преобразования найденных поддоменов в IP-адреса

Для дальнейшего сканирования лучше использовать IP-адреса, нежели домены, ибо у разных доменов могут быть одинаковые адреса, что только увеличит нам работу. dig позволит нам преобразовать домены в IP-адреса, на подобии утилиты nslookup на Windows.

!/bin/bash 
# Пути к файлам
domains_file="путь к исходному файлу domains.txt"
results_file="путь к файлу с найденными поддоменами domains_results.txt"
ip_file="путь к файлу с результатом данного скрипта ip-addresses.txt"
dig_file="файл для лога, чтобы отслеживать косяки dig.txt"

# Очищаем файлы перед началом работы
> "$ip_file"
> "$dig_file"

# Проверяем наличие файла с доменами
if [ ! -f "$domains_file" ]; then
    echo "Файл domains.txt не найден."
    exit 1
fi

# Проверяем наличие файла с результатами
if [ ! -f "$results_file" ]; then
    echo "Файл domains_results.txt не найден."
    exit 1
fi

# Создаем временный файл для уникальных доменов
temp_domains=$(mktemp)
touch "$temp_domains"

# Добавляем IP-адреса из файла ip-addresses.txt в папке /home/wardeathmor/Desktop/Work/Domains/
if [ -f "/home/wardeathmor/Desktop/Work/Domains/ip-addresses.txt" ]; then
    cat "/home/wardeathmor/Desktop/Work/Domains/ip-addresses.txt" >> "$ip_file"
fi
 
# Читаем файл domains_results.txt и добавляем во временный файл уникальные домены 
current_domain=""
while IFS= read -r line; do
    # Проверяем, является ли строка доменом 
    if [[ "$line" =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then 
        current_domain="$line" 
    elif [[ "$line" =~ ^[[:space:]]+ ]]; then 
        # Проверяем, что поддомен содержит основной домен 
        subdomain=$(echo "$line" | awk '{print $1}') 
        if [[ "$subdomain" == *"$current_domain"* && "$subdomain" != "$current_domain" ]]; then
            echo "$subdomain" >> "$temp_domains"
        fi
    fi
done < "$results_file"

# Перебираем уникальные домены и выполняем dig для поиска IP-адресов
while IFS= read -r domain; do
    # Используем dig для поиска IP-адресов и сохраняем только уникальные адреса
    unique_ips=$(dig +short "$domain" | grep -Eo "([0-9]{1,3}\.){3}[0-9]{1,3}" | grep -Ev "^192" | sort -u)
    for ip in $unique_ips; do
        # Проверяем, что IP-адрес еще не добавлен в файл
        if ! grep -q "^$ip$" "$ip_file"; then
            echo "$ip" >> "$ip_file"
        fi
    done
    # Записываем результаты dig в файл dig.txt
    echo "Domain: $domain" >> "$dig_file"
    echo "IP addresses:" >> "$dig_file"
    echo "$unique_ips" | tr '\n' ',' | sed 's/,$//' >> "$dig_file" 
    echo "" >> "$dig_file"  # Пустая строка для разделения записей
done < "$temp_domains"

# Удаляем временный файл с уникальными доменами
rm "$temp_domains"

echo "Готово. Уникальные IP-адреса сохранены в $ip_file"

По сути через данный скрипт мы отфильтровываем на уникальность домены, чтобы остались только поддомены главного домена, а так же добавил сюда фильтр на 192е айпишники, ибо иногда может вернуть их.

Великий и ужасный Nmap и скрипт для его автоматизации

Данная утилита всем давно уже известна, но лично для меня существует проблема с масштабом загрузки желаемых для проверки адресов, так как приходится или это дело вручную вбивать, или используя версию Zenmap через запятые постоянно вводить адреса и видеть еще много информации, которая не всегда нам нужна. Так как у нас цель именно открытые порты, то и будем искать их

#!/bin/bash

input_file="путь к файлу/ip-addresses.txt"

# путь до папки с документом
output_folder="путь до папки/Nmap/"
output_document="$output_folder/results.txt"

# проверка наличия папки
mkdir -p "$output_folder"

# Очистка файла результатов перед началом сканирования
> "$output_document"

# Цикл по каждому IP-адресу из входного файла
while IFS= read -r ip_address; do
  # Вывод сообщения о сканировании текущего IP-адреса
  echo "Сканирование IP-адреса: $ip_address"

  # Выполнение начального сканирования nmap всех 65535 TCP-портов с быстрой настройкой -T4
  # Можно пробовать и забивать разные параметры для сканирвования
  nmap_output1=$(nmap -p 1-65535 -T4 -Pn --max-retries 1 --max-scan-delay 10 --initial-rtt-timeout 300ms --min-hostgroup 64 --min-parallelism 16 --min-rtt-timeout 10ms --min-rate 1000 "$ip_address")

  # Проверка наличия открытых портов в начальном сканировании
  if echo "$nmap_output1" | grep -q "open"; then
    # Извлечение номеров открытых портов и их статусов
    open_ports=$(echo "$nmap_output1" | grep -oP "^\d+/tcp\s+\w+\s+\w+")

    # Вывод результатов в файл
    echo "IP адрес: $ip_address" >> "$output_document"
    echo "$open_ports" >> "$output_document"
  else
    # Если не найдены открытые порты в начальном сканировании, выполняется глубокая проверка
    echo "IP адрес: $ip_address" >> "$output_document"
    echo "IP адрес повторно сканируется с глубокой проверкой" >> "$output_document"

    # Выполнение более глубокого сканирования nmap, медленного, но более подробного
    # Можно пробовать и забивать разные параметры для сканирвования
    nmap_output2=$(nmap -p 1-65535 -T5 -f -A --max-retries 2 --max-scan-delay 5 --initial-rtt-timeout 500ms --min-hostgroup 64 --min-parallelism 16 --min-rtt-timeout 5ms --min-rate 500 "$ip_address")

    # Извлечение номеров открытых портов и их статусов
    oopen_ports=$(echo "$nmap_output2" | grep -oP "^\d+/tcp\s+\w+\s+\w+")

    # Проверка наличия открытых портов в глубоком сканировании
    if [ -n "$open_ports" ]; then
      # Вывод результатов в файл
      echo "Глубокое сканирование IP адреса: $ip_address" >> "$output_document"
      echo "$open_ports" >> "$output_document"
    else
      # Если не найдены открытые порты в глубоком сканировании, сообщить об этом в результатах
      echo "Этот IP адрес не имеет открытых портов в глубоком сканировании" >> "$output_document"
    fi
  fi
done < "$input_file"

echo "Сканирование завершено. Результаты сохранены в файле $output_document"

Тут все просто: берем IP-адреса, скрипт прогоняет первый раз для быстрой проверки, если ничего не найдено, то использует повторную и более медленную проверку.

результат сканирования для поддоменов nmap.org

результат сканирования для поддоменов nmap.org

cd03c24ac8dc2d5225df7c6b570c35bd.png

Создание телеграмм-бота

Постоянно заходить на виртуальную машину или постоянно сидеть за компьютером, вручную запускать скрипты и просматривать результаты не является удобным способом для выполнения проверки, поэтому создадим своего телеграмм-бота для удобства

Для начала ищем данного уважаемого молодого человека: https://t.me/BotFather

Далее все по шаблону:

После получаем следующее сообщение:

Done! Congratulations on your new bot. You will find it at t.me/*имя вашего бота*. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you’ve finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.

Use this token to access the HTTP API:
*здесь ваш токен*
Keep your token secure and store it safely, it can be used by anyone to control your bot.

For a description of the Bot API, see this page: https://core.telegram.org/bots/api

В диалоге с созданным ботом необходимо написать произвольное сообщение, затем открыть в браузере ссылку: https://api.telegram.org/bot/getUpdates

где  — идентификатор, полученный от @BotFather.

В полученном json-ответе найдите значение в параметре result→message→chat→id, это и есть .

URL для отправки сообщения боту формируется по образцу:

https://api.telegram.org/bot/sendMessage? chat_id=&text=<текст_отправляемого_сообщения>

Займемся ботом, используем язык программирования Pyhon для написания необходимого нам функционала.
Помимо прямого запуска скриптов, в боте реализован просмотр файлов с результатами, а так же возможность из бота изменить исходный файл с доменами

Скрытый текст

есть баг, если редактировать с нуля файл с исходным доменом, то все перестанет работать. Приходится вручную открывать данный файл и нажимать enter после домена, чтобы все работало.

import subprocess
import telebot
from telebot import types
from telebot import apihelper
import os

# Укажите ваш токен для бота
TOKEN = '*Здесь токен*'

# Список chat_id разрешенных пользователей
ALLOWED_USERS = ['Здесь можно указать ID пользователей у кого есть доступ к боту']


# Пути к файлам и папкам
SUBDOMAINS_FILE = 'путь к файлу с поддоменами'
MAIN_DOMAIN_FILE = 'путь к файлу с исходными доменами'


# значение timeout (в секундах), для того чтоб бот не отключался
timeout_seconds = 9600
apihelper.SESSION_TIMEOUT = timeout_seconds

# Инициализация объекта TeleBot
bot = telebot.TeleBot(TOKEN, parse_mode=None, threaded=False, skip_pending=False)

# Обработчик команды /start
@bot.message_handler(commands=['start'])
def start(message):
    send_main_menu(message)

# Функция для отправки главного меню
def send_main_menu(message):
    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    markup.add(types.KeyboardButton("Помощь"))
    markup.add(types.KeyboardButton("Работа с поддоменами"))
    markup.add(types.KeyboardButton("Nmap"))
    bot.send_message(message.chat.id, "Главное меню", reply_markup=markup)

# Обработчик кнопки "Помощь"
@bot.message_handler(func=lambda message: message.text.lower() == 'помощь')
def help_message(message):
    help_text = """
    Привет! Я бот для работы с файлами и скриптами. Вот что я могу:

    1. Просмотр поддоменов - показывает содержимое файла с поддоменами.
    2. Запуск скрипта поиска поддоменов - выполняет скрипт для поиска поддоменов.
    3. Просмотр основного домена - показывает информацию из файла с основными доменами.
    4. Редактирование файла - позволяет изменить содержимое файла с основными доменами.
    5. Добавить домен - добавляет новый домен в файл с основными доменами.
    6. Nmap - работа с Nmap сканером. Позволяет запустить сканирование открытыx портов.

    Чтобы использовать эти функции, просто нажмите соответствующие кнопки в меню.
    """
    bot.send_message(message.chat.id, help_text)

# Обработчик кнопки "Работа с поддоменами"
@bot.message_handler(func=lambda message: message.text == "Работа с поддоменами")
def subdomains_menu(message):
    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    markup.add(types.KeyboardButton("Просмотр поддоменов"))
    markup.add(types.KeyboardButton("Запуск скрипта поиска поддоменов"))
    markup.add(types.KeyboardButton("Просмотр основного домена"))
    markup.add(types.KeyboardButton("Назад"))
    bot.send_message(message.chat.id, "Меню работы с поддоменами:", reply_markup=markup)

# Обработчик кнопки "Просмотр поддоменов"
@bot.message_handler(func=lambda message: message.text == "Просмотр поддоменов")
def view_subdomains(message):
    try:
        with open(SUBDOMAINS_FILE, 'r') as file:
            content = file.read()
            bot.send_message(message.chat.id, content)
    except FileNotFoundError:
        bot.send_message(message.chat.id, "Файл не найден.")

# Обработчик кнопки "Запуск скрипта поиска поддоменов"
@bot.message_handler(func=lambda message: message.text == "Запуск скрипта поиска поддоменов")
def run_subdomain_script(message):
    try:
        result = subprocess.run(['sudo', '-n', 'bash', '*путь к скрипту для поиска поддоменов*'], capture_output=True, text=True)
        if result.returncode == 0:
            bot.send_message(message.chat.id, "Запуск скрипта")
        else:
            bot.send_message(message.chat.id, "Ошибка при выполнении скрипта.")
    except FileNotFoundError:
        bot.send_message(message.chat.id, "Скрипт не найден.")
        
# Обработчик кнопки "Просмотр основного домена"
@bot.message_handler(func=lambda message: message.text == "Просмотр основного домена")
def main_domain_menu(message):
    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    markup.add(types.KeyboardButton("Посмотреть файл"))
    markup.add(types.KeyboardButton("Редактирование файла"))
    markup.add(types.KeyboardButton("Назад"))
    bot.send_message(message.chat.id, "Меню работы с основным доменом:", reply_markup=markup)

# Обработчик кнопки "Посмотреть файл"
@bot.message_handler(func=lambda message: message.text == "Посмотреть файл")
def view_main_domain_file(message):
    try:
        with open(MAIN_DOMAIN_FILE, 'r') as file:
            content = file.read()
            if not content.strip():
                bot.send_message(message.chat.id, "Файл пуст.")
            else:
                bot.send_message(message.chat.id, content)
    except FileNotFoundError:
        bot.send_message(message.chat.id, "Файл не найден.")

# Обработчик кнопки "Редактирование файла"
@bot.message_handler(func=lambda message: message.text == "Редактирование файла")
def edit_main_domain_file_menu(message):
    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    markup.add(types.KeyboardButton("Изменить полностью файл"))
    markup.add(types.KeyboardButton("Добавить домен"))
    markup.add(types.KeyboardButton("Назад"))
    bot.send_message(message.chat.id, "Меню редактирования файла:", reply_markup=markup)

# Обработчик кнопки "Изменить полностью файл"
@bot.message_handler(func=lambda message: message.text == "Изменить полностью файл")
def edit_full_main_domain_file(message):
    bot.send_message(message.chat.id, "Введите домены построчно для изменения файла:")
    bot.register_next_step_handler(message, process_full_edit)

def process_full_edit(message):
    if message.text.strip():
        if message.text.strip().lower() == "назад":
            bot.send_message(message.chat.id, "Изменение файла отменено.")
            send_main_menu(message)
            return
        try:
            with open(MAIN_DOMAIN_FILE, 'w') as file:
                file.write(message.text)
            bot.send_message(message.chat.id, "Файл успешно обновлен.")
        except Exception as e:
            bot.send_message(message.chat.id, f"Произошла ошибка при обновлении файла: {e}")
    else:
        bot.send_message(message.chat.id, "Файл не был изменен, так как данные пуст.")

# Обработчик кнопки "Добавить домен"
@bot.message_handler(func=lambda message: message.text == "Добавить домен")
def add_domain(message):
    bot.send_message(message.chat.id, "Введите домен для добавления:")
    bot.register_next_step_handler(message, process_add_domain)

def process_add_domain(message):
    if message.text.strip() and not message.text.strip().lower() == "назад":
        try:
            with open(MAIN_DOMAIN_FILE, 'a') as file:
                file.write(message.text + '\n')
            bot.send_message(message.chat.id, "Домен успешно добавлен.")
        except Exception as e:
            bot.send_message(message.chat.id, f"Произошла ошибка при добавлении домена: {e}")
    else:
        bot.send_message(message.chat.id, "Добавление домена отменено.")

# Обработчик кнопки "Nmap"
@bot.message_handler(func=lambda message: message.text == "Nmap")
def nmap_menu(message):
    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    markup.add(types.KeyboardButton("Запуск скрипта Nmap"))
    markup.add(types.KeyboardButton("Просмотр открытых портов"))
    markup.add(types.KeyboardButton("Назад"))
    bot.send_message(message.chat.id, "Меню Nmap:", reply_markup=markup)


@bot.message_handler(func=lambda message: message.text == "Запуск скрипта Nmap")
def run_nmap_script(message):
    # запускаем скрипт dti.sh
    dti_location = "путь к скрипту для преобразования доменов в IP адреса"
    bot.send_message(message.chat.id, "Запуск скрипта dti.sh...")
    try:
        subprocess.run(['sudo', '-n', 'bash', dti_location], capture_output=True, text=True)
        bot.send_message(message.chat.id, "Скрипт dti.sh завершен.")
    except FileNotFoundError:
        bot.send_message(message.chat.id, "Скрипт dti.sh не найден.")

    # запускаем скрипт Nmap
    nmap_location = "путь к скрипту Nmap"
    bot.send_message(message.chat.id, "Запуск скрипта Nmap-script.sh...")
    try:
        result = subprocess.run(['sudo', '-n', 'bash', nmap_location], capture_output=True, text=True)
        if result.returncode == 0:
            bot.send_message(message.chat.id, "Скрипт Nmap-script.sh завершен.")
        else:
            bot.send_message(message.chat.id, "Ошибка при выполнении скрипта Nmap-script.sh.")
    except FileNotFoundError:
        bot.send_message(message.chat.id, "Скрипт Nmap-script.sh не найден.")

@bot.message_handler(func=lambda message: message.text == "Просмотр открытых портов")
def view_open_ports(message):
    nmap_results_location = "путь к файлу results.txt"
    try:
        # Отправляем файл с результатами Nmap
        with open(nmap_results_location, 'rb') as file:
            bot.send_document(message.chat.id, file)
    except FileNotFoundError:
        bot.send_message(message.chat.id, "Файл с результатами Nmap не найден.")


# Обработчик кнопки "Назад"
@bot.message_handler(func=lambda message: message.text == "Назад")
def go_back(message):
    send_main_menu(message)

# Словарь для отслеживания текущего каталога каждого пользователя
user_current_dir = {}

# Обработчик кнопки "Просмотр файлов"
@bot.message_handler(func=lambda message: message.text == "Просмотр файлов")
def view_files_menu(message):
    user_id = message.chat.id
    user_current_dir[user_id] = RESULTS_DIR  
    show_files_and_dirs(message, user_current_dir[user_id])

def show_files_and_dirs(message, current_dir):
    try:
        items = os.listdir(current_dir)
        if not items:
            bot.send_message(message.chat.id, "Файлы отсутствуют.")
        else:
            markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
            for item in items:
                markup.add(types.KeyboardButton(item))
            if current_dir != RESULTS_DIR:
                markup.add(types.KeyboardButton("Назад"))
            bot.send_message(message.chat.id, "Выберите файл или папку для просмотра:", reply_markup=markup)
    except FileNotFoundError:
        bot.send_message(message.chat.id, "Директория не найдена.")
    except Exception as e:
        bot.send_message(message.chat.id, f"Произошла ошибка: {e}")

# Обработчик выбора файла или каталога
@bot.message_handler(func=lambda message: message.chat.id in user_current_dir)
def handle_file_or_dir_selection(message):
    user_id = message.chat.id
    current_dir = user_current_dir.get(user_id, RESULTS_DIR)
    selected_item = message.text

    try:
        if selected_item == "Назад":
            parent_dir = os.path.dirname(current_dir)
            user_current_dir[user_id] = parent_dir
            show_files_and_dirs(message, parent_dir)
        else:
            selected_path = os.path.join(current_dir, selected_item)
            if os.path.isdir(selected_path):
                user_current_dir[user_id] = selected_path
                show_files_and_dirs(message, selected_path)
            elif os.path.isfile(selected_path):
                with open(selected_path, 'rb') as file:
                    bot.send_document(message.chat.id, file)
            else:
                bot.send_message(message.chat.id, "Выбранный элемент не найден.")
        # кнопка «Назад», чтобы можно было вернуться на любой этап
        markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
        markup.add(types.KeyboardButton("Назад"))
        bot.send_message(message.chat.id, "Чтобы вернуться назад, нажмите кнопку:", reply_markup=markup)
                
    except FileNotFoundError:
        bot.send_message(message.chat.id, "Файл не найден.")
    except Exception as e:
        bot.send_message(message.chat.id, f"Произошла ошибка: {e}")
        


if __name__ == '__main__':
    bot.polling(none_stop=True)

Заключение

Благодаря данной автоматизации сканирования можно систематизировать проверку сетевой инфраструктуры компании, используя хакерские утилиты. Когда данная процедура выполняется регулярно, можно оперативно отслеживать бреши в безопасности, например после конфигурации оборудования, или при развертывании новой инфраструктуры.

P.S. первый раз публикую статью, просьба сильно камнями не кидаться

© Habrahabr.ru