Ce qui quitte l'hôte
bitscanner expédie la description du réseau de quelqu'un hors de la machine sur laquelle il s'exécute. Deux questions deviennent donc porteuses : qu'y a-t-il dedans, et comment cela circule-t-il. Cette page répond aux deux, puis énonce sans détour les limites de cette version.
Pour savoir ce qui arme le sondage en premier lieu, voir Le scan, et les deux interrupteurs qui l'arment.
Ce que contient la télémétrie
Tout ce que bitscanner expédie porte sur le réseau qui entoure l'hôte, jamais sur le contenu de l'hôte lui-même.
| Enregistrement | Champs |
|---|---|
network_state | Routes uniquement : destination, gateway, interface, metric, flags |
neighbor_table | Par voisin : ip_address, hardware_addr, interface, state, type |
neighbor_discovery | Par voisin : adresse, MAC, vendor (recherche OUI hors ligne), device_type, is_reachable, first_seen/last_seen — plus hostname si reverse_dns est armé, services (ports, et bannières si port_scan est armé), mdns_services/ssdp_info si service_discovery est armé. Plus les sous-réseaux scannés |
gateway_discovery | Par passerelle : adresse, MAC, fabricant, is_default, joignabilité, et — uniquement avec gateway_probe — les ports qui ont répondu ainsi que toute bannière présentée |
service_discovery | Répondeurs : name, type, host, port, protocol, discovered_via, enregistrements TXT mDNS |
Deux conséquences méritent d'être dites explicitement :
- Les sondes les plus bruyantes enregistrent les versions logicielles d'autrui. Une bannière
issue du port 22 ou 25 est une chaîne de version appartenant à un équipement qui n'est
peut-être pas le vôtre. Ce n'est pas un effet de bord à découvrir plus tard ; c'est
précisément ce à quoi servent
port_scanetgateway_probe, et c'est pourquoi ils sont désactivés par défaut et conditionnés à une déclaration d'autorisation. hostnamen'existe que si vous avez arméreverse_dns. Sans cela, un voisin est une adresse, une MAC et une supposition de fabricant.
Ce qu'il ne collecte délibérément pas
network_state transportait autrefois interfaces (nom, MTU, MAC, adresses), listeners
(l'inventaire des sockets locaux), une liste connections toujours vide et un champ
dns_servers que rien ne remplissait jamais — un champ nul affirmant un inventaire de
résolveurs qui n'a jamais été collecté. Tous ont disparu, ainsi que le code et les types qui les
portaient.
Les trois premiers n'étaient qu'une redite des collecteurs réseau et ports de bitcollector. bitscanner ne collecte aucun fait sur l'hôte — pas de processus, pas de paquets installés, pas de comptes locaux, pas de lignes de commande. Si c'est un enregistrement sur la machine elle-même qu'il vous faut, il vient du collecteur, sous la confidentialité par défaut de celui-ci.
Chaque enregistrement est encapsulé dans une enveloppe portant une somme de contrôle SHA-256
(checksum) sur son en-tête et sa charge utile. C'est un contrôle d'intégrité, pas une
signature — bitscanner n'a pas d'équivalent de la chaîne de preuves signée et chaînée par
hachage de bitcollector.
Le trafic sortant est en https, ou l'agent ne démarre pas
La règle porte sur les données, pas sur l'identifiant d'authentification : une carte du réseau du client qui circule en clair est lisible et modifiable par quiconque se trouve sur le chemin, qu'un jeton l'accompagne ou non.
telemetry.destinations[0]: endpoint http://es.example.com:9200 is not https — refusing to
start. Everything this agent collects about the customer's network would cross that link
in cleartext, readable and alterable by anyone on the path, credential or no credential.
Use an https endpoint; if this is a lab and you accept that the data is exposed, set
security.i_accept_plaintext_egress: true
security.i_accept_plaintext_egress est l'unique dérogation, réservée au fichier, elle est
annoncée dans le journal à chaque démarrage, et elle est faite pour les laboratoires. Avec un
identifiant configuré, il n'existe aucune dérogation — un endpoint en clair, ou
tls.skip_verify: true, est refusé sans discussion, parce que tout ce qui peut répondre à la
place de l'endpoint récolte l'identifiant.
Trois autres refus de la même famille :
- Un identifiant intégré à l'URL est refusé, avec pour instruction de le déplacer dans le
bloc
auth. Go transforme les informations d'utilisateur de l'URL en un véritable en-têteAuthorization: Basic, celui-ci atterrit dans chaque ligne de journal qui affiche l'endpoint, et l'expurgation de l'agent ne peut pas l'atteindre. - Un élément de certificat qui serait silencieusement ignoré est refusé. Définir
ca_cert/client_cert/client_keyalors quetls.enabledest faux est une erreur, parce que le bloc se lirait comme du TLS mutuel tout en ne présentant rien. headers:n'existe pas. L'ancien exemple de configuration annonçaitheaders: {Authorization: "Bearer YOUR-TOKEN"}et il n'y a jamais eu une ligne de Go pour le lire — les opérateurs croyaient leur télémétrie authentifiée alors qu'elle partait anonymement. Une configuration contenant cette clé est refusée nommément.
Aucune redirection n'est jamais suivie
refusing to follow the redirect from <from> to <to>: this agent never follows redirects on
egress, because net/http would re-attach the credential when the hostname matches (even
downgrading https to http) and would replay the batch body on a 307/308 to a host the
server chose. Point the endpoint at its final URL instead
Ces deux moitiés sont des propriétés de la bibliothèque standard de Go, pas des spéculations. Sa
règle de réattachement de l'en-tête Authorization compare uniquement le nom d'hôte — ni le
schéma, ni le port — de sorte qu'un serveur répondant 301 Location: http://same-host:80/
récupère le jeton de porteur en clair. Un en-tête de clé d'API personnalisé ne figure pas du
tout sur la liste des en-têtes sensibles de la bibliothèque standard : il est donc recopié vers
n'importe quel hôte, sur n'importe quel schéma. Et sur un 307/308, le corps de la
requête — l'inventaire réseau du client — est rejoué vers l'hôte que désigne la redirection.
Le client sortant n'est pas un *http.Client. Tous les champs de cette structure sont exportés :
c.CheckRedirect = nil ou le remplacement du transport aurait donc supprimé les deux contrôles
en une seule affectation — et il a été prouvé que ces deux modifications laissaient la suite de
tests entièrement au vert. Le client est encapsulé dans un type qui n'expose que Do et
CloseIdleConnections, et le transport est construit à partir d'une configuration TLS plutôt
qu'accepté depuis l'appelant : il ne reste donc aucun round-tripper fourni par l'appelant avec
lequel réécrire une requête.
L'identifiant est appliqué au moment de l'envoi, et non au moment de la construction de la requête, et il échoue en position fermée : si le canal ou l'identifiant ne passe pas le contrôle — non résolu, pas en https, vérification de certificat désactivée — le lot n'est pas envoyé. « Poser l'en-tête si l'on en a un sous la main » est exactement la forme qui a déjà expédié une fois de la télémétrie non authentifiée.
Les endpoints sont expurgés dans les journaux
Un endpoint est journalisé à chaque échec d'export : l'expurgation procède donc par refus par
défaut plutôt que par une liste de noms qui ressemblent à des identifiants — toutes les
valeurs de la chaîne de requête et le fragment entier sont expurgés, les noms de paramètres
étant conservés pour que la ligne reste exploitable au diagnostic (api_key=[redacted] indique
quel paramètre était défini). Les segments de chemin qui semblent porter un identifiant sont
expurgés eux aussi. Si l'URL ne s'analyse pas du tout, l'expurgateur renvoie une constante —
jamais le texte de l'appelant, parce qu'une URL qui échoue à l'analyse est en général une URL
dont le mot de passe contient précisément les caractères qui cassent l'analyseur.
Ce dernier point a une limite énoncée : l'heuristique de chemin peut manquer un secret court, ou
qui ressemble à un mot ordinaire, dans un segment de chemin. Le contrôle porteur, c'est que les
identifiants ont leur place dans le bloc auth, où ils sont typés, vérifiés et jamais formatés
dans une chaîne de caractères.
Fichiers de secrets
Un jeton n'est réellement sorti du fichier de configuration que si personne d'autre ne peut le
lire. bitscanner config validate refuse de démarrer dans chacun des cas suivants, et le
message nomme le chemin fautif ainsi que le correctif. Vérifié par exécution — les chemins
ci-dessous sont des chemins de documentation substitués à ceux de la transcription réelle :
# mode
control_plane: auth.token_file "/etc/bitscanner/control-plane.token" is mode 0644 —
readable by group or other; restrict it with `chmod 600 /etc/bitscanner/control-plane.token`
# any directory on the path, not just the one holding the file
control_plane: the directory "/opt/agent" on the path to auth.token_file
"/opt/agent/secrets/control-plane.token" is mode 0775 — group- or world-writable, so
another account can replace the file this agent reads. It is not the directory holding the
file — it is 1 level(s) above it — but another account can rename the whole subtree and
put its own in place. Restrict it with `chmod 755 /opt/agent` (or tighter), or move the
secret somewhere only root and this agent can write
L'ensemble complet des refus :
| Refusé | Parce que |
|---|---|
| Mode lisible par le groupe ou par les autres | Tous les comptes locaux de l'hôte peuvent lire le jeton |
| Détenu par un compte tiers | « Seul le propriétaire peut le lire » ne vaut rien quand le propriétaire est quelqu'un d'autre |
| Pas un fichier ordinaire | Un FIFO ou un périphérique n'est pas un fichier de secret — et l'ouverture simple d'un FIFO sans écrivain bloquerait l'agent au démarrage sans le moindre diagnostic |
| Atteint via un lien symbolique | La cible du lien se trouve dans un répertoire que le contrôle n'aurait pas examiné |
Tout répertoire du chemin accessible en écriture au groupe ou à tous — y compris /tmp | Ce compte peut renommer le sous-arbre et mettre son propre fichier à la place |
Les ancêtres du chemin résolu et le chemin tel qu'il est écrit sont tous deux parcourus,
et il est prouvé, par périphérique et inode, que le chemin résolu désigne bien le fichier dont
le descripteur est lu. Chacun de ces points était un vrai trou, comblé lors d'une vague
différente : ne contrôler que le parent immédiat, laisser passer un grand-parent accessible en
écriture à tous, puis l'image en miroir — ne parcourir que la chaîne résolue, ce qui acceptait
un fichier 0600 dans un répertoire 0700 atteint à travers un répertoire 0777, parce que la
chaîne résolue est immaculée et que c'est dans la chaîne écrite qu'habite l'attaquant.
Enfin, chaque secret provient d'exactement une source : la valeur en ligne, un
<field>_file, ou un <field>_env nommant une variable d'environnement. Deux sources à la fois
constituent une erreur plutôt qu'une règle de priorité — avec une règle de priorité, un
opérateur qui ajoute token_file en laissant en place un jeton en ligne périmé ne peut pas
savoir lequel des deux circule sur le réseau.