Zum Hauptinhalt springen
Version: 1.0.0

Was den Host verlässt

bitscanner versendet eine Beschreibung des Netzes von jemandem von der Maschine weg, auf der er läuft. Damit werden zwei Fragen tragend: was darin steht und wie es reist. Diese Seite beantwortet beide und benennt anschließend die Grenzen dieses Releases unumwunden.

Was das Sondieren überhaupt erst scharfstellt, steht unter Scannen, und die beiden Schalter, die es scharfstellen.

Was in der Telemetrie steht​

Alles, was bitscanner versendet, betrifft das Netz rund um den Host, nie die Inhalte des Hosts selbst.

DatensatzFelder
network_stateNur Routen: destination, gateway, interface, metric, flags
neighbor_tableJe Nachbar: ip_address, hardware_addr, interface, state, type
neighbor_discoveryJe Nachbar: Adresse, MAC, vendor (Offline-OUI-Nachschlag), device_type, is_reachable, first_seen/last_seen — dazu hostname, wenn reverse_dns scharfgestellt ist, services (Ports, und Banner, wenn port_scan scharfgestellt ist), mdns_services/ssdp_info, wenn service_discovery scharfgestellt ist. Dazu die gescannten Subnetze
gateway_discoveryJe Gateway: Adresse, MAC, Hersteller, is_default, Erreichbarkeit und — ausschließlich mit gateway_probe — die Ports, die geantwortet haben, sowie jedes Banner, das sie vorgezeigt haben
service_discoveryAntwortende Geräte: name, type, host, port, protocol, discovered_via, mDNS-TXT-Einträge

Zwei Folgen, die ausdrücklich genannt gehören:

  • Die lauteren Sonden erfassen die Softwareversionen anderer Leute. Ein Banner von Port 22 oder 25 ist eine Versionsangabe, die zu einem Gerät gehört, das Ihnen womöglich nicht gehört. Das ist kein Nebeneffekt, den man später entdeckt; genau dafür sind port_scan und gateway_probe da, und genau deshalb sind sie standardmäßig aus und hinter einer Berechtigungsbestätigung verriegelt.
  • hostname existiert nur, wenn Sie reverse_dns scharfgestellt haben. Ohne das ist ein Nachbar eine Adresse, eine MAC und eine Vermutung zum Hersteller.

Was er bewusst nicht erfasst​

network_state trug früher interfaces (Name, MTU, MAC, Adressen), listeners (das lokale Socket-Inventar), eine immer leere connections-Liste und ein Feld dns_servers, das nie irgendetwas befüllt hat — ein Nullfeld, das ein Resolver-Inventar behauptete, das nie erhoben wurde. Alle sind verschwunden, samt dem Code und den Typen dahinter.

Die ersten drei waren nichts anderes als die Netzwerk- und Port-Collectors von bitcollector, noch einmal aufgesagt. bitscanner erfasst keine Host-Fakten — keine Prozesse, keine installierten Pakete, keine lokalen Konten, keine Kommandozeilen. Wenn Sie einen Datensatz über die Maschine selbst brauchen, kommt er vom Collector, unter dessen Datenschutz-Voreinstellungen.

Jeder Datensatz ist in einen Umschlag verpackt, der eine SHA-256-checksum über seinen Header und seine Nutzlast trägt. Das ist eine Integritätsprüfung, keine Signatur — bitscanner hat kein Gegenstück zu bitcollectors signierter, per Hash verketteter Nachweiskette.

Ausgehender Verkehr läuft über https, sonst startet der Agent nicht​

Die Regel betrifft die Daten, nicht die Zugangsdaten: Eine Karte des Kundennetzes, die im Klartext über die Leitung geht, ist für jeden auf dem Weg lesbar und veränderbar — ob nun ein Token mitreist oder nicht.

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 ist das einzige Opt-out, es steht ausschließlich in der Datei, es wird bei jedem Start im Log angekündigt, und es ist für Labore gedacht. Sind Zugangsdaten konfiguriert, gibt es überhaupt kein Opt-out — ein Klartext-Endpunkt oder tls.skip_verify: true wird rundheraus abgelehnt, denn alles, was für den Endpunkt antworten kann, sammelt die Zugangsdaten ein.

