Construir un escáner de puertos TCP basado en la web

HTMLHTMLBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En el proyecto anterior, desarrollamos un escáner de puertos de Python que utilizaba subprocesamiento y sockets para escanear puertos TCP. Si bien es efectivo, existe margen para mejorar utilizando paquetes de terceros.

En este proyecto, mejoraremos nuestro escáner de puertos integrando la biblioteca python-nmap, ofreciendo capacidades de escaneo más robustas. Además, construiremos una aplicación web utilizando Flask para proporcionar una interfaz de usuario amigable para nuestro escáner. Este proyecto paso a paso lo guiará a través del proceso, asegurándose de que puedas seguir y construir sobre tus conocimientos existentes.

👀 Vista previa

🎯 Tareas

En este proyecto, aprenderás:

  • Cómo configurar un proyecto de Flask y organizar su estructura
  • Cómo utilizar Flask-WTF para crear y manejar formularios web de manera segura
  • Cómo implementar rutas de Flask para manejar solicitudes y envíos de páginas web
  • Cómo utilizar la biblioteca Nmap en Python para realizar escanos de puertos
  • Cómo mostrar los resultados del escaneo dinámicamente en una página web utilizando Flask y plantillas HTML
  • Cómo aplicar CSS básico de Tailwind para mejorar el diseño de la interfaz de usuario

🏆 Logros

Después de completar este proyecto, serás capaz de:

  • Demostrar un conocimiento básico del desarrollo web con Flask, incluyendo enrutamiento, renderizado de plantillas y manejo de formularios
  • Aplicar experiencia práctica en la integración de scripts de Python con interfaces web
  • Exhibir habilidad en el uso de la biblioteca Nmap para tareas de escaneo de red
  • Utilizar Flask-WTF para la creación y validación de formularios en una aplicación web
  • Mostrar familiaridad con el uso de CSS de Tailwind para dar estilo a páginas web y mejorar el diseño de la interfaz de usuario
  • Crear una aplicación web funcional que interactúe con scripts de Python de backend para realizar escanos de red

Implementando la página de índice

Para comenzar, abre templates/index.html y agrega el siguiente código para asegurarse de que el formulario esté correctamente configurado para enviar solicitudes de escaneo:

<!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/[email protected]/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>

La plantilla index.html proporciona una interfaz de usuario amigable para enviar un host y un rango de puertos para escanear. El formulario HTML incluye dos campos principales: uno para la dirección del host y otro para especificar el rango de puertos.

Flask-WTF, una extensión de Flask para trabajar con WTForms, se utiliza para renderizar estos campos en la plantilla. La envío del formulario se mejora con una pequeña función de JavaScript updateButton() que cambia el texto del botón de envío a "Scanning..." al enviar el formulario. Esta retroalimentación visual informa al usuario de que su solicitud de escaneo está siendo procesada.

El estilo de la página es manejado por Tailwind CSS, un framework CSS centrado en las utilidades que permite un desarrollo rápido de la interfaz de usuario.

✨ Revisar Solución y Practicar

Implementando la página de resultados del escaneo

Abre templates/results.html y asegúrate de incluir el siguiente código para mostrar los resultados del escaneo:

<!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/[email protected]/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>

La plantilla results.html es responsable de mostrar el resultado del escaneo de puertos. Presenta los resultados del escaneo en un formato de tabla, listando cada puerto con su estado correspondiente, nombre del servicio y versión del servicio si está disponible.

Esta plantilla utiliza Tailwind CSS para el estilo, lo que garantiza que la página de resultados sea responsiva y visualmente atractiva. El uso del motor de plantillas Jinja2 (integrado con Flask) permite el renderizado de contenido dinámico, donde los resultados del escaneo se pasan desde la aplicación Flask a la plantilla y se iteran para poblar la tabla.

✨ Revisar Solución y Practicar

Inicializar la aplicación Flask

En este paso, crearemos el script principal de Python para nuestra aplicación Flask.

Agrega el siguiente código en 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')

En este paso, se inicializan la aplicación Flask y la clase del formulario.

