Passa al contenuto principale
Versione: 1.0.0

La scansione, e i due interruttori che la armano

La scansione attiva è l'unica parte di bitscanner che invia pacchetti non sollecitati a dispositivi di proprietà di qualcun altro. Sondare una rete che non si è autorizzati a sondare è una questione legale prima ancora che tecnica — e il segmento che si trova davanti all'agente è scelto dalle interfacce dell'host, non da te: una VPN, una rete di container in bridge o un'ampia interfaccia aziendale possono mettere a portata uno spazio di indirizzi che nessuno aveva previsto.

Quindi l'impostazione predefinita è l'inviluppo, non la capacità. Ogni interruttore qui sotto fallisce in modo chiuso, e quelli che contano possono essere impostati solo da una persona che modifica un file.

Per ciò che l'agente riporta senza nulla di tutto questo, vedi la panoramica di bitscanner.

I due interruttori​

Nulla sonda a meno che ENTRAMBI non siano veri
  1. scanning.enabled arma l'inviluppo — "questo agente può sondare, ecco l'ambito, e io sono autorizzato a farlo".
  2. scanning.probes.<name> sceglie quali sonde girano al suo interno — quanto rumore fanno.

Ogni sonda ha false come valore predefinito, quindi scanning.enabled: true da solo invia esattamente zero pacchetti.

Sono due decisioni distinte perché le due modalità di guasto sono diverse: un ambito sbagliato sonda le persone sbagliate, un insieme di sonde sbagliato sonda le persone giuste in modo troppo aggressivo. Allargare l'uno o l'altro è sempre una modifica esplicita.

scanning:
enabled: false # the envelope
i_accept_scanning_authorization: false # your assertion that you may probe these networks
allowed_cidrs: [] # the ONLY address space that may ever be probed
probes:
neighbor_discovery: false # …and every probe, individually

Una sonda armata mentre l'inviluppo è disattivato è un errore di avvio che nomina entrambe le chiavi, non un'impostazione ignorata in silenzio. Ignorarla in silenzio lascerebbe un operatore che rilegge il proprio file convinto che una scansione sia in corso — oppure a concludere che il prodotto è rotto quando non compare alcun risultato:

$ bitscanner config validate --config config.yaml
Error: configuration validation failed: failed to load config: config validation failed:
scanning.probes.neighbor_discovery, scanning.probes.port_scan are true but
scanning.enabled is false: the probe toggles choose WHICH probes run, scanning.enabled
arms the envelope that permits any of them at all, and with the envelope off every one of
those probes would be denied. Either set scanning.enabled: true (together with
scanning.i_accept_scanning_authorization and scanning.allowed_cidrs), or turn off
scanning.probes.neighbor_discovery, scanning.probes.port_scan

Lo stesso trattamento vale per una sonda il cui prerequisito è disattivato. Una scansione delle porte gira sopra l'elenco dei vicini prodotto dalla scoperta dei vicini, quindi chiedere l'una senza l'altra è una configurazione che girerebbe senza produrre nulla — indistinguibile, dal lato dell'operatore, da un agente rotto:

scanning.probes.port_scan is true but scanning.probes.neighbor_discovery is false: the
port scan probes the neighbours that neighbour discovery finds; with neighbour discovery
off the target list is empty and nothing would be scanned. Set
scanning.probes.neighbor_discovery: true as well, or turn off scanning.probes.port_scan

E abilitare l'inviluppo senza la dichiarazione di accettazione dell'autorizzazione:

scanning.enabled is true but scanning.i_accept_scanning_authorization is false: active
scanning sends unsolicited packets to other people's devices, and setting this key is you
asserting that you are authorised to probe every network listed in scanning.allowed_cidrs
(you own them, or you hold written permission from whoever does). Probing a segment you
are not authorised to probe — a shared/colocated network, or a partner network reachable
over a VPN — may be unlawful.

Non esiste nemmeno un implicito valore predefinito che scansiona tutto. allowed_cidrs deve essere non vuoto quando l'inviluppo è attivo, le voci devono essere reti canoniche, e un 0.0.0.0/0 viene rifiutato senza eccezioni:

