Eine 300.000-Einträge-Schwachstellendatenbank, die aktuell bleibt.
Eine Schwachstellendatenbank ist nur nützlich, wenn sie aktuell, präzise ist und der Art entspricht, wie Code tatsächlich ausgeliefert wird. Hier beschreiben wir, wie wir Ingestion, Deduplizierung und Versionsabgleich im Maßstab öffentlicher Bedrohungs-Feeds angegangen sind und was wir gelernt haben, um False Positives unter 4 % zu halten.
Die meisten Schwachstellentools teilen ein gemeinsames Versagen. Sie liefern eine lange Liste von Befunden, von denen die meisten für Ihren Code gar nicht gelten, und einige wenige kritische, die in der Masse untergehen. Das Team gibt die Liste innerhalb einer Woche auf, und ein Jahr später wird etwas davon ausgenutzt.
Die Ursache ist fast immer dieselbe. Die Schwachstellendatenbank ist entweder veraltet, oder zu großzügig abgeglichen, oder zu eng abgeglichen. Jede Variante erzeugt ein anderes Versagen, und alle drei gleichzeitig richtig hinzubekommen, macht einen nutzbaren Scanner aus. Dieser Beitrag fasst die Prinzipien zusammen, mit denen wir das erreicht haben.
Was „Schwachstellendatenbank“ wirklich bedeutet
Eine Schwachstellendatenbank ist nicht ein einzelner Feed. Sie ist die Zusammenführung mehrerer Quellen, jede mit unterschiedlicher Abdeckung, Latenz und Genauigkeit. Die Version in CyberDebunk zieht aus einer Mischung öffentlicher Quellen, interner Kuratierung und von Kunden gemeldeter False Positives. Die Zahlen unten beschreiben die Seite der öffentlichen Quellen.
Der zusammengeführte Datensatz kommt auf 300.000 Einträge. Ungefähr zwei Drittel davon sind Duplikate über Quellen hinweg, dieselbe Advisory leicht unterschiedlich beschrieben. Deduplizierung ist ein eigenes Projekt.
Das Aktualitätsproblem
Bei einer Schwachstellendatenbank hat „aktuell“ eine sehr konkrete Bedeutung. Irgendwo wird eine neue Advisory veröffentlicht, und die Frage ist, wie lange es dauert, bis ein Scan Ihres Codes das widerspiegelt. Alles über wenige Stunden hinaus ist in moderner Abhängigkeitssicherheit inakzeptabel, denn in diesem Fenster erscheint typischerweise Proof-of-Concept-Exploit-Code.
Die einfachste Antwort ist Polling. Jede Quelle hat einen Feed kürzlich geänderter Einträge. Wir ziehen diese in engem Takt, deduplizieren gegen das, was wir bereits haben, und indizieren die Änderungen. Der gesamte Roundtrip von einer Feed-Aktualisierung bis zu einem Datensatz, der gegen Kundencode abgleichbar ist, dauert in der Regel nur wenige Minuten.
Achtundachtzig Stunden.
Das war die mediane Zeit zwischen der öffentlichen Veröffentlichung von CVE-2024-23334 (aiohttp Path Traversal) und den ersten internetweiten Exploit-Versuchen, die wir beobachtet haben. Ein täglicher Scan hätte drei volle Zyklen verpasst. Ein kontinuierlicher hat sie beim ersten erfasst.
Das Abgleichproblem
Eine Schwachstelle zu kennen ist nicht dasselbe wie zu wissen, dass sie auf Ihren Code zutrifft. Eine Advisory sagt etwa Folgendes:
„aiohttp-Versionen>=3.9.0, <3.9.5sind anfällig für Path Traversal, wennfollow_symlinks=truegesetzt ist.“
Den Abgleich mit Ihrem Code korrekt durchzuführen, bedeutet, drei Fragen richtig zu beantworten:
- Verwenden Sie dieses Paket überhaupt? Einfach. Manifest lesen.
- Welche Version ist tatsächlich installiert? Schwerer. Manifeste drücken Bereiche aus. Lockfiles drücken Auflösungen aus. Transitive Abhängigkeiten erscheinen in keinem von beiden.
- Überlappt sich der Versionsbereich in der Advisory mit dem in Ihrem Code? Am schwierigsten. Die Syntax für Versionsbereiche variiert je nach Ökosystem.
^1.2.3bedeutet in npm und Composer sehr Unterschiedliches.
Warum die meisten Scanner das falsch machen
Zwei Fehlermuster dominieren.
Das erste ist Over-Matching. Jede bloße Präsenz des Paketnamens wird als Treffer gewertet, unabhängig von der Version. Das erzeugt eine lange Liste von „Sie könnten betroffen sein“, von der die meisten falsch sind. Der Nutzer lernt, die Liste zu ignorieren.
Das zweite ist Under-Matching. Es wird exakte Versionsgleichheit verlangt, und alles, wo der Bereich locker ausgedrückt ist, geht verloren. Das ergibt eine saubere Liste, aber mit Lücken. Der Nutzer erfährt von den Lücken erst, wenn etwas ausgenutzt wird.
Die Mitte richtig zu treffen, erfordert ökosystem-bewusstes Parsen von Versionsbereichen. Semver für npm. PEP 440 für PyPI. Maven-Syntax für Bereiche. Cargo-Syntax für Bereiche. Wir behandeln das Versionsmodell jedes Ökosystems als gleichwertig, parsen den Bereich aus der Advisory in der Grammatik dieses Ökosystems und berechnen echte Bereichsüberlappung. Nicht „String enthält“, nicht „beginnt mit“.
Das Genauigkeitsbudget
Branchenweit liegen False-Positive-Raten für Dependency-Scanner je nach Messmethode und Korpus zwischen 15 % und 40 %. Die Zahl, an der wir uns messen, liegt unter 4 %. Dieses Ziel treibt die meisten der oben genannten Designentscheidungen.
Konkret messen wir False Positives, indem wir den Scanner gegen einen kuratierten Korpus von Repositories laufen lassen, bei denen die richtige Antwort bekannt ist. Bei jedem Release verschiebt sich der Korpus, und die Zahl wird neu gemessen. Zwei Taktiken sind besonders wichtig:
- Abgleich auf aufgelöste Version, nicht auf deklarierte Version. Ein Manifest, das
"react": "^18.0.0"angibt, sagt wenig aus. Das Lockfile, das zu18.3.1auflöst, sagt genau, was installiert ist. Wir gleichen ab, wo vorhanden, auf der aufgelösten Version ab. - Reachability-bewusste Filterung. Eine Schwachstelle in einer transitiven Abhängigkeit, die nur von einem Codepfad genutzt wird, den Sie nicht aufrufen, ist technisch vorhanden, aber nicht ausnutzbar. Wir nennen das kein False Positive (es ist real), aber wir zeigen es anders an als die Kritischen auf Ihrem Hot Path.
Deduplizierung
Die NVD kann eine Schwachstelle unter einer CVE-ID beschreiben. GHSA kann dasselbe unter einer anderen Kennung beschreiben. OSV kann es unter beiden tun. Würden wir Feeds naiv addieren, würde unsere 300.000-Zählung auf 800.000 anwachsen, und Kunden sähen denselben Befund dreimal.
Der Merge-Key sind die zugrunde liegenden Paketkoordinaten plus der betroffene Versionsbereich, nicht die Kennung. Zwei Datensätze, die dasselbe Paket und denselben Bereich beschreiben, werden zu einem zusammengeführt. Die Kennungen bleiben als Querverweise angehängt, damit ein Auditor nachvollziehen kann.
Was wir nicht lösen
Öffentliche Schwachstellendaten haben bekannte Grenzen, und wir tun nicht so, als würden wir alle beheben.
- Nicht gemeldete Schwachstellen. Per Definition können wir nicht gegen abgleichen, was niemand offengelegt hat. Die Datenbank ist durch das begrenzt, was öffentlich ist.
- Falsch zugeordnete Pakete. Wenn eine Advisory das betroffene Paket falsch benennt oder ein Elternpaket statt des tatsächlich anfälligen Kindpakets nennt, geht unser Abgleich schief. Wir fangen das über kuratierte Overrides ab.
- Konfigurationsabhängige Schwachstellen. Manche Advisories gelten nur, wenn eine bestimmte Option gesetzt ist. Wir zeigen die Konfigurationsanforderung an, prüfen Ihre Laufzeit aber derzeit nicht, ob sie erfüllt ist.
Das sind offene Probleme. Sie sind überall offen.
Die Erkenntnis für alle, die einen Scanner evaluieren
Wenn Sie ein Schwachstellentool evaluieren, sind die drei relevanten Fragen nicht die auf der Marketingseite. Es sind diese:
- Wie schnell erreicht eine neu veröffentlichte Advisory Ihr Dashboard? Gemessen in Stunden, nicht in Tagen.
- Wie hoch ist die False-Positive-Rate gegen einen Korpus, den Sie kontrollieren? Alles über 10 % wird von Ihrem Team innerhalb eines Monats ignoriert.
- Wie werden Versionsbereiche geparst? Wenn die Antwort nicht „pro Ökosystem“ lautet, sind Over-Matching und Under-Matching eingebaut.
Die Mechanik ist nicht glamourös. Aber sie unterscheidet ein Tool, das Teams wirklich nutzen, von einem, das still auf stumm geschaltet wird.
Verbinden Sie ein Repository und sehen Sie den Abgleich in Aktion.
Der erste Scan ist in der Regel innerhalb weniger Minuten abgeschlossen, und die Befunde enthalten die Begründung. Sie sehen genau, welche Advisory getroffen hat, warum und wo die Überlappung der Versionsbereiche liegt.