Construa um Scanner de Portas TCP Baseado na Web

HTMLBeginner
Pratique Agora

Introdução

No projeto anterior, desenvolvemos um scanner de portas em Python que utilizava threading e sockets para escanear portas TCP. Embora eficaz, há margem para melhorias através do uso de pacotes de terceiros.

Neste projeto, vamos aprimorar nosso scanner de portas integrando a biblioteca python-nmap, que oferece capacidades de varredura muito mais robustas. Além disso, construiremos uma aplicação web usando Flask para fornecer uma interface amigável ao usuário. Este projeto passo a passo guiará você por todo o processo, garantindo que você possa acompanhar e expandir seus conhecimentos existentes.

👀 Prévia

🎯 Tarefas

Neste projeto, você aprenderá:

  • Como configurar um projeto Flask e organizar sua estrutura
  • Como usar o Flask-WTF para criar e manipular formulários web de forma segura
  • Como implementar rotas no Flask para gerenciar requisições e envios de páginas web
  • Como utilizar a biblioteca Nmap no Python para realizar varreduras de portas
  • Como exibir resultados de varredura dinamicamente em uma página web usando Flask e templates HTML
  • Como aplicar o Tailwind CSS básico para aprimorar o design do frontend

🏆 Conquistas

Após concluir este projeto, você será capaz de:

  • Demonstrar uma compreensão fundamental do desenvolvimento web com Flask, incluindo roteamento, renderização de templates e manipulação de formulários
  • Aplicar experiência prática na integração de scripts Python com interfaces web
  • Exibir proficiência no uso da biblioteca Nmap para tarefas de varredura de rede
  • Utilizar o Flask-WTF para criação e validação de formulários em uma aplicação web
  • Demonstrar familiaridade com o uso do Tailwind CSS para estilizar páginas web e melhorar o design da interface do usuário
  • Criar uma aplicação funcional baseada na web que interage com scripts Python no backend para realizar varreduras de rede

Implementando a Página Inicial

Para começar, abra o arquivo templates/index.html e adicione o código a seguir para garantir que o formulário esteja configurado corretamente para enviar as solicitações de varredura:

<!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>

O template index.html fornece uma interface intuitiva para que o usuário informe o host e o intervalo de portas para a varredura. O formulário HTML inclui dois campos principais: um para o endereço do host e outro para especificar o intervalo de portas.

O Flask-WTF, uma extensão do Flask para trabalhar com WTForms, é utilizado para renderizar esses campos no template. O envio do formulário é aprimorado por uma pequena função JavaScript updateButton() que altera o texto do botão de envio para "Scanning..." assim que o formulário é enviado. Esse feedback visual informa ao usuário que sua solicitação de varredura está sendo processada.

A estilização da página é feita com o Tailwind CSS, um framework CSS utilitário que permite o desenvolvimento rápido de interfaces.

✨ Verificar Solução e Praticar

Implementando a Página de Resultados da Varredura

Abra o arquivo templates/results.html e certifique-se de incluir o código a seguir para exibir os resultados da varredura:

<!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>

O template results.html é responsável por mostrar o desfecho da varredura de portas. Ele apresenta os resultados em formato de tabela, listando cada porta com seu estado correspondente, nome do serviço e versão do serviço, caso esteja disponível.

Este template utiliza Tailwind CSS para a estilização, garantindo que a página de resultados seja responsiva e visualmente atraente. O uso do motor de templates Jinja2 (integrado ao Flask) permite a renderização de conteúdo dinâmico, onde os resultados da varredura são passados da aplicação Flask para o template e percorridos para preencher a tabela.

✨ Verificar Solução e Praticar

Inicializando a Aplicação Flask

Nesta etapa, criaremos o script Python principal para nossa aplicação Flask.

Adicione o seguinte código em 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')

Nesta etapa, a aplicação Flask e a classe do formulário são inicializadas.

