Accueil

Python pour le cyberespace - Deuxième partie

martin reato - 9 juillet 2021

Introduction 

Cet article est le deuxième de la série qui sert d'introduction aux scripts Python pour Cyber. Cette série suppose que le lecteur a une connaissance de base de Python et des réseaux. 

Avant de commencer cette série, il est recommandé de se familiariser avec les bases de la programmation et de connaître les principes fondamentaux de Python, ce que vous pouvez faire en suivant le formidable parcours de développement logiciel de WYWM. Cela vous permettra d'apprendre les bases de la programmation et les principes fondamentaux de Python.

En outre, une compréhension de la terminologie des réseaux est également recommandée. Vous l'avez deviné... WYWM a tout ce qu'il vous faut avec son cours sur les principes fondamentaux des réseaux.

J'ai également inclus un défi pour ceux qui aiment ça ! Tous les détails à la fin de l'article.

À qui s'adresse cette série ?

Cette série profitera aux amateurs de python, aux analystes en cybersécurité, aux testeurs de pénétration et à tous ceux qui souhaitent se familiariser avec l'écriture d'exploits. Elle profitera également à ceux qui apprécient tout simplement python et aiment "geeker" et avoir l'opportunité d'écrire leur propre code lorsqu'ils sont confrontés à un défi, que ce soit sur leur lieu de travail ou lorsqu'ils font une machine sur TryHackMe ou HackTheBox. 

À titre d'exemple, j'ai récemment réalisé un défi où je devais parcourir des fichiers PDF enregistrés dans un sous-répertoire. Je n'avais pas la possibilité de voir le nombre total de fichiers PDF. Tout ce que je pouvais voir était un fichier à la fois en demandant le nom valide du fichier pdf dans un navigateur. Cependant, une chose était très claire, ils avaient tous le même format de nomenclature. Oui, j'ai utilisé un outil comme l'intrus de Burp pour déterminer quels fichiers existaient ; cependant, j'ai aussi ensuite écrit mon propre code python pour reproduire cela. J'adore programmer et automatiser des tâches. C'est une excellente compétence à posséder et elle est très utile. Certains outils ne seront pas toujours disponibles en fonction de la portée, du contexte et des règles d'engagement. La connaissance de Python peut être très utile lorsque c'est tout ce que vous avez.

Serveur TCP

Cette deuxième entrée de la série se concentre sur la démonstration de la façon dont nous pouvons créer un simple serveur TCP. C'est aussi simple que de créer le client TCP de la première partie. À la fin, nous serons en mesure de faire communiquer le client TCP créé dans la première partie avec notre serveur nouvellement créé. La première partie peut être visualisée en cliquant sur le bouton ci-dessous :

Étape 1

Tout d'abord, nous allons créer un fichier appelé "udp_client.py" en utilisant un éditeur de texte. N'hésitez pas à utiliser nano, VIM ou tout autre éditeur de texte. Pour notre premier serveur TCP, notre script commence par l'importation du module socket. Ce module nous fournit les fonctions nécessaires à la création d'un serveur.

importation de la prise

Le module socket fournit de nombreuses fonctions et méthodes utiles en matière de réseau. Pour notre serveur TCP, nous utiliserons le module ci-dessous : 

  • socket()
  • bind()
  • écouter()
  • accepter()
  • recv()
  • envoyer()
  • fermer()

Pour ceux qui souhaitent approfondir le module, ils peuvent trouver la documentation complète du module socket ici : https://docs.python.org/3/library/socket.html

Étape 2

L'étape suivante consiste à ajouter deux variables à notre script qui seront nommées "IP" et "PORT". La variable "IP" contiendra l'adresse IP ou la plage d'adresses que le serveur écoutera. La variable "PORT" contiendra le numéro de port sur lequel notre serveur écoutera. 

Dans cet exemple, nous utilisons une adresse IP de "127.0.0.1". Ceci est utilisé pour que notre serveur TCP accepte les connexions clients entrantes de notre localhost ; cependant, nous pouvons toujours mettre une adresse spécifique ou même "0.0.0.0". L'adresse IP "0.0.0.0" indiquerait à notre serveur d'accepter les clients provenant de n'importe quelle adresse IP entrante.