Drei weitere Ablehnungen derselben Familie:

  • In die URL eingebettete Zugangsdaten werden abgelehnt, mit der Anweisung, sie in den auth-Block zu verschieben. Go macht aus dem Userinfo-Teil einen echten Authorization: Basic-Header, er landet in jeder Log-Zeile, die den Endpunkt ausgibt, und die Schwärzung des Agenten kommt nicht an ihn heran.
  • Zertifikatsmaterial, das stillschweigend verworfen würde, wird abgelehnt. ca_cert / client_cert / client_key zu setzen, während tls.enabled false ist, ist ein Fehler, denn der Abschnitt läse sich wie mutual TLS, während nichts vorgezeigt wird.
  • headers: gibt es nicht. Die alte Beispielkonfiguration bewarb headers: {Authorization: "Bearer YOUR-TOKEN"}, und es gab nie eine Zeile Go, die das gelesen hätte — Operatoren glaubten, ihre Telemetrie sei authentifiziert, während sie anonym hinausging. Eine Konfiguration, die diesen Schlüssel enthält, wird namentlich zurückgewiesen.

Es wird niemals einem Redirect gefolgt​

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

Beide Hälften davon sind Eigenschaften der Go-Standardbibliothek, keine Spekulation. Ihre Regel für das erneute Anhängen von Authorization vergleicht ausschließlich den Hostnamen — nicht das Schema, nicht den Port —, sodass ein Server, der mit 301 Location: http://same-host:80/ antwortet, das Bearer-Token im Klartext zurückbekommt. Ein eigener API-Key-Header steht auf der Liste sensibler Header der Standardbibliothek überhaupt nicht, wird also an jeden Host über jedes Schema kopiert. Und bei einem 307/308 wird der Anfrage-Body — das Netzinventar des Kunden — an genau den Host wiederholt gesendet, den der Redirect nennt.

Die Ablehnung ist eine Struktur, keine Einstellung

Der ausgehende Client ist kein *http.Client. Jedes Feld dieses Structs ist exportiert, sodass c.CheckRedirect = nil oder ein ausgetauschter Transport beide Kontrollen mit einer einzigen Zuweisung entfernt hätte — und für beide Änderungen wurde nachgewiesen, dass die Testsuite dabei vollständig grün bleibt. Der Client steckt in einem Typ, der nach außen nur Do und CloseIdleConnections zeigt, und der Transport wird aus einer TLS-Konfiguration gebaut, statt vom Aufrufer entgegengenommen zu werden — es bleibt also kein vom Aufrufer gelieferter RoundTripper übrig, mit dem sich eine Anfrage umschreiben ließe.

Die Zugangsdaten werden zum Sendezeitpunkt angewendet, nicht beim Zusammenbauen der Anfrage, und im Zweifelsfall wird verweigert: Wenn der Kanal oder die Zugangsdaten nicht in Ordnung sind — nicht aufgelöst, nicht https, Zertifikatsprüfung aus —, wird der Batch nicht gesendet. „Setze den Header, falls wir zufällig einen haben“ ist genau die Form, die schon einmal unauthentifizierte Telemetrie ausgeliefert hat.

Endpunkte werden in Logs geschwärzt​

Bei jedem Exportfehler wird ein Endpunkt protokolliert; deshalb schwärzt der Agent im Zweifel alles, statt sich auf eine Liste von Namen zu verlassen, die nach Zugangsdaten aussehen: jeder Query-Wert und das gesamte Fragment werden geschwärzt, wobei die Parameternamen erhalten bleiben, damit die Zeile weiterhin diagnostizierbar ist (api_key=[redacted] sagt, welcher Parameter gesetzt war). Auch Pfadsegmente, die nach Zugangsdaten aussehen, werden geschwärzt. Lässt sich die URL überhaupt nicht parsen, gibt der Schwärzer eine Konstante zurück — niemals den Text des Aufrufers, denn eine URL, die sich nicht parsen lässt, ist meist eine, deren Passwort genau die Zeichen enthält, die den Parser brechen.

Dieser letzte Punkt hat eine ausgesprochene Grenze: Die Pfad-Heuristik kann ein kurzes oder wortähnliches Geheimnis in einem Pfadsegment übersehen. Die tragende Kontrolle ist, dass Zugangsdaten in den auth-Block gehören, wo sie typisiert sind, geprüft werden und niemals in eine Zeichenkette formatiert werden.

Geheimnisdateien​

Ein Token ist erst dann wirklich aus der Konfigurationsdatei heraus, wenn niemand sonst es lesen kann. bitscanner config validate verweigert bei jedem der folgenden Fälle den Start, und die Meldung benennt den beanstandeten Pfad und die Abhilfe. Durch Ausführung verifiziert — die Pfade unten sind Dokumentationspfade, eingesetzt anstelle derer aus dem echten Mitschnitt:

