Введение
В предыдущем проекте мы разработали сканер портов на 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 и сетевой анализ, объединяя эти две важные области в одном практическом инструменте.