IP = "127.0.0.1"

PORT = 1337

Étape 3

L'étape suivante crée un objet socket appelé "server" et spécifie le type de socket que nous voulons utiliser. Notre prochaine ligne sera la suivante :

  serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

  • server - est le nom que nous donnons à notre objet. Il peut s'agir de n'importe quoi en dehors des noms réservés. L'idéal serait d'écrire quelque chose qui décrit la fonction de l'objet que vous créez.
  • socket.socket() - est l'objet que nous créons à partir de notre module importé.
  • socket.AF_INET est une constante. Ce paramètre est typiquement ce que vous utiliserez le plus souvent lors de la création d'un client ou d'un serveur. Lorsqu'il est passé à socket(), il indique que nous allons utiliser ipv4.
  • socket.SOCK_STREAM est une constante. Ce paramètre est passé à socket() et indique que nous allons utiliser TCP.

Étape 4

Notre prochaine ligne de code utilisera la fonction bind() du module socket. Le module prend deux paramètres. Nous devons transmettre à la fonction une adresse IP et un numéro de port. En utilisant ces deux paramètres, la fonction bind() attribue le numéro de port et l'adresse IP à l'objet serveur. 

server.bind((IP,PORT))

  • server - fait référence à l'objet que nous avons créé à l'étape précédente où nous avons identifié que notre serveur utilisera ipv4 et TCP.
  • bind() - définit le serveur pour utiliser les variables "IP" et "PORT" que nous avons créées à l'étape 2.

Étape 5

Dans l'étape précédente, nous avons attaché une adresse IP et un PORT à notre serveur. Dans cette étape, nous utiliserons la fonction listen(); cela demandera au serveur de commencer à écouter sur le PORT que nous avons passé dans l'étape précédente avec la fonction bind(). De plus, il sait qu'il doit écouter l'adresse loopback (127.0.0.1).

serveur.listen()

  • server - fait référence à l'objet que nous avons créé à l'étape 3 où nous avons identifié que notre client utilisera ipv4 et TCP. 
  • listen() - est la méthode par laquelle nous indiquons à notre serveur le nombre de connexions simultanées qu'il peut gérer. Nous n'avons pas besoin de fournir de paramètres à la fonction car nous ne l'utilisons que pour initier une connexion à partir de notre client TCP. (Cela crée des problèmes dans python2, nous y reviendrons plus loin).

Étape 6

Notre prochaine ligne de code utilisera la méthode accept( ) qui démarre un listener sur le port spécifié de l'hôte local. De plus, il est important de savoir que lorsqu'un client se connecte au serveur, la méthode accept() stocke les données dans un tuple. L'IP et le numéro de port utilisés par le client entrant sont enregistrés dans ce tuple afin que le programme puisse garder en mémoire l'endroit où renvoyer les données au client.

client,(ip,port) = server.accept()

  • client, (ip,port) - tuple "client" qui contient/enregistre les détails d'une connexion entrante sous forme d'IP et de PORT.
  • server.accept() - ouvre un port et écoute les connexions entrantes.

À ce stade, nous pouvons vérifier que notre serveur se lie bien au port 1337. Même dans son état inachevé, nous pouvons confirmer que tout fonctionne comme prévu jusqu'à présent. Pour des raisons de clarté et de facilité d'utilisation, nous allons ajouter une simple instruction print indiquant que le serveur est à l'écoute.

Nous pouvons exécuter notre serveur dans un terminal et vérifier que le serveur écoute bien sur le port 1337 dans un autre terminal en tapant :

sudo lsof -i:<port number>

Super ! Notre serveur écoute sur le port 1337. 

Étape 7

Afin d'envoyer des données, un client doit se connecter au serveur. Une fois que cela est fait, la variable client (qui est un tuple) que nous avons créée à l'étape précédente aura une adresse de retour composée du numéro d'IP et de PORT d'où elle provient. Par conséquent, afin d'envoyer des informations au client, nous utilisons la ligne de code suivante : 