# 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

Die vollständige Menge der Ablehnungen:

AbgelehntWeil
Ein Modus, der für Gruppe oder andere lesbar istJedes lokale Konto auf dem Host kann das Token lesen
Einem dritten Konto gehörend„Nur der Eigentümer kann es lesen“ ist nichts wert, wenn der Eigentümer jemand anderes ist
Keine reguläre DateiEin FIFO oder ein Gerät ist keine Geheimnisdatei — und ein einfaches Öffnen eines FIFO ohne Schreiber würde den Agenten beim Start ohne jede Diagnose hängen lassen
Über einen Symlink erreichtDas Ziel des Links liegt in einem Verzeichnis, in das die Prüfung nicht geschaut hätte
Jedes durch die Gruppe oder alle beschreibbare Verzeichnis auf dem Pfad — einschließlich /tmpDieses Konto kann den Teilbaum umbenennen und seine eigene Datei an dessen Stelle setzen

Abgelaufen werden sowohl die Vorgängerverzeichnisse des aufgelösten Pfades als auch der Pfad so, wie er geschrieben steht, und für den aufgelösten Pfad wird über Gerät und Inode belegt, dass es genau die Datei ist, deren Deskriptor gelesen wird. Jedes davon war ein echtes Loch, geschlossen in jeweils einer anderen Welle: nur das unmittelbare Elternverzeichnis zu prüfen; ein für alle beschreibbares Großelternverzeichnis zu übersehen; und dann das Spiegelbild davon — nur die aufgelöste Kette abzulaufen, was eine 0600-Datei in einem 0700-Verzeichnis akzeptierte, das über ein 0777-Verzeichnis erreicht wurde, weil die aufgelöste Kette makellos ist und der Angreifer in der geschriebenen Kette sitzt.

Schließlich stammt jedes Geheimnis aus genau einer Quelle: dem Inline-Wert, einer <field>_file oder einer <field>_env, die eine Umgebungsvariable benennt. Zwei Quellen gleichzeitig sind ein Fehler und keine Vorrangregel — mit einer Vorrangregel kann ein Operator, der token_file hinzufügt und dabei ein veraltetes Inline-Token stehen lässt, nicht erkennen, welches davon über die Leitung geht.

Enrolment: Der Schlüssel verlässt den Host nie​

Sie erzeugen im Cert-IX-Dashboard ein einmalig verwendbares Enrolment-Token und legen es in eine Datei, die nur der Agent lesen kann:

enrollment:
endpoints:
- "https://<your-cert-ix-agent-endpoint>"
enrollment_token_file: "/etc/bitscanner/enrollment.token" # chmod 600, owner-only

Beim ersten Start tut der Agent Folgendes:

  1. Er erzeugt auf dem Host ein ECDSA-P-256-Schlüsselpaar. Die private Hälfte wird mit 0600 in agent.data_dir geschrieben, das auf 0700 gezwungen wird — beim Anlegen und durch ein erneutes chmod, denn MkdirAll lässt den Modus eines bestehenden Verzeichnisses unangetastet, und ein Upgrade in ein 0755-Datenverzeichnis würde ihn sonst beibehalten. Der private Schlüssel verlässt die Maschine nie.
  2. Er registriert sich genau einmal und legt dabei das Enrolment-Token im Header X-Agent-Enrollment-Token und seinen öffentlichen Schlüssel im Body vor.
  3. Er speichert das Agent-JWT, das das Gateway zurückgibt, mit 0600, zusammen mit dessen Ablaufzeitpunkt.

Danach ist das Token verbraucht: Die Datei kann gelöscht werden, und ein Neustart lädt die gespeicherte Identität und registriert sich nicht erneut. Eine zweite Registrierung wäre ein 409 gegen ein bereits verbrauchtes Token, und der Agent bliebe tot. Das JWT wird eine Stunde vor seinem Ablauf am Erneuerungs-Endpunkt erneuert, unter Verwendung des Tokens selbst — ein Enrolment-Token ist daran nicht beteiligt.

Das Enrolment-Token ist kein Bearer-Token

Es wird ausschließlich in X-Agent-Enrollment-Token vorgelegt, bei genau einer Anfrage. Es in auth.token_file zu legen, ist die Fehlkonfiguration, die das Onboarding in einem früheren Build unmöglich machte: Das Ingest-Gateway will ein Agent-JWT, also endet jede Telemetrie-Anfrage mit 401. Verwenden Sie stattdessen auth: {type: enrollment} am Ziel — damit wird die gespeicherte Identität vorgelegt, und es gibt nichts einzufügen.

