Создание веб-сканера TCP-портов

HTMLBeginner
Практиковаться сейчас

Введение

В предыдущем проекте мы разработали сканер портов на Python, который использовал многопоточность и сокеты для проверки TCP-портов. Хотя это решение вполне работоспособно, использование сторонних специализированных пакетов открывает возможности для значительного улучшения инструмента.

В рамках этого проекта мы модернизируем наш сканер, интегрировав библиотеку python-nmap, которая предоставляет более мощные возможности сканирования. Кроме того, мы разработаем веб-приложение на базе Flask, чтобы создать интуитивно понятный интерфейс для нашего инструмента. Этот пошаговый проект поможет вам закрепить имеющиеся знания и освоить новые технологии.

👀 Предварительный просмотр

🎯 Задачи

В этом проекте вы научитесь:

  • Настраивать проект Flask и организовывать его структуру.
  • Использовать Flask-WTF для безопасного создания и обработки веб-форм.
  • Реализовывать маршруты Flask для обработки запросов и отправки данных со страниц.
  • Использовать библиотеку Nmap в Python для проведения сканирования портов.
  • Динамически отображать результаты сканирования на веб-странице с помощью Flask и шаблонов HTML.
  • Применять базовые стили Tailwind CSS для улучшения дизайна интерфейса.

🏆 Достижения

После завершения этого проекта вы сможете:

  • Продемонстрировать базовое понимание веб-разработки на Flask, включая маршрутизацию, рендеринг шаблонов и обработку форм.
  • Применять практический опыт интеграции Python-скриптов с веб-интерфейсами.
  • Уверенно использовать библиотеку Nmap для задач сетевого сканирования.
  • Использовать Flask-WTF для создания и валидации форм в веб-приложении.
  • Работать с Tailwind CSS для стилизации веб-страниц и улучшения пользовательского интерфейса.
  • Создавать функциональные веб-приложения, взаимодействующие с серверными скриптами Python для выполнения сетевых операций.

Реализация главной страницы