client.send(b 'Vous vous êtes connecté avec succès au serveur TCP')

  • client.send() - demande à notre programme d'envoyer des données au client. Ici, nous envoyons un message, indiquant simplement à l'utilisateur qui s'est connecté avec le client TCP, qu'il a réussi.

Étape 8 

Une fois que nous aurons envoyé un message de confirmation au client, nous ferons en sorte que notre client TCP envoie également un message au serveur. Pour cela, nous allons créer une autre variable appelée "data" où les données reçues seront stockées. Nous allons également ajouter une instruction print pour sortir les données.

  données = client.recv(1024)

print(f’[*] Received message: {data.decode(“utf-8”)}’)

  • Data - est notre variable nouvellement créée.
  • Client - est le tuple créé par accept() précédemment 
  • recv() - est la fonction responsable de la réception des données du client dans cette instance. (client.recv()) Nous attribuons également une mémoire tampon de 1024 octets pour le message à envoyer. Ceci peut être ajusté, mais pour nos besoins, c'est plus que suffisant.
  • print() - imprime les données reçues sur le terminal.

Étape 9 

Maintenant que le serveur TCP peut envoyer et recevoir des informations, nous pouvons fermer la connexion. Nous allons ajouter deux lignes de code pour fermer la connexion du client et du serveur.

client.close()

server.close()

  • client.close() - ferme la connexion avec le client.
  • server.close() - Ferme le serveur. Détache le port attribué lors de la connexion.

Étape 10

Pour cette prochaine étape, nous allons devoir utiliser le client TCP que nous avons créé dans la première partie. Si vous n'avez pas fait ou complété votre client TCP, je vous suggère d'essayer la première partie en cliquant sur le bouton ci-dessous :

Cela dit, nous pouvons aussi nous connecter à notre serveur avec un autre outil tel que netcat. Si vous utilisez Netcat, sautez cette étape et passez à l'étape 11.

Dans la première partie de la série, nous avons fait en sorte que notre client TCP fasse une requête "GET" vers une adresse HTTP. Pour cette étape, nous allons modifier la fonction TCP send() pour inclure un simple message "Hello". Les images ci-dessous du client TCP peuvent différer légèrement si le module argparse a été importé.

Client TCP original

Client TCP modifié

Étape 11

Notre client TCP est prêt (ou netcat) et le serveur TCP est également terminé. Faisons une connexion ! Dans un terminal, nous démarrons notre serveur.

Python3 tcp_server.py

Puis nous exécutons le client sur l'adresse de bouclage et le port que nous avons lié à notre serveur: :

Python3 127.0.0.1 1337

La connexion est établie et le client TCP envoie le message "Hello". Comme nous l'avons mentionné précédemment, nous pouvons obtenir le même résultat avec Netcat ; cependant, avec Netcat, le message doit être tapé par l'utilisateur et la touche "entrée" doit être pressée pour que le message soit envoyé.

Traitement d'une erreur courante

Une erreur qui est souvent rencontrée lors de la création d'un serveur est l'erreur observée dans l'image suivante :

Cela se produit lorsque la combinaison de ports est déjà utilisée. Si vous avez choisi un port utilisé par un autre service, cette erreur se produira. Cependant, cela se produit le plus souvent sur votre serveur TCP lorsque vous fermez la connexion et essayez de redémarrer le serveur trop rapidement. Cela peut prendre quelques secondes pour que le port soit à nouveau libre car il n'est pas disponible pour être réutilisé. Nous pouvons résoudre ce problème avec une ligne de code supplémentaire qui utilise la fonction setsockopt().

server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

Il n'y aura pas d'erreur lorsqu'on essaiera de redémarrer le serveur immédiatement après une connexion fermée ou une panne du serveur.

Pour plus d'informations sur la fonction setsockopt(), veuillez consulter la ressource suivante :

https://docs.python.org/3/library/socket.html

Mise à niveau des fonctionnalités

Super, notre serveur fonctionne. Faisons en sorte que notre serveur puisse recevoir des informations jusqu'à ce que le client ferme la connexion et non pas seulement recevoir une chaîne de données suivie d'une fermeture de la connexion.