Der Enrolment-Endpunkt muss unter jeder Konfiguration https sein. security.i_accept_plaintext_egress gilt dafür nicht — über ihn kommt die Identität zurück.

Weder der private Schlüssel noch das Token werden je protokolliert, in eine Fehlermeldung formatiert oder in ein Log-Feld gelegt: Die eigenen Methoden String() und GoString() des Identitätstyps geben nur die Agent-ID und den Ablaufzeitpunkt aus, sodass ein versehentliches %v das JWT dieses Hosts nicht für immer in eine Logdatei schreiben kann.

Ehrliche Grenzen​

Hier benannt, statt später entdeckt.

Die Advisories der Go-Standardbibliothek, die 0.1.2 betrafen, sind in 0.1.3 behoben​

0.1.3-ga ist mit go1.25.12 gebaut, und das bringt die Korrektur für beide Advisories mit, denen 0.1.2-ga (gebaut mit go1.26.4) ausgesetzt war:

AdvisoryCVSSAktiv ausgenutzt?Worum es gehtBehoben in 0.1.3-ga
CVE-2026-39822 (GO-2026-4970)7.8 HochNein — nicht auf der CISA-KEV-Liste, EPSS unterhalb der SchwelleAusbruch aus dem Wurzelverzeichnis über einen Symlink plus abschließenden Schrägstrich in os✅
CVE-2026-42505 (GO-2026-5856)5.3 MittelNein — nicht auf der CISA-KEV-Liste, EPSS unterhalb der SchwellePreisgabe privater Informationen bei Encrypted Client Hello in crypto/tls✅

🪤 Wissenswert, wenn Sie Toolchains selbst festpinnen: Eine höhere Go-Version ist nicht immer die korrigierte. CVE-2026-39822 ist in go1.25.12 auf der 1.25-Linie behoben, auf der 1.26-Linie aber erst in go1.26.5 — go1.26.4, eine numerisch neuere Toolchain, trug sie also weiterhin. Eine einzelne „Mindestversions“-Untergrenze kann einen zweiglinienspezifischen Backport nicht ausdrücken; deshalb pinnt dieses Release ein exaktes Image fest und überlässt die Autorität dem Scanner statt der Versionsnummer. Unser eigener Build wurde genau daran einmal verweigert, bevor das Pinning korrigiert war.

Wenn Sie noch 0.1.2-ga betreiben, trägt es das High-Advisory — aktualisieren Sie. Die Toolchain-Version gibt bitscanner version aus, und sie ist in der CycloneDX-SBOM des Releases festgehalten — prüfen Sie also den Build, der vor Ihnen liegt, statt dieser Tabelle für immer zu vertrauen.

„Zugestellt“ heißt „es kam ein 2xx zurück“​

Der Agent meldet, was er eingereiht hat und was angenommen wurde:

INFO network_collector collection cycle complete
{"queued_for_delivery": ["network_state", "neighbor_table", "neighbor_discovery"]}
INFO telemetry telemetry batch accepted by destination {"status": 200, …}

Ein 2xx ist das Wort des Ziels und kein Beleg dafür, dass der Datensatz gespeichert wurde; deshalb steht dort mit Absicht accepted und nicht delivered. Mehr kann der Agent nicht wissen, und eine Zeile, die etwas anderes behauptete, würde eine Garantie erfinden. Die queued_for_delivery-Zeile wird in jedem Zyklus ausgegeben — auch bei ausgeschaltetem Scannen —, damit ein Zustellfehler niemals mit einem Collector verwechselt werden kann, der gar nicht läuft.

config validate kann ein Enrolment-Token nicht von einem JWT unterscheiden​

Beide sind undurchsichtige Zeichenketten in einer Datei; ein bitscanner config validate auf einer Konfiguration, deren auth.token_file ein Enrolment-Token enthält, meldet daher:

Configuration is valid.

… und jeder Export scheitert anschließend zur Laufzeit. Die Validierung prüft die Berechtigungen der Datei, ihren Eigentümer, jedes Verzeichnis auf dem Weg dorthin und dass genau eine Quelle konfiguriert ist — sie prüft nicht und kann nicht prüfen, ob die Bytes darin die richtige Art von Zugangsdaten sind. Wenn Ihre Telemetrie bei einer frischen Installation mit 401 scheitert, ist das die erste Sache, die Sie nachsehen sollten.

Nächste Schritte​

War diese Seite hilfreich?