Для начала откройте файл templates/index.html и добавьте следующий код, чтобы правильно настроить форму для отправки запросов на сканирование:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TCP Port Scanner</title>
    <link
      href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.3/dist/tailwind.min.css"
      rel="stylesheet"
    />
    <script>
      function updateButton() {
        var submitButton = document.getElementById("scanButton");
        submitButton.value = "Scanning...";
        submitButton.classList.add("cursor-not-allowed", "opacity-50");
        submitButton.disabled = true;
      }
    </script>
  </head>
  <body class="bg-gray-100 flex items-center justify-center h-screen">
    <div class="bg-white p-8 rounded-lg shadow-md">
      <h1 class="text-2xl font-bold mb-4">TCP Port Scanner</h1>
      <form action="" method="post" class="space-y-4" onsubmit="updateButton()">
        {{ form.hidden_tag() }}
        <div>
          {{ form.host.label(class="block text-sm font-medium text-gray-700") }}
          {{ form.host(class="mt-1 block w-full rounded-md border-gray-300
          shadow-sm focus:border-indigo-500 focus:ring-indigo-500") }}
        </div>
        <div>
          {{ form.ports.label(class="block text-sm font-medium text-gray-700")
          }} {{ form.ports(class="mt-1 block w-full rounded-md border-gray-300
          shadow-sm focus:border-indigo-500 focus:ring-indigo-500") }}
        </div>
        <div>
          {{ form.submit(id="scanButton", class="w-full flex justify-center py-2
          px-4 border border-transparent rounded-md shadow-sm text-sm
          font-medium text-white bg-indigo-600 hover:bg-indigo-700
          focus:outline-none focus:ring-2 focus:ring-offset-2
          focus:ring-indigo-500") }}
        </div>
      </form>
    </div>
  </body>
</html>

Шаблон index.html предоставляет пользователю удобный интерфейс для ввода адреса хоста и диапазона портов. HTML-форма содержит два основных поля: одно для адреса цели и другое для указания диапазона портов.

Для отрисовки этих полей используется Flask-WTF — расширение Flask для работы с библиотекой WTForms. Процесс отправки формы дополнен небольшой JavaScript-функцией updateButton(), которая меняет текст кнопки на "Scanning..." в момент нажатия. Эта визуальная обратная связь сообщает пользователю, что запрос на сканирование принят и обрабатывается.

Стилизация страницы выполнена с помощью Tailwind CSS — современного CSS-фреймворка, ориентированного на утилитарные классы, что позволяет быстро создавать качественные интерфейсы.

Реализация страницы результатов сканирования

Откройте файл templates/results.html и добавьте в него следующий код для отображения полученных данных:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Scan Results</title>
    <link
      href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.3/dist/tailwind.min.css"
      rel="stylesheet"
    />
  </head>
  <body class="bg-gray-100">
    <div class="flex flex-col items-center justify-center min-h-screen">
      <div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-4xl">
        <h1 class="text-2xl font-bold mb-4 text-center">
          Scan Results for {{ host }}
        </h1>
        <div class="overflow-x-auto">
          <table class="table-auto w-full text-left whitespace-no-wrap">
            <thead>
              <tr
                class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50"
              >
                <th class="px-4 py-3">PORT</th>
                <th class="px-4 py-3">STATE</th>
                <th class="px-4 py-3">SERVICE</th>
                <th class="px-4 py-3">VERSION</th>
              </tr>
            </thead>
            <tbody
              class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800"
            >
              {% for result in scan_results %}
              <tr class="text-gray-700 dark:text-gray-400">
                <td class="px-4 py-3 text-sm">{{ result.port }}/tcp</td>
                <td class="px-4 py-3 text-sm">{{ result.state }}</td>
                <td class="px-4 py-3 text-sm">{{ result.name }}</td>
                <td class="px-4 py-3 text-sm">
                  {{ result.product }} {{ result.version }} {{ result.extra }}
                </td>
              </tr>
              {% endfor %}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </body>
</html>

Шаблон results.html отвечает за визуализацию итогов сканирования. Результаты представлены в табличном виде: для каждого порта указывается его состояние, название службы и версия ПО (если её удалось определить).

Для оформления страницы используется Tailwind CSS, что гарантирует адаптивность и современный внешний вид. Благодаря движку шаблонов Jinja2 (встроенному во Flask), данные динамически передаются из приложения в шаблон, где цикл проходит по списку результатов и заполняет строки таблицы.

Инициализация приложения Flask

На этом этапе мы создадим основной Python-скрипт для нашего веб-приложения.

Добавьте следующий код в файл app.py:

## Import necessary modules
from flask import Flask, render_template, request, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Regexp
import nmap

## Initialize Flask app
app = Flask(__name__)
app.config['SECRET_KEY'] = 'labex'

## Initialize Nmap PortScanner
nm = nmap.PortScanner()

## Define Flask-WTF form for scanning inputs
class ScanForm(FlaskForm):
    host = StringField('Host', validators=[DataRequired()])
    ports = StringField('Port Range', validators=[DataRequired(), Regexp(r'^\d+-\d+$', message="Format must be start-end")])
    submit = SubmitField('Scan')

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

Скрипт app.py начинается с импорта необходимых модулей: самого Flask, расширения Flask-WTF для работы с формами и библиотеки Nmap для сканирования. Экземпляр приложения Flask настраивается с использованием секретного ключа для защиты от CSRF-атак.

Класс ScanForm описывает поля ввода для хоста и диапазона портов. Мы используем валидаторы, чтобы убедиться, что данные введены и соответствуют нужному формату (в частности, для диапазона портов используется регулярное выражение).

Замените 'your_secret_key' на реальный секретный ключ, который используется для обеспечения безопасности форм.

Обработка главного маршрута

Теперь мы реализуем логику для главной страницы, где пользователи будут вводить данные для сканирования. Добавьте следующую функцию в app.py:

## Define route for the index page
@app.route('/', methods=['GET', 'POST'])
def index():
    form = ScanForm()  ## Instantiate the form
    if form.validate_on_submit():
        ## Get data from the form
        host = form.host.data
        ports = form.ports.data  ## Format: "start-end"
        ## Redirect to the scan route with form data
        return redirect(url_for('scan', host=host, ports=ports))
    ## Render the index page template with the form
    return render_template('index.html', form=form)

Эта часть скрипта app.py определяет маршрут для корневой страницы приложения. Функция index() отображает шаблон index.html, передавая в него экземпляр формы ScanForm.

Когда форма отправлена и успешно прошла валидацию, функция перенаправляет пользователя на маршрут сканирования, передавая введенные данные (хост и порты) через параметры URL. Это перенаправление запускает процесс анализа сети.

Реализация маршрута сканирования

Этот шаг посвящен созданию маршрута, который выполняет непосредственное сканирование и выводит результаты. Добавьте следующую функцию в app.py:

## Define route for the scan results
@app.route('/scan')
def scan():
    ## Retrieve host and ports from the query string
    host = request.args.get('host')
    ports = request.args.get('ports')
    ## Perform the scan using Nmap
    nm.scan(hosts=host, ports=ports, arguments='-sV')  ## -sV for service/version detection
    scan_results = []

    ## Process scan results and store them in a list
    for host in nm.all_hosts():
        for proto in nm[host].all_protocols():
            lport = nm[host][proto].keys()
            for port in lport:
                service = nm[host][proto][port]
                scan_results.append({
                    'port': port,
                    'state': service['state'],
                    'name': service.get('name', 'Unknown'),
                    'product': service.get('product', ''),
                    'version': service.get('version', ''),
                    'extra': service.get('extrainfo', '')
                })

    ## Render the results page template with the scan results
    return render_template('results.html', scan_results=scan_results, host=host)

Функция scan() отвечает за выполнение сканирования портов. Она извлекает адрес хоста и диапазон портов из параметров запроса в URL.

Используя экземпляр Nmap PortScanner, приложение проводит сканирование указанных целей. Аргумент -sV позволяет определить версии запущенных служб.

Результаты сканирования обрабатываются и структурируются в виде списка словарей, каждый из которых содержит подробную информацию о найденном порте. Затем эти данные передаются в шаблон results.html для отображения пользователю.

Запуск приложения Flask

Теперь, когда все компоненты готовы, пришло время запустить приложение и оживить ваш сканер портов. Последний фрагмент кода в app.py гарантирует, что сервер Flask запустится только в том случае, если скрипт выполняется напрямую, а не импортируется как модуль. Это стандартная практика для Python-приложений.

Добавьте следующий блок в самый конец файла app.py:

if __name__ == '__main__':
    app.run(debug=True, port=8080, host='0.0.0.0')

Этот код указывает Flask запустить приложение с включенным режимом отладки (debug=True), что упрощает поиск ошибок. Приложение будет принимать запросы на всех сетевых интерфейсах (host='0.0.0.0') через порт 8080. Помните, что режим отладки предназначен только для разработки и небезопасен для использования в реальной эксплуатации.

Чтобы запустить приложение, убедитесь, что вы находитесь в директории проекта, где расположен файл app.py. Затем выполните команду в терминале:

python app.py

Перейдите на вкладку Web 8080, чтобы открыть интерфейс сканера. Теперь вы можете ввести адрес хоста и диапазон портов для проверки.

Обратите внимание: порты 22 и 3306 обычно связаны со службами SSH и MySQL соответственно, а порт 3000 используется для среды WebIDE.

Резюме

В этом проекте вы научились создавать простой, но эффективный веб-сканер TCP-портов, используя Flask и Nmap. Мы начали с настройки окружения и установки зависимостей, после чего перешли к разработке логики приложения: обработке форм, выполнению сетевого сканирования и наглядному отображению результатов. Этот проект является отличным введением в веб-разработку на Python и сетевой анализ, объединяя эти две важные области в одном практическом инструменте.

✨ Проверить решение и практиковаться✨ Проверить решение и практиковаться✨ Проверить решение и практиковаться✨ Проверить решение и практиковаться✨ Проверить решение и практиковаться✨ Проверить решение и практиковаться