Pour cela, nous allons demander à notre script d'exécuter notre fonction accept( ) pour écouter jusqu'à ce que quelqu'un ferme le serveur. Nous imbriquons également une autre boucle while pour la fonction recv(). Cette deuxième boucle attend que des données soient envoyées par le client jusqu'à ce que ce dernier ferme la connexion. En outre, nous insérons une instruction IF afin que, si le client ferme la connexion, nous sortions de notre boucle while imbriquée et retournions à notre boucle initiale qui écoute les connexions entrantes. 

Maintenant nous pouvons vérifier que tout fonctionne comme prévu. Pour cet exemple, nous utiliserons Netcat pour envoyer les données car notre client TCP n'était pas configuré pour envoyer plus d'une ligne de données.

Nous pouvons observer les messages reçus du client Netcat et lorsque Netcat est fermé, le serveur reste opérationnel et écoute d'autres connexions. Pour fermer le serveur, nous pouvons simplement appuyer sur CTRL+C.

Python2 vs Python3

Dans la première partie, j'ai mentionné quelques différences entre Python2 et 3. Il y a aussi quelques différences que nous pouvons observer dans notre serveur. Dans Python2, il y a des erreurs produites lors de l'exécution du serveur TCP. La première étant la fonction listen() qui prend un argument. Dans Python3, nous n'avons pas besoin d'en fournir un ; la valeur par défaut est de 1. C'était suffisant pour nos besoins. Si nous voulions écouter de nombreuses connexions, il faudrait modifier cette fonction. Cela dit, en Python2, nous devons fournir un paramètre. 

Le simple ajout d'un paramètre permettra de résoudre ce problème.

Dans notre serveur TCP, j'utilise le préfixe "f" dans l'instruction print(). C'est ce qu'on appelle une chaîne f et le préfixe doit être inclus en raison de la variable "data" intégrée qui est utilisée à l'intérieur des accolades.

En supprimant le préfixe "f", l'accolade et son contenu seront imprimés littéralement.

Lorsque nous exécutons le script en Python2, nous obtenons une erreur :

Cela se produit parce que la chaîne f a été introduite dans Python3.6. En Python2, pour obtenir le résultat souhaité, nous pourrions remplacer notre instruction print() par la suivante :

Avec les modifications apportées au script, notre serveur fonctionne avec Python2 :

Défi et incitation

Comme défi supplémentaire. Pouvez-vous modifier le client TCP créé dans la partie 1 pour envoyer plus d'une ligne de données avec le serveur créé ? Tout comme Netcat l'a fait.

Instructions :

Le client TCP créé dans la partie 1 envoie une ligne de données et ferme la connexion. Faites en sorte qu'il puisse envoyer des données au serveur jusqu'à ce que la connexion soit intentionnellement fermée par le client. Envoyez-moi votre extrait de code ou un lien vers un repo github à mon email (martin.reato@withyouwithme.com) ou sur Discord (mreato#5413). Parmi les participants, je choisirai au hasard une personne et lui remettrai un prix.

Date de fin du défi : 16 juillet 2021

Prix : Un bon d'achat de 3 mois pour TryHackMe

Conclusion

Ceci marque la fin de la partie 2 de la série Python for Cyber. Notre serveur TCP est complet et nous avons pu faire en sorte que notre client TCP et Netcat s'y connectent. Il existe de nombreuses façons de configurer notre serveur et même le client de la partie 1 pour améliorer la fonctionnalité. N'hésitez pas à expérimenter et à apprendre quelque chose de nouveau, soyez créatifs ! 

N'hésitez pas à nous contacter pour toute question/commentaire/suggestion.

A la vôtre ! 

martin.reato@withyouwithme.com

Si vous voulez percer dans le secteur des technologies, inscrivez-vous sur notre plateforme et commencez votre formation dès aujourd'hui.

Laisser une réponse

Votre adresse électronique ne sera pas publiée. Les champs obligatoires sont marqués d'un *.

Rejoignez notre communauté

Nous avons un serveur Discord où vous pourrez discuter avec vos instructeurs et votre cohorte. Restez actif dans votre apprentissage !
Rejoindre le discord