El script app.py comienza importando los módulos necesarios, incluyendo Flask en sí mismo, Flask-WTF para el manejo de formularios y Nmap para realizar escanos de puertos. La instancia de la aplicación Flask se crea y configura con una clave secreta para protegerse contra ataques CSRF.

La clase ScanForm define los campos del formulario para las entradas de host y rango de puertos, utilizando validadores para asegurarse de que los datos se proporcionen y estén en el formato correcto (especialmente para el rango de puertos).

Reemplaza 'your_secret_key' con una clave secreta real que se utiliza para proteger los formularios contra ataques CSRF.

✨ Revisar Solución y Practicar

Manejando la ruta de índice

En este paso, manejaremos la ruta de índice donde los usuarios pueden enviar el host y el rango de puertos que desean escanear. Agrega la siguiente función a 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)

Esta parte del script app.py define la ruta para la página de índice de la aplicación web. La función index() renderiza la plantilla index.html junto con la instancia de ScanForm.

Cuando se envía el formulario y pasa las comprobaciones de validación, la función redirige al usuario a la ruta de escaneo, pasando los datos del formulario (host y rango de puertos) a través de parámetros de URL. Esta redirección inicia el proceso de escaneo.

✨ Revisar Solución y Practicar

Implementando la ruta de escaneo

Este paso implica crear una ruta para realizar el escaneo real y mostrar los resultados. Agrega la siguiente función a 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)

La función scan() maneja la ruta responsable de realizar el escaneo real de puertos y mostrar los resultados. Recupera el host y el rango de puertos de los parámetros de la cadena de consulta pasados en la URL.

Utilizando la instancia de Nmap PortScanner, realiza un escaneo en el host y puertos especificados, con el argumento -sV para detectar las versiones de los servicios.

Los resultados del escaneo se procesan y se organizan en una lista de diccionarios, cada uno de los cuales contiene detalles sobre un puerto escaneado. Estos detalles se pasan luego a la plantilla results.html, donde se muestran al usuario.

✨ Revisar Solución y Practicar

Ejecutando la aplicación Flask

Con todos los componentes en su lugar, ya estás listo para ejecutar la aplicación Flask y dar vida a tu escáner de puertos TCP. El último trozo de código necesario en tu app.py asegura que la aplicación Flask solo se ejecutará si el script se ejecuta directamente, no si se importa como un módulo en otro script. Este es un patrón común en aplicaciones de Python que incluyen un script ejecutable.

Coloca el siguiente fragmento de código al final de tu archivo app.py:

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

Este código le dice a Flask que inicie tu aplicación con la depuración habilitada, lo que facilita la búsqueda de errores. La aplicación escuchará en todas las interfaces de red (host='0.0.0.0') y utilizará el puerto 8080. El modo de depuración solo debe usarse durante el desarrollo, ya que puede ser inseguro usarlo en un entorno de producción.

Para ejecutar tu aplicación Flask, asegúrate de estar en el directorio del proyecto donde se encuentra app.py. Luego, ejecuta el siguiente comando en la terminal:

python app.py

Cambia a la pestaña Web 8080 para acceder al escáner de puertos TCP. Ahora puedes ingresar un host y un rango de puertos para escanear y ver los resultados en la página de resultados.

Los puertos 22 y 3306 están asociados respectivamente con los servicios SSH y MySQL, el puerto 3000 se utiliza para los entornos WebIDE.

✨ Revisar Solución y Practicar

Resumen

En este proyecto, has aprendido cómo construir un sencillo pero potente escáner de puertos TCP basado en la web utilizando Flask y Nmap. Comenzamos configurando el entorno del proyecto e instalando las dependencias necesarias. Luego avanzamos hacia la creación de la aplicación Flask, el manejo de envíos de formularios, la realización del escaneo de puertos y la presentación de los resultados de manera amigable para el usuario. Este proyecto es una excelente introducción al desarrollo web con Flask y al escaneo de redes con Nmap, ofreciendo una aplicación práctica que combina ambas habilidades.