Introduction
Dans le projet précédent, nous avons développé un scanner de ports en Python qui exploitait le threading et les sockets pour analyser les ports TCP. Bien que cette solution soit fonctionnelle, l'utilisation de bibliothèques tierces permet d'atteindre un niveau de performance et de fiabilité supérieur.
Dans ce projet, nous allons perfectionner notre outil en intégrant la bibliothèque python-nmap, offrant ainsi des capacités d'analyse beaucoup plus robustes. De plus, nous allons concevoir une application web avec Flask afin de proposer une interface utilisateur intuitive. Ce guide étape par étape vous accompagnera dans tout le processus, vous permettant de consolider et de mettre en pratique vos connaissances existantes.
👀 Aperçu
🎯 Tâches
Dans ce projet, vous apprendrez à :
- Configurer un projet Flask et organiser sa structure de fichiers.
- Utiliser Flask-WTF pour créer et gérer des formulaires web de manière sécurisée.
- Implémenter des routes Flask pour gérer les requêtes et les soumissions de pages web.
- Exploiter la bibliothèque Nmap en Python pour effectuer des scans de ports.
- Afficher dynamiquement les résultats d'analyse sur une page web via Flask et les templates HTML.
- Appliquer les bases de Tailwind CSS pour soigner le design de l'interface utilisateur.
🏆 Acquis
À l'issue de ce projet, vous serez capable de :
- Démontrer une compréhension fondamentale du développement web avec Flask, incluant le routage, le rendu de templates et la gestion de formulaires.
- Intégrer concrètement des scripts Python à des interfaces web.
- Maîtriser l'utilisation de la bibliothèque Nmap pour des tâches d'analyse réseau.
- Utiliser Flask-WTF pour la création et la validation de formulaires dans une application web.
- Utiliser Tailwind CSS pour styliser des pages web et améliorer l'expérience utilisateur.
- Créer une application web fonctionnelle capable d'interagir avec un backend Python pour réaliser des scans réseau.
Mise en œuvre de la page d'accueil
Pour commencer, ouvrez le fichier templates/index.html et ajoutez le code suivant pour configurer correctement le formulaire de soumission des requêtes de scan :
<!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>
Le template index.html fournit une interface simple pour saisir l'adresse de l'hôte et la plage de ports à analyser. Le formulaire HTML comprend deux champs principaux : l'un pour l'adresse IP ou le nom de domaine, et l'autre pour spécifier l'intervalle de ports.
Nous utilisons Flask-WTF, une extension Flask pour WTForms, pour générer ces champs dans le template. L'expérience utilisateur est améliorée par une petite fonction JavaScript updateButton() qui change le texte du bouton en "Scanning..." lors de l'envoi. Ce retour visuel indique à l'utilisateur que son analyse est en cours de traitement.
Le style de la page est assuré par Tailwind CSS, un framework CSS utilitaire qui permet de construire rapidement des interfaces modernes.
Mise en œuvre de la page des résultats
Ouvrez le fichier templates/results.html et insérez le code suivant pour afficher les données issues du scan :
<!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>
Le template results.html est chargé de présenter l'issue de l'analyse réseau. Il affiche les résultats sous forme de tableau, listant chaque port avec son état (ouvert/fermé), le nom du service détecté et sa version si elle est disponible.
Ce template utilise également Tailwind CSS pour garantir une présentation claire et adaptative (responsive). L'utilisation du moteur de template Jinja2 (intégré à Flask) permet d'injecter dynamiquement les données : les résultats du scan sont transmis de l'application Flask au template, puis parcourus par une boucle pour remplir les lignes du tableau.
Initialisation de l'application Flask
Dans cette étape, nous allons créer le script Python principal qui pilotera notre application Flask.
Ajoutez le code suivant dans le fichier app.py :
## Import nécessaire des 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
## Initialisation de l'application Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'labex'
## Initialisation du PortScanner Nmap
nm = nmap.PortScanner()
## Définition du formulaire Flask-WTF pour les entrées du scan
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')
Ici, nous initialisons l'application Flask ainsi que la classe de gestion du formulaire.
Le script app.py commence par importer les modules requis, notamment Flask, Flask-WTF pour les formulaires et Nmap pour l'analyse réseau. L'instance de l'application Flask est configurée avec une clé secrète (SECRET_KEY), indispensable pour protéger les formulaires contre les attaques de type CSRF.
La classe ScanForm définit les champs de saisie pour l'hôte et la plage de ports. Nous utilisons des validateurs pour s'assurer que les données sont bien renseignées et respectent le format attendu (notamment une expression régulière pour la plage de ports).
Remplacez 'labex' par une clé secrète robuste si vous déployez cette application, afin de garantir la sécurité des échanges.
Gestion de la route principale
Dans cette étape, nous allons définir la route d'accueil où les utilisateurs soumettent l'hôte et la plage de ports. Ajoutez la fonction suivante à app.py :
## Définition de la route pour la page d'accueil
@app.route('/', methods=['GET', 'POST'])
def index():
form = ScanForm() ## Instanciation du formulaire
if form.validate_on_submit():
## Récupération des données du formulaire
host = form.host.data
ports = form.ports.data ## Format attendu : "début-fin"
## Redirection vers la route de scan avec les données
return redirect(url_for('scan', host=host, ports=ports))
## Rendu du template de la page d'accueil avec le formulaire
return render_template('index.html', form=form)
Cette partie du script app.py définit le comportement de la page d'accueil. La fonction index() affiche le template index.html en lui passant l'instance de ScanForm.
Lorsque le formulaire est soumis et que les validations réussissent, la fonction redirige l'utilisateur vers la route de scan. Les paramètres (hôte et ports) sont transmis via l'URL, ce qui déclenche le processus d'analyse proprement dit.
Mise en œuvre de la route de scan
Cette étape consiste à créer la route qui effectue l'analyse réelle et affiche les résultats. Ajoutez la fonction suivante à app.py :
## Définition de la route pour les résultats du scan
@app.route('/scan')
def scan():
## Récupération de l'hôte et des ports depuis la chaîne de requête
host = request.args.get('host')
ports = request.args.get('ports')
## Exécution du scan avec Nmap
nm.scan(hosts=host, ports=ports, arguments='-sV') ## -sV pour la détection de service/version
scan_results = []
## Traitement des résultats et stockage dans une liste
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', '')
})
## Rendu du template des résultats avec les données collectées
return render_template('results.html', scan_results=scan_results, host=host)
La fonction scan() gère la logique métier de l'application. Elle extrait l'hôte et la plage de ports des paramètres URL.
En utilisant l'instance PortScanner de Nmap, elle lance une analyse sur la cible. L'argument -sV est crucial ici car il permet de tenter d'identifier les versions des services tournant sur les ports ouverts.
Les données brutes de Nmap sont ensuite transformées en une liste de dictionnaires plus facile à manipuler. Chaque dictionnaire contient les détails d'un port. Enfin, ces informations sont envoyées au template results.html pour être présentées à l'utilisateur.
Lancement de l'application Flask
Tous les composants étant prêts, vous pouvez maintenant lancer l'application et tester votre scanner de ports TCP. Le dernier bloc de code dans app.py garantit que le serveur Flask ne démarre que si le script est exécuté directement (et non importé comme module). C'est une pratique standard en Python.
Ajoutez ce fragment de code à la toute fin de votre fichier app.py :
if __name__ == '__main__':
app.run(debug=True, port=8080, host='0.0.0.0')
Ce code demande à Flask de démarrer le serveur avec le mode débogage activé, facilitant ainsi l'identification d'éventuelles erreurs. L'application écoutera sur toutes les interfaces réseau (host='0.0.0.0') sur le port 8080. Notez que le mode debug ne doit être utilisé qu'en phase de développement pour des raisons de sécurité.
Pour lancer l'application, assurez-vous d'être dans le répertoire du projet où se trouve app.py, puis exécutez la commande suivante dans votre terminal :
python app.py
Basculez sur l'onglet Web 8080 pour accéder à l'interface. Vous pouvez désormais saisir un hôte et une plage de ports pour lancer une analyse et consulter les résultats.
À titre d'information, les ports 22 et 3306 sont généralement associés aux services SSH et MySQL, tandis que le port 3000 est souvent utilisé par les environnements WebIDE.
Résumé
Au cours de ce projet, vous avez appris à construire un scanner de ports TCP web, à la fois simple et performant, en combinant Flask et Nmap. Nous avons commencé par configurer l'environnement et installer les dépendances. Nous avons ensuite développé l'application Flask, géré la soumission de formulaires, exécuté l'analyse réseau et affiché les résultats de manière ergonomique. Ce projet constitue une excellente introduction au développement web avec Flask et à l'audit réseau avec Nmap, offrant une application concrète qui réunit ces deux domaines de compétences.