scanning.allowed_cidrs entry "10.20.1.3/16" is not a network address: it authorises
10.20.0.0/16 (65536 addresses), which is almost certainly wider than intended. Write the
network explicitly, or use a /32 (or /128) for a single host

Quel rifiuto esiste perché ogni parser CIDR al mondo allarga in silenzio un indirizzo host con una maschera di rete. L'operatore ha scritto un indirizzo e ne ha autorizzati 65.534.

scanning.* e security.* possono provenire solo dal file​

Impostarne uno da una variabile d'ambiente arresta l'agente
$ BITSCANNER_SCANNING_ENABLED=true bitscanner config validate --config config.yaml
Error: configuration validation failed: failed to load config: refusing to start:
BITSCANNER_SCANNING_ENABLED set in the environment, and the scanning envelope and the
security block are settable ONLY from the config file. Those keys — scanning.enabled,
scanning.i_accept_scanning_authorization, scanning.allowed_cidrs, every
scanning.probes.*, and security.i_accept_plaintext_egress — are acknowledgements that a
person is authorised to probe somebody else's network, or that a customer's network map
may cross the wire in cleartext. An acknowledgement has to be written by hand into a file
that can be reviewed and diffed, not inherited from a shell profile, a systemd drop-in, a
compose file or a parent process. This is NOT ignored and NOT applied: the agent stops.

Questo è il senso delle dichiarazioni di accettazione, e vale la pena essere espliciti sul perché.

Tutto il valore di i_accept_scanning_authorization sta nel fatto che una persona l'ha scritto in un file che può essere revisionato, confrontato con un diff e conservato nella gestione delle configurazioni. Una variabile esportata da un profilo di shell, da un drop-in systemd, da un file compose o da un processo padre compromesso non è nulla di tutto ciò. Se l'ambiente potesse impostarla, il controllo sarebbe una formalità.

Tre proprietà lo rendono strutturale, anziché una regola che qualcuno deve ricordarsi:

  • L'ambiente non è una fonte per quelle chiavi. L'associazione alle variabili d'ambiente è un'allowlist esplicita di normali chiavi operative (agent.id, intervalli, dimensioni dei batch). Nulla al suo interno può armare una sonda o allentare la sicurezza del trasporto.
  • I sottoalberi di sicurezza vengono riletti dal file da un secondo loader che non ha alcun tipo di associazione all'ambiente, e quei valori sovrascrivono qualunque cosa abbia prodotto il loader principale.
  • Un tentativo è un rifiuto, non un filtro. L'intero namespace BITSCANNER_SCANNING_* e BITSCANNER_SECURITY_* è riservato, e l'errore nomina la variabile — perché a un operatore convinto di aver armato una scansione non si può semplicemente non dire nulla.

Il namespace riservato è deliberatamente più ampio delle chiavi di configurazione: anche una tua variabile chiamata BITSCANNER_SECURITY_TOKEN viene rifiutata. Un rifiuto che nomina la variabile è recuperabile in pochi secondi; un elenco di pattern che deve indovinare quali grafie contano non lo è.

La scala delle sonde​

Tutte e sette hanno false come valore predefinito. Sono elencate a partire dalla meno invasiva — e ogni riga dice cosa finisce davvero sulla rete, perché un operatore non può dare il proprio consenso a qualcosa descritto solo dal nome.

SondaCosa mette sulla reteRichiede
neighbor_discoveryNulla. Legge la cache ARP/NDP del kernel stesso e arricchisce ogni voce da una tabella MAC/OUI dei produttori inclusa nel binario e offline—
gateway_discoveryNulla di nuovo. Tabella di routing più attribuzione del MAC dalla cache ARP e dalla tabella OUI offline. ⚠️ Non vero in 0.1.3-ga e versioni precedenti — inviava anche un echo ICMP a ogni gateway trovato; vedi l'avvertenza sotto—
reverse_dnsUna query PTR per ogni vicino senza nome. Questo è un pacchettoneighbor_discovery
service_discoveryUna query mDNS verso 224.0.0.251:5353 e un M-SEARCH SSDP verso 239.255.255.250:1900. Un pacchetto ciascuno — e tutti i dispositivi del dominio di broadcast li vedononeighbor_discovery
gateway_probeConnessioni TCP alle porte del gateway stesso (80, 443, 22, 23, 53; poi 8080/8443 per la caratterizzazione dei servizi, con lettura dei banner), più un echo ICMPgateway_discovery
active_arp_sweepINVASIVA. Percorre l'intervallo utilizzabile di ogni subnet collegata e tenta connessioni TCP su una manciata di porte per ogni indirizzo — da centinaia a migliaia di tentativi di connessione per cicloneighbor_discovery
port_scanLA PIÙ INVASIVA. ~19 porte TCP comuni su ogni vicino scoperto, più la lettura dei banner su quelli che ne presentano uno (21/22/25/110/143)neighbor_discovery