O script app.py começa importando os módulos necessários, incluindo o próprio Flask, o Flask-WTF para manipulação de formulários e o Nmap para realizar as varreduras de portas. A instância da aplicação Flask é criada e configurada com uma chave secreta para proteção contra ataques CSRF.

A classe ScanForm define os campos do formulário para as entradas de host e intervalo de portas, utilizando validadores para garantir que os dados sejam fornecidos e estejam no formato correto (especificamente para o intervalo de portas).

Substitua 'your_secret_key' por uma chave secreta real, que é usada para proteger os formulários contra ataques CSRF.

✨ Verificar Solução e Praticar

Gerenciando a Rota Inicial

Nesta etapa, vamos gerenciar a rota inicial onde os usuários podem enviar o host e o intervalo de portas que desejam escanear. Adicione a seguinte função ao arquivo 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 do script app.py define a rota para a página inicial da aplicação web. A função index() renderiza o template index.html junto com a instância do ScanForm.

Quando o formulário é enviado e passa pelas verificações de validação, a função redireciona o usuário para a rota de varredura, passando os dados do formulário (host e intervalo de portas) via parâmetros de URL. Esse redirecionamento inicia o processo de varredura.

✨ Verificar Solução e Praticar

Implementando a Rota de Varredura

Esta etapa envolve a criação de uma rota para realizar a varredura propriamente dita e exibir os resultados. Adicione a seguinte função ao arquivo 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)

A função scan() gerencia a rota encarregada de realizar a varredura de portas real e exibir os resultados. Ela recupera o host e o intervalo de portas a partir dos parâmetros da string de consulta passados na URL.

Utilizando a instância do Nmap PortScanner, ela conduz uma varredura no host e portas especificados, com o argumento -sV para detectar as versões dos serviços.

Os resultados da varredura são processados e organizados em uma lista de dicionários, cada um contendo detalhes sobre uma porta escaneada. Esses detalhes são então passados para o template results.html, onde são exibidos para o usuário.

✨ Verificar Solução e Praticar

Executando a Aplicação Flask

Com todos os componentes prontos, você agora pode executar a aplicação Flask e dar vida ao seu scanner de portas TCP. O último trecho de código necessário no seu app.py garante que a aplicação Flask só será executada se o script for chamado diretamente, e não se for importado como um módulo em outro script. Este é um padrão comum em aplicações Python que incluem um script executável.

Coloque o seguinte trecho de código ao final do seu arquivo app.py:

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

Este código instrui o Flask a iniciar sua aplicação com a depuração (debug) ativada, facilitando o rastreamento de erros. A aplicação escutará em todas as interfaces de rede (host='0.0.0.0') e usará a porta 8080. O modo de depuração deve ser usado apenas durante o desenvolvimento, pois pode ser inseguro em um ambiente de produção.

Para executar sua aplicação Flask, certifique-se de estar no diretório do projeto onde o arquivo app.py está localizado. Em seguida, execute o seguinte comando no terminal:

python app.py

Mude para a aba Web 8080 para acessar o scanner de portas TCP. Agora você pode inserir um host e um intervalo de portas para escanear e visualizar os resultados na página de resultados.

As portas 22 e 3306 são amplamente associadas aos serviços SSH e MySQL, respectivamente, enquanto a porta 3000 é utilizada para os ambientes WebIDE.

✨ Verificar Solução e Praticar

Resumo

Neste projeto, você aprendeu como construir um scanner de portas TCP baseado na web, simples porém poderoso, utilizando Flask e Nmap. Começamos configurando o ambiente do projeto e instalando as dependências necessárias. Em seguida, avançamos na criação da aplicação Flask, manipulando o envio de formulários, realizando a varredura de portas e exibindo os resultados de maneira amigável. Este projeto serve como uma excelente introdução ao desenvolvimento web com Flask e à varredura de rede com Nmap, oferecendo uma aplicação prática que combina ambas as habilidades.