Tre di queste meritano più di una riga di tabella.

reverse_dns è un interruttore perché prima non lo era​

La scoperta dei vicini viene presentata come la prima esecuzione sicura: leggere le tabelle del kernel, non inviare nulla. Quell'affermazione era falsa. Ogni vicino senza nome veniva risolto all'inverso in modo incondizionato, e non esisteva alcuna configurazione che lo disattivasse — un'osservazione dell'inviluppo di scansione con il solo neighbor_discovery armato registrava due sonde concesse per ciclo, che erano proprio queste risoluzioni.

Una risoluzione inversa è un pacchetto, ed è più rivelatore di quanto sembri. Va a qualunque resolver punti questo host — su un segmento aziendale, spesso uno gestito e tracciato dal team di sicurezza del cliente stesso — e la query rivela esattamente quali dei loro host questo agente ha enumerato, un PTR alla volta. Su un segmento dove l'agente è in prova senza che il team di rete lo sappia, la divulgazione è quella, non la scansione delle porte.

È disattivata per impostazione predefinita, così la modalità a zero pacchetti è raggiungibile tramite configurazione.

gateway_probe richiede un privilegio che potrebbe non avere​

Il controllo di raggiungibilità apre un socket ICMP raw (ip4:icmp), che richiede root o CAP_NET_RAW. Senza, l'apertura fallisce e l'agente ripiega su un datagramma UDP — e il ripiego viene sottoposto alla guardia separatamente, perché un bersaglio non autorizzato non deve diventare autorizzato solo perché il primo metodo è fallito.

Il gateway è il router di qualcuno, e spesso l'unico dispositivo il cui guasto manda giù l'intero segmento. Essere nella tua tabella di routing non è un'autorizzazione a sondarlo, ed è per questo che gateway_discovery (passiva, risponde a "dietro quale router mi trovo, e il suo MAC è cambiato") e gateway_probe (attiva) sono chiavi separate.

Anche gateway_discovery apriva questo socket raw, in 0.1.3-ga e versioni precedenti

La riga sopra dice che gateway_discovery non mette nulla di nuovo sulla rete, e il paragrafo sopra dice che il socket ICMP raw appartiene a gateway_probe. In tutte le release fino a 0.1.3-ga inclusa, nessuna delle due cose è vera.

La chiamata di raggiungibilità si trovava nel ramo preso quando gateway_probe è spenta. Armare gateway_discovery e rifiutare deliberatamente gateway_probe — che è esattamente il modo in cui un operatore dice «non aprite un socket raw verso il mio router e non costringetemi a concedere CAP_NET_RAW» — ne apriva comunque uno, una volta per gateway e per ciclo, con un datagramma UDP verso la porta 33434 come ripiego ogni volta che il socket raw veniva rifiutato.

Ciò che non è mai stato interessato: quel sondaggio restava autorizzato dalla guardia di scansione, quindi poteva raggiungere solo un indirizzo dentro i vostri allowed_cidrs, ed era rifiutato del tutto con scanning.enabled: false. Il difetto è che un interruttore documentato come spento agiva comunque — non che qualcosa sia sfuggito all'involucro.

Se state eseguendo 0.1.3-ga o una versione precedente con gateway_discovery armata, accettate che il vostro gateway riceva un ping a ogni ciclo, oppure disattivate gateway_discovery finché non potete aggiornare. Nel codice sorgente la via passiva ora non invia più nulla: riporta la raggiungibilità solo dalla voce ARP risolta dal kernel stesso, non riporta alcun tempo di andata e ritorno che non abbia misurato, e la build fallisce se una funzione diversa dalla via di gateway_probe può raggiungere quel socket — fissare il socket a una funzione anziché ai suoi chiamanti è ciò che ha permesso a questo difetto di sopravvivere a due revisioni.

active_arp_sweep è limitata dal file, non dall'host​

Le subnet provengono dalle interfacce di questo host, quindi è una VPN o un'ampia interfaccia aziendale — non la tua configurazione — a decidere cosa si trova davanti alla scansione a tappeto. allowed_cidrs e min_prefix_length sono ciò che la trattiene. min_prefix_length ha 22 come valore predefinito (~1.022 host) e viene rifiutato del tutto al di sotto di /16: non per via dei pacchetti, che il budget di host limita, ma perché il ciclo di scansione a /8 percorrerebbe 16 milioni di indirizzi chiedendo il permesso per ciascuno.

Il risultato misurato a zero pacchetti​

L'affermazione "la scoperta dei vicini da sola non invia nulla" è del tipo che va misurata anziché asserita. Eseguito su un normale host Linux con scanning.enabled: true, neighbor_discovery armato e ogni altra sonda disattivata — l'intervallo consentito qui sotto è stato sostituito con un intervallo da documentazione, e nient'altro è stato modificato:

WARN scanguard active scanning is ENABLED: this agent will send unsolicited probes
{"allowed_cidrs": ["10.20.0.0/24"], "allow_public_targets": false,
"min_prefix_length": 22, "max_hosts_per_cycle": 1024, "max_concurrency": 16,
"per_probe_delay": "50ms", "max_cycle_duration": "5m0s"}
WARN agent ACTIVE SCANNING ARMED: this agent will send unsolicited probes
{"armed_probes": ["neighbor_discovery"], "confined_to_allowed_cidrs": ["10.20.0.0/24"], …}

INFO neighbor_scanner neighbor discovery complete
{"neighbors_found": 116, "subnets": 27, "duration": "71.116454ms"}
INFO network_collector scan envelope
{"probes_allowed_since_start": 0, "probes_denied_since_start": 0,
"denied_by_reason_since_start": {}, "hosts_probed_this_cycle": 0,
"host_budget_remaining_this_cycle": 1024}
INFO network_collector collection cycle complete
{"queued_for_delivery": ["network_state", "neighbor_table", "neighbor_discovery"]}

Quattro cicli consecutivi, 116–119 vicini reali su 27 subnet collegate, e probes_allowed_since_start è rimasto a 0 in ognuno di essi. La guardia di scansione non è mai stata interpellata, perché non c'era nulla da chiederle: i vicini sono usciti dalle tabelle del kernel e i nomi dei produttori da una tabella inclusa nel binario. Tre record di telemetria sono stati comunque messi in coda a ogni ciclo.

Questa è la forma di una prima installazione: attiva l'inviluppo, arma solo neighbor_discovery e scopri cosa c'è sul segmento prima di decidere se qualcosa di più rumoroso sia giustificato.

I contatori dichiarano su quale orologio corrono

probes_allowed_since_start e probes_denied_since_start sono totali sull'intera vita del processo; hosts_probed_this_cycle e host_budget_remaining_this_cycle si azzerano a ogni ciclo. Un tempo venivano stampati insieme sotto un'intestazione che dichiarava tutti e quattro come valori per ciclo, e su un agente in esecuzione il conteggio delle sonde permesse saliva da 686 a 1142 tra un ciclo e l'altro mentre il conteggio degli host restava a 16 — cosa che si legge come una scansione in escalation e non lo era affatto. La guardia non tiene contatori di decisione per ciclo, quindi i totali sono etichettati come totali anziché inventare un numero per ciclo da mettere nella riga di log.

scanguard: la decisione avviene dove il pacchetto parte​

Ogni sonda viene autorizzata immediatamente prima che venga tentata la connessione — non quando viene costruito l'elenco dei bersagli.

Perché quella distinzione è l'intero progetto

Un elenco di bersagli filtrato è una convenzione, e le convenzioni sono ciò che i futuri percorsi di chiamata aggirano in silenzio. Qualcuno aggiunge un percorso di codice che costruisce il proprio elenco, oppure riutilizza un indirizzo preso da una risposta, e il filtro applicato tre funzioni prima non gli si applica. Mettere il controllo dove il pacchetto parte significa che un nuovo percorso di chiamata o interpella la guardia, oppure non compila contro l'impianto del collector.

La guardia fallisce in modo chiuso a ogni livello: la guardia a configurazione azzerata nega tutto; una guardia nil passata a un collector viene sostituita da una che nega tutto anziché essere aggirata; un indirizzo che non si riesce ad analizzare — o che si analizza in modo ambiguo — viene negato; l'esaurimento del budget, del ritmo e della scadenza porta a negare anziché a degradare.

Ogni decisione, di permesso o di rifiuto, viene registrata con una motivazione leggibile da una macchina:

RifiutatoMotivazione registrata
La scansione attiva è disattivatascanning_disabled
Il bersaglio non è analizzabileunparseable_target
Loopback, link-local, multicast, indirizzo non specificato, indirizzo di rete o di broadcast dell'intervallo consentitoreserved_address
Qualsiasi cosa fuori da allowed_cidrsoutside_allowed_cidrs
Spazio di indirizzi pubblico senza allow_public_targetspublic_target_not_allowed
max_hosts_per_cycle raggiuntohost_budget_exhausted
max_cycle_duration raggiuntacycle_duration_exceeded
Una subnet più ampia di min_prefix_length, oppure fuori ambitosubnet_wider_than_min_prefix, subnet_outside_allowed_cidrs
Un gruppo multicast che non è un gruppo di discovery notonot_a_discovery_multicast_group
L'agente si sta arrestando, o il contesto del ciclo è stato annullato durante il sondaggiocontext_cancelled

Anche i permessi portano una motivazione — in_scope_private, in_scope_public_explicitly_allowed, link_local_discovery_group, subnet_within_sweep_limits — così la traccia di audit dice perché una sonda è stata permessa, e non soltanto che lo è stata.

I rifiuti reggono anche quando l'allowlist corrisponderebbe. È verificato direttamente dalla suite di test della guardia stessa, che passa sul commit fornito: loopback (v4 e v6), link-local (v4 e v6), multicast compreso il gruppo SSDP, l'indirizzo non specificato, l'indirizzo di broadcast limitato e gli indirizzi di rete e di broadcast dell'intervallo consentito sono tutti negati pur essendo dentro allowed_cidrs; un host privato in ambito viene permesso senza l'attivazione esplicita per gli indirizzi pubblici; e un indirizzo IPv6 mappato su IPv4 non può eludere l'allowlist.

0 probes sent, 4094 denied outside_allowed_cidrs non è un malfunzionamento. È la prova, visibile all'operatore, che è l'allowlist a decidere.

I tetti fanno parte dell'inviluppo​

ChiavePredefinitoLimitiCosa trattiene
min_prefix_length2216–32La subnet più ampia che una scansione a tappeto può percorrere
max_hosts_per_cycle10241–65536Host distinti sondati per ciclo, su tutte le subnet
max_concurrency161–256Sonde in volo contemporaneamente, applicato dalla guardia stessa
per_probe_delay50ms0–10sIntervallo minimo tra i pacchetti di sonda — per sonda, quindi una scansione di 19 porte su un solo host viene ritmata 19 volte
max_cycle_duration5m≤ 1hTetto sul tempo reale di sondaggio all'interno di un ciclo
allow_public_targetsfalse—Sondare fuori da RFC1918 / RFC6598 / RFC4193

Non sono suggerimenti di ottimizzazione. Un max_concurrency di 10.000 con ritardo zero su un /22 è un denial-of-service contro il segmento del cliente stesso, consegnato da un binario che ha installato su nostra raccomandazione — quindi i valori vengono controllati sul loro intervallo al caricamento e rifiutati se ne escono.

Passi successivi​

  • Cosa lascia l'host — cosa contengono davvero i record scoperti, come viaggiano e i limiti con cui questa release viene fornita.
  • Panoramica di bitscanner — cosa produce un ciclo, quanto costa e come verificare il download.
  • Le guardie di bitenforcer — lo stesso istinto progettuale applicato a un raggio d'impatto diverso: misurarlo, nominarlo e rifiutare anziché tirare a indovinare.
  • Asset Management — dove i dispositivi scoperti diventano asset con un proprietario.

Questa pagina ti è stata utile?