Kapitel 4. Finden von Subdomains

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Um API-Endpunkte ausfindig zu machen und zu testen, solltest du zunächst mit der Domänenstruktur vertraut sein, die eine Webanwendung verwendet. Heutzutage wird nur noch selten eine einzige Domain verwendet, um eine Webanwendung vollständig zu bedienen. Meistens sind Webanwendungen mindestens in eine Client- und eine Server-Domäne aufgeteilt, plus die bekannte https://www und nicht nur https://. Die Fähigkeit von, iterativ Subdomains zu finden und aufzuzeichnen, die eine Webanwendung bedienen, ist eine nützliche Technik zur ersten Aufklärung gegen diese Webanwendung.

Mehrere Anwendungen pro Bereich

Nehmen wir an,, dass wir versuchen, die Webanwendungen der MegaBank abzubilden, um einen von der Bank gesponserten Blackbox-Penetrationstest besser durchführen zu können. Wir wissen, dass die MegaBank eine App hat, mit der sich die Nutzer einloggen und auf ihre Bankkonten zugreifen können. Diese App befindet sich unter https://www.mega-bank.com.

Wir sind besonders neugierig, ob die MegaBank noch andere Server im Internet hat, die mit dem Domainnamen mega-bank.com verbunden sind. Wir wissen, dass die MegaBank ein Bug-Bounty-Programm hat, und der Geltungsbereich dieses Programms deckt die Hauptdomain mega-bank.com ziemlich umfassend ab. Daher wurden alle leicht zu findenden Sicherheitslücken in mega-bank.com bereits behoben oder gemeldet. Wenn neue Schwachstellen auftauchen, arbeiten wir gegen die Zeit, um sie zu finden, bevor die Bug Bounty-Jäger sie finden.

Deshalb möchten wir nach leichteren Zielen suchen, die es uns trotzdem ermöglichen, die MegaBank dort zu treffen, wo es weh tut. Dies ist ein rein ethischer, vom Unternehmen gesponserter Test, aber das bedeutet nicht, dass wir keinen Spaß haben können.

Als Erstes sollten wir unsere Webanwendung mit einer Liste von Subdomains füllen, die zu mega-bank.com gehören (siehe Abbildung 4-1). Da www auf die öffentliche Webanwendung selbst verweist, ist das für uns wahrscheinlich nicht von Interesse. Die meisten großen Verbraucherunternehmen hosten jedoch eine Vielzahl von Subdomains, die mit ihrer Hauptdomain verbunden sind. Diese Subdomains werden für eine Vielzahl von Diensten genutzt, von E-Mail über Verwaltungsanwendungen bis hin zu Dateiservern und mehr.

subdomains
Abbildung 4-1. Mega-bank.com einfaches Subdomain-Web - oft sind diese Webs deutlich komplexer und können Server enthalten, die von einem externen Netzwerk aus nicht erreichbar sind

Es gibt viele Möglichkeiten, diese Daten zu finden, und oft musst du mehrere ausprobieren, um die gewünschten Ergebnisse zu erhalten. Wir beginnen mit den einfachsten Methoden und arbeiten uns dann nach oben.

Die in den Browser integrierten Tools zur Netzwerkanalyse

Zunächst können wir einige nützliche Daten sammeln, indem wir einfach durch die sichtbaren Funktionen der MegaBank gehen und sehen, welche API-Anfragen im Hintergrund gestellt werden. Dadurch erhalten wir oft ein paar niedrig hängende Endpunkte. Um diese Anfragen zu sehen, während sie gestellt werden, können wir die Netzwerkwerkzeuge unseres Webbrowsers oder ein leistungsfähigeres Tool wie Burp oder ZAP verwenden.

Abbildung 4-2 zeigt ein Beispiel für die Entwickler-Tools des Wikipedia-Browsers, mit denen Netzwerkanfragen angezeigt, verändert, erneut gesendet und aufgezeichnet werden können. Frei verfügbare Netzwerkanalyse-Tools wie dieses sind viel leistungsfähiger als viele kostenpflichtige Netzwerk-Tools von vor 10 Jahren. Da dieses Buch ohne spezialisierte Tools geschrieben ist, werden wir uns vorerst nur auf den Browser verlassen.

Solange du einen der drei großen Browser (Chrome, Firefox oder Edge) verwendest, solltest du feststellen, dass die darin enthaltenen Tools für Entwickler extrem leistungsstark sind. Tatsächlich sind die Browser-Entwickler-Tools so weit fortgeschritten, dass du leicht ein erfahrener Hacker werden kannst, ohne dass du Tools von Drittanbietern kaufen musst. Moderne Browser bieten Tools für die Netzwerkanalyse, die Codeanalyse, die Laufzeitanalyse von JavaScript mit Breakpoints und Dateiverweisen, die genaue Leistungsmessung (die auch als Hacking-Tool für Side-Channel-Angriffe genutzt werden kann) sowie Tools für kleinere Sicherheits- und Kompatibilitätsprüfungen.

wikipedia-devtools-network
Abbildung 4-2. Die Netzwerk-Registerkarte der Entwicklertools des Wikipedia.org-Browsers zeigt eine asynchrone HTTP-Anfrage an die Wikipedia-API

Um den Netzwerkverkehr zu analysieren, der über deinen Browser läuft, gehe wie folgt vor (in Chrome):

  1. Klicke auf die dreifachen Punkte oben rechts in der Navigationsleiste, um das Menü Einstellungen zu öffnen.

  2. Klicke unter "Weitere Werkzeuge" auf "Entwicklertools".

  3. Klicke oben in diesem Menü auf den Reiter "Netzwerk". Wenn sie nicht sichtbar ist, erweitere die Entwicklertools horizontal, bis sie sichtbar ist.

Versuche nun, durch die Seiten einer beliebigen Website zu navigieren, während die Registerkarte Netzwerk geöffnet ist. Beachte, dass neue HTTP-Anfragen zusammen mit einer Reihe anderer Anfragen auftauchen (siehe Abbildung 4-3).

reddit
Abbildung 4-3. Registerkarte "Netzwerk" zur Analyse des Netzwerkverkehrs, der zu und von deinem Webbrowser fließt

Du kannst die Registerkarte Netzwerk im Browser verwenden, um den gesamten Netzwerkverkehr zu sehen, den der Browser verarbeitet. Bei einer größeren Website kann es ziemlich einschüchternd sein, das zu filtern.

Die interessantesten Ergebnisse liefert oft die Registerkarte XHR unter der Registerkarte Netzwerk, die dir alle HTTP POST-, GET-, PUT-, DELETE- und andere Anfragen an einen Server anzeigt und Schriftarten, Bilder, Videos und Abhängigkeitsdateien herausfiltert. Du kannst jede einzelne Anfrage im linken Bereich anklicken, um weitere Details zu sehen.

Wenn du auf eine dieser Anfragen klickst, werden die Roh- und die formatierte Version der Anfrage angezeigt, einschließlich der Kopfzeilen und des Textes der Anfrage. Auf der Registerkarte Vorschau, die erscheint, wenn eine Anfrage ausgewählt wird, kannst du eine hübsch formatierte Version des Ergebnisses jeder API-Anfrage sehen.

Die Registerkarte "Antwort" unter XHR zeigt dir eine rohe Antwort-Payload und die Registerkarte "Timing" zeigt dir ganz bestimmte Metriken zu den Warteschlangen-, Download- und Wartezeiten im Zusammenhang mit einer Anfrage. Diese Leistungskennzahlen sind sehr wichtig, da sie zum Aufspüren von Side-Channel-Angriffen genutzt werden können (ein Angriff, der sich auf eine andere sekundäre Kennzahl als die Antwort stützt, um festzustellen, welcher Code auf einem Server läuft; zum Beispiel die Ladezeit zwischen zwei Skripten auf einem Server, die beide über denselben Endpunkt aufgerufen werden).

Inzwischen solltest du mit der Registerkarte "Netzwerk" im Browser so vertraut sein, dass du sie zur Aufklärung nutzen kannst. Die Werkzeuge sind zwar einschüchternd, aber eigentlich ist es gar nicht so schwer, sie zu lernen.

Wenn du durch eine Website navigierst, kannst du die Anfrage → Kopfzeilen → Allgemein → Anfrage-URL überprüfen, um zu sehen, an welche Domain eine Anfrage gesendet oder von welcher eine Antwort gesendet wurde. Oft reicht das schon aus, um die zugehörigen Server einer primären Website zu finden.

Die Vorteile öffentlicher Aufzeichnungen nutzen

Heutzutage ist die Menge an öffentlich zugänglichen Informationen, die im Internet gespeichert sind, so groß, dass ein versehentliches Datenleck jahrelang unbemerkt durch die Maschen schlüpfen kann. Ein guter Hacker kann diese Tatsache ausnutzen und viele interessante Informationen finden, die später zu einem einfachen Angriff führen können.

Einige Daten, die ich im Internet gefunden habe, als ich in der Vergangenheit Penetrationstests durchgeführt habe, sind:

  • Zwischengespeicherte Kopien von GitHub-Repos, die versehentlich öffentlich gemacht wurden, bevor sie wieder privat gemacht wurden

  • SSH-Schlüssel

  • Verschiedene Schlüssel für Dienste wie Amazon AWS oder Stripe, die in regelmäßigen Abständen offengelegt und dann aus einer öffentlich zugänglichen Webanwendung entfernt wurden

  • DNS-Listen und URLs, die nicht für ein öffentliches Publikum bestimmt waren

  • Seiten mit unveröffentlichten Produkten, die nicht für den Live-Betrieb vorgesehen waren

  • Finanzdaten, die im Internet gehostet werden, aber nicht von einer Suchmaschine gecrawlt werden sollen

  • E-Mail-Adressen, Telefonnummern und Benutzernamen

Diese Informationen kannst du an vielen Stellen finden, zum Beispiel bei:

  • Suchmaschinen

  • Beiträge in den sozialen Medien

  • Archivierungsanwendungen, wie archive.org

  • Bildsuche und umgekehrte Bildsuche

Wenn du versuchst, Subdomains zu finden, können auch öffentliche Aufzeichnungen eine gute Informationsquelle sein, denn Subdomains sind vielleicht nicht leicht über ein Wörterbuch zu finden, aber sie könnten in einem der zuvor genannten Dienste indexiert sein.

Caches der Suchmaschine

Google ist die am häufigsten genutzte Suchmaschine der Welt und es wird oft angenommen, dass sie mehr Daten als jede andere Suchmaschine indexiert hat. Die Google-Suche an sich ist für die manuelle Aufklärung nicht geeignet, da man eine riesige Menge an Daten durchsuchen muss, um etwas Wertvolles zu finden. Hinzu kommt, dass Google gegen automatisierte Suchanfragen vorgeht und Anfragen ablehnt, die nicht genau wie ein echter Webbrowser aussehen.

Zum Glück bietet Google spezielle Suchoperatoren für Power-Sucher an, mit denen du die Spezifität deiner Suchanfrage erhöhen kannst. Mit dem Operator site:<my-site> können wir Google bitten, nur nach einer bestimmten Domain zu suchen:

site:mega-bank.com log in

Wenn du dies bei einer beliebten Website machst, wirst du normalerweise seitenweise Inhalte von der Hauptdomain finden und nur sehr wenige Inhalte von den interessanten Subdomains. Du musst deine Suche weiter eingrenzen, um interessante Inhalte zu finden.

Mit kannst du den Minus-Operator verwenden, um bestimmte negative Bedingungen an eine beliebige Abfragekette anzuhängen. Mit -inurl:<pattern> werden zum Beispiel alle URLs abgelehnt, die mit dem angegebenen Muster übereinstimmen. Abbildung 4-4 zeigt ein Beispiel für eine Suche, die die Google-Suchoperatoren site: und -inurl:<pattern> kombiniert. Durch die Kombination dieser beiden Operatoren können wir Google bitten, nur Wikipedia.org-Webseiten zu finden, die sich mit Welpen beschäftigen, und alle Seiten auszulassen, die das Wort "Hund" in ihrer URL enthalten. Diese Technik kann verwendet werden, um die Anzahl der Suchergebnisse zu reduzieren und um bestimmte Subdomains zu durchsuchen, während bestimmte Keywords ignoriert werden. Wenn du die Suchoperatoren von Google und anderen Suchmaschinen beherrschst, kannst du Informationen finden, die du sonst nicht so leicht findest.

Wir können den Operator -inurl:<pattern> verwenden, um Ergebnisse für die uns bereits bekannten Subdomains wie www zu entfernen. Beachte, dass er auch Instanzen von www aus anderen Teilen einer URL herausfiltert, da er nicht die Subdomain, sondern den gesamten URL-String angibt. Das bedeutet, dass auch https://admin.mega-bank.com/www herausgefiltert wird, was bedeutet, dass es zu falsch-positiven Entfernungen kommen kann:

site:mega-bank.com -inurl:www

Du kannst dies für viele Websites ausprobieren und wirst Subdomains finden, von denen du nicht einmal gedacht hättest, dass sie existieren. Probieren wir es zum Beispiel mit der beliebten Nachrichtenseite Reddit:

site:reddit.com -inurl:www

Das erste Ergebnis dieser Abfrage ist code.reddit.com - einArchiv mit Code, der in den frühen Versionen von Reddit verwendet wurde und den die Mitarbeiter/innen beschlossen haben, der Öffentlichkeit zugänglich zu machen. Websites wie Reddit stellen diese Domains absichtlich der Öffentlichkeit zur Verfügung.

google-puppies
Abbildung 4-4. Eine Google.com-Suche, die die Google-Suchoperatoren site: und-inurl:<pattern>

Wenn wir bei unserem Pen-Test gegen die MegaBank weitere Domains finden, die absichtlich offengelegt werden und für uns nicht von Interesse sind, filtern wir sie einfach ebenfalls heraus. Wenn die MegaBank eine mobile Version unter der Subdomain mobile.mega-bank.com gehostet hat, können wir auch diese einfach herausfiltern:

site:mega-bank.com -inurl:www -inurl:mobile

Wenn du versuchst, Subdomains für eine bestimmte Website zu finden, kannst du diesen Vorgang wiederholen, bis du keine relevanten Ergebnisse mehr findest. Es kann auch von Vorteil sein, diese Techniken mit anderen Suchmaschinen wie Bing auszuprobieren - die großen Suchmaschinen unterstützen alle ähnliche Operatoren.

Zeichne alles auf, was du mit dieser Methode herausgefunden hast, und gehe dann zu anderen Methoden der Subdomain-Erkundung über.

Zufällige Archive

Öffentlich Archivierungsprogramme wie Archive.org sind nützlich, weil sie regelmäßig Schnappschüsse von Websites erstellen und es dir ermöglichen, eine Kopie einer Website aus der Vergangenheit zu besuchen. Archive.org bemüht sich, die Geschichte des Internets zu bewahren, da viele Websites sterben und neue Websites ihre Domains übernehmen. Da Archive.org historische Schnappschüsse von Websites speichert, die manchmal 20 Jahre zurückliegen, ist die Website eine Goldgrube für die Suche nach Informationen, die einst (absichtlich oder versehentlich) veröffentlicht, aber später entfernt wurden. Der Screenshot in Abbildung 4-5 zeigt die Startseite von Wikipedia.org, die 2003 indexiert wurde - vor über zwei Jahrzehnten!

wikipedia-archive-2003
Abbildung 4-5. Archive.org, eine gemeinnützige Organisation mit Sitz in San Francisco, gibt es seit 1996

Im Allgemeinen indexieren Suchmaschinen die Daten einer Website, versuchen aber, diese Website regelmäßig zu crawlen, um ihren Cache auf dem neuesten Stand zu halten. Das bedeutet, dass du in einer Suchmaschine nach relevanten aktuellen Daten suchen solltest, aber für relevante historische Daten solltest du vielleicht besser in einem Website-Archiv nachsehen.

Die New York Times ist gemessen an den Besucherzahlen eines der beliebtesten webbasierten Medienunternehmen. Wenn wir ihre Hauptwebseite auf Archive.org(https://www.nytimes.com) nachschlagen, werden wir feststellen, dass Archive.org zwischen 1996 und heute über 500.000 Schnappschüsse der Titelseite gespeichert hat.

Historische Snapshots sind besonders wertvoll, wenn wir den Zeitpunkt kennen oder erahnen können, an dem eine Webanwendung ein größeres Release veröffentlicht oder eine schwerwiegende Sicherheitslücke aufgedeckt wurde. Bei der Suche nach Subdomains geben historische Archive diese oft über Hyperlinks preis, die einst durch HTML oder JS offengelegt wurden, aber in der Live-Anwendung nicht mehr sichtbar sind.

Wenn wir in unserem Browser mit der rechten Maustaste auf einen Archive.org-Snapshot klicken und "Quelle anzeigen" wählen, können wir eine schnelle Suche nach gängigen URL-Mustern durchführen. Eine Suche nach file:// könnte einen früheren Live-Download anzeigen, während eine Suche nach https:// oder http:// alle HTTP-Hyperlinks anzeigen sollte.

Mit diesen einfachen Schritten können wir die Erkennung von Subdomains aus einem Archiv automatisieren:

  1. Öffne 10 Archive von 10 verschiedenen Daten mit viel Zeit dazwischen.

  2. Klicke mit der rechten Maustaste auf "Quelle anzeigen" und drücke dann Strg-A, um den gesamten HTML-Code zu markieren.

  3. Drücke Strg-C, um den HTML-Code in deine Zwischenablage zu kopieren.

  4. Erstelle auf deinem Desktop eine Datei namens legacy-source.html.

  5. Drücke Strg-V, um den Quellcode aus einem Archiv in die Datei einzufügen.

  6. Wiederhole dies für jedes der neun anderen Archive, die du geöffnet hast.

  7. Öffne diese Datei in deinem bevorzugten Texteditor (VIM, Atom, VSCode, etc.).

  8. Führe eine Suche nach den gängigsten URL-Schemata durch:

    • http://

    • https://

    • Datei://.

    • ftp://

    • ftps://

Eine vollständige Liste der von den Browsern unterstützten URL-Schemata findest du im Spezifikationsdokument, das von allen wichtigen Browsern verwendet wird, um zu definieren, welche Schemata unterstützt werden sollen.

Soziale Schnappschüsse

Jede große Social-Media-Website verdient heute ihr Geld mit dem Verkauf von Nutzerdaten. Je nach Plattform kann dies öffentliche Posts, private Posts und in manchen Fällen sogar Direktnachrichten umfassen.

Leider geben sich die großen Social-Media-Unternehmen heutzutage große Mühe, die Nutzer/innen davon zu überzeugen, dass ihre privatesten Daten sicher sind. Dies geschieht oft durch Marketing-Botschaften, in denen beschrieben wird, wie sehr sie sich bemühen, die Daten ihrer Kunden außer Reichweite zu halten. Allerdings wird dies oft nur gesagt, um aktive Nutzer/innen zu gewinnen und zu halten. In den wenigsten Ländern sind die Gesetze und Gesetzgeber modern genug, um die Legitimität dieser Behauptungen durchzusetzen. Es ist wahrscheinlich, dass viele Nutzerinnen und Nutzer dieser Websites nicht genau wissen, welche Daten weitergegeben werden, mit welchen Methoden sie weitergegeben werden und für welche Zwecke diese Daten genutzt werden.

Das Auffinden von Subdomains für einen vom Unternehmen gesponserten Pen-Test über Social-Media-Daten würde von den meisten nicht als unethisch angesehen werden. Ich bitte dich jedoch inständig, an den Endnutzer zu denken, wenn du diese APIs in Zukunft für eine gezieltere Aufklärung nutzt.

Der Einfachheit halber werden wir uns die Twitter-API als Beispiel ansehen. Bedenke jedoch, dass jedes große Social Media-Unternehmen eine ähnliche Reihe von APIs anbietet, die in der Regel einer ähnlichen API-Struktur folgen. Die Konzepte, die zum Abfragen und Durchsuchen von Tweet-Daten über die Twitter-API erforderlich sind, lassen sich auf jedes andere große soziale Netzwerk anwenden.

Twitter API

X, das Unternehmen, das früher unter dem Namen Twitter bekannt war, hat eine Reihe von Angeboten zum Suchen und Filtern seiner Daten (siehe Abbildung 4-6).

Diese Angebote unterscheiden sich in Umfang, Funktionsumfang und Datenmenge. Das heißt, je mehr Daten du abrufen und filtern möchtest, desto mehr musst du bezahlen. In einigen Fällen kann die Suche sogar auf den Servern von X statt lokal durchgeführt werden. Denke daran, dass dies für böswillige Zwecke wahrscheinlich gegen die ToS von X verstößt, daher sollte diese Nutzung nur White Hat vorbehalten sein.

Auf der untersten Ebene bietet X eine Test-"Such-API" an, mit der du die Tweets von 30 Tagen durchsuchen kannst, sofern du nicht mehr als 100 Tweets pro Abfrage abrufst und nicht mehr als 30 Abfragen pro Minute tätigst. Bei der kostenlosen API ist die Anzahl deiner monatlichen Abfragen auf 250 begrenzt. Um den maximalen monatlichen Datensatz zu erhalten, der auf dieser Stufe angeboten wird, brauchst du etwa 10 Minuten für eine Abfrage. Das bedeutet, dass du nur 25.000 Tweets analysieren kannst, ohne für eine erweiterte Mitgliedschaft zu bezahlen.

twitter-api
Abbildung 4-6. Die API-Entwicklerdokumentation von X gibt dir die Möglichkeit, Benutzerdaten schnell zu durchsuchen und zu filtern

Diese Einschränkungen können die Programmierung von Tools zur Analyse der API erschweren. Wenn du X für ein von der Arbeit gefördertes Projekt benötigst, solltest du ein Upgrade in Betracht ziehen oder dich nach anderen Datenquellen umsehen.

Wir können diese API nutzen, um ein JSON zu erstellen, das Links zu *.mega-bank.com enthält, um unsere Subdomain-Aufklärung voranzutreiben. Um die X-Search-API abzufragen, brauchst du Folgendes:

  • Ein registriertes Entwicklerkonto

  • Eine registrierte App

  • Ein Inhaber-Token, den du in deine Anfragen einfügst, um dich zu authentifizieren

Die Abfrage dieser API ist recht einfach, obwohl die Dokumentation verstreut und aufgrund fehlender Beispiele manchmal schwer zu verstehen ist:

curl --request POST \
  --url https://api.twitter.com/1.1/tweets/search/30day/Prod.json \
  --header 'authorization: Bearer <MY_TOKEN>' \
  --header 'content-type: application/json' \
  --data '{
           "maxResults": "100",
           "keyword": "mega-bank.com"
           }'

Standardmäßig führt diese API eine unscharfe Suche nach Schlüsselwörtern durch. Für exakte Übereinstimmungen musst du sicherstellen, dass die übermittelte Zeichenkette selbst in doppelte Anführungszeichen eingeschlossen ist. Doppelte Anführungszeichen können über gültiges JSON in der folgenden Form gesendet werden: "keyword": "\"mega-bank.com\"".

Die Aufzeichnung der Ergebnisse dieser API und die Suche nach Links kann zur Entdeckung von bisher unbekannten Subdomains führen. Diese stammen in der Regel von Marketingkampagnen, Werbetrackern und sogar von Einstellungsereignissen, die mit einem anderen Server verbunden sind als die Haupt-App.

Ein Beispiel aus der Praxis: Versuche, eine Abfrage zu erstellen, die Tweets über Microsoft abfragt. Nachdem du genügend Tweets gesichtet hast, wirst du feststellen, dass Microsoft eine Reihe von Subdomains hat, die es aktiv auf X bewirbt, darunter:

  • careers.microsoft.com (eine Website zur Stellenausschreibung)

  • office.microsoft.com (die Heimat von Microsoft Office)

  • powerbi.microsoft.com (die Heimat des Produkts PowerBI)

  • support.microsoft.com (Microsoft Kundensupport)

Wenn ein Tweet populär genug ist, werden die großen Suchmaschinen ihn indizieren. Die Analyse der Twitter-API ist also sinnvoller, wenn du nach weniger populären Tweets suchst. Sehr populäre virale Tweets werden aufgrund der vielen eingehenden Links von Suchmaschinen indexiert. Das bedeutet, dass es manchmal effektiver ist, einfach eine Suchmaschine mit den richtigen Operatoren zu befragen, wie bereits in diesem Kapitel beschrieben.

Sollten die Ergebnisse dieser API für dein Aufklärungsprojekt nicht ausreichen, bietet X noch zwei weitere APIs: Streaming und Firehose.

Die Streaming-API von X bietet einen Live-Stream aktueller Tweets, die in Echtzeit analysiert werden können. Allerdings bietet diese API nur einen sehr kleinen Prozentsatz der tatsächlichen Live-Tweets, da das Volumen zu groß ist, um es zu verarbeiten und in Echtzeit an einen Entwickler zu senden. Das bedeutet, dass du zu einem bestimmten Zeitpunkt mehr als 99 % der Tweets verpassen könntest. Wenn eine App, die du recherchierst, im Trend liegt oder sehr beliebt ist, könnte diese API von Vorteil sein. Wenn du für ein Startup recherchierst, wird dir diese API nicht viel nützen.

Die Firehose-API von X funktioniert ähnlich wie die Streaming-API, garantiert aber die Lieferung von 100 % der Tweets, die einem von dir angegebenen Kriterium entsprechen. Das ist in der Regel vielwertvoller als die Streaming-API für die Aufklärung, da wir in den meisten Situationen Relevanz der Quantität vorziehen.

Wenn du X als Aufklärungsinstrument einsetzt, solltest du diese Regeln beachten:

  • Für die meisten Webanwendungen liefert dir die Abfrage der Such-API die relevantesten Daten für die Aufklärung.

  • Groß angelegte Apps oder Apps, die im Trend liegen, können nützliche Informationen in den Firehose- oder Streaming-APIs enthalten.

  • Wenn historische Informationen für deine Situation akzeptabel sind, solltest du in Erwägung ziehen, einen großen historischen Daten-Dump von Tweets herunterzuladen und stattdessen eine lokale Abfrage durchzuführen.

Erinnere dich daran, dass fast alle großen Social-Media-Websites Daten-APIs anbieten, die für den Abgleich oder andere Formen der Analyse genutzt werden können. Wenn eine Seite nicht die gewünschten Ergebnisse liefert, dann vielleicht eine andere.

Zonenübertragungsangriffe

Eine öffentliche Webanwendung zu durchforsten und die Netzwerkanfragen zu analysieren, bringt uns nur bedingt weiter. Wir wollen auch die Subdomains finden, die mit MegaBank verbunden sind und die in keiner Weise mit der öffentlichen Webanwendung verknüpft sind.

Ein Zonentransfer-Angriff ist eine Art Aufklärungs-Trick, der gegen falsch konfigurierte Domain Name System (DNS)-Server funktioniert. Es handelt sich dabei nicht wirklich um einen "Hack", auch wenn der Name es vermuten ließe. Stattdessen handelt es sich um eine Technik zum Sammeln von Informationen, die wenig Aufwand erfordert und uns wertvolle Informationen liefern kann, wenn sie erfolgreich ist. Im Kern ist ein DNS-Zonentransfer-Angriff eine speziell formatierte Anfrage im Namen einer Person, die wie eine gültige DNS-Zonentransfer-Anfrage von einem gültigen DNS-Server aussehen soll.

DNS-Server sind dafür zuständig, von Menschen lesbare Domänennamen (z. B. https://mega-bank.com) in maschinenlesbare IP-Adressen (z. B. 195.250.100.195) zu übersetzen, die hierarchisch aufgebaut und nach einem gemeinsamen Muster gespeichert sind, damit sie leicht angefordert und durchsucht werden können. DNS-Server sind wertvoll, weil sie es ermöglichen, die IP-Adresse eines Servers zu ändern, ohne dass die Benutzer der Anwendung auf diesem Server aktualisiert werden müssen. Mit anderen Worten: Ein Nutzer kann https://www.mega-bank.com immer wieder besuchen, ohne sich Gedanken darüber zu machen, zu welchem Server die Anfrage aufgelöst wird.

Das DNS-System ist sehr abhängig von seiner Fähigkeit, DNS-Datensatz-Updates mit anderen DNS-Servern zu synchronisieren. DNS-Zonentransfers sind eine standardisierte Methode, mit der DNS-Server DNS-Datensätze austauschen können. Die Datensätze werden in einem textbasierten Format ausgetauscht, das als Zonendatei bekannt ist.

Zonendateien enthalten oft DNS-Konfigurationsdaten, die nicht leicht zugänglich sein sollen. Daher sollte ein richtig konfigurierter DNS-Primärserver nur in der Lage sein, Zonentransferanfragen aufzulösen, die von einem anderen autorisierten DNS-Sekundärserver angefordert werden. Wenn ein DNS-Server nicht richtig konfiguriert ist, um nur Anfragen für andere, speziell definierte DNS-Server aufzulösen, ist er anfällig für böswillige Akteure.

Zusammenfassend lässt sich sagen, dass wir, wenn wir einen Zonentransfer-Angriff gegen MegaBank durchführen wollen, so tun müssen, als wären wir ein DNS-Server und eine DNS-Zonendatei anfordern, als ob wir sie bräuchten, um unsere eigenen Einträge zu aktualisieren. Zuerst müssen wir die DNS-Server finden, die mit https://www.mega-bank.com verbunden sind . Das können wir auf jedem Unix-basierten System ganz einfach über das Terminal erledigen:

host -t mega-bank.com

Der Befehl host bezieht sich auf ein DNS-Lookup-Dienstprogramm, das du in den meisten Linux-Distributionen sowie in neueren Versionen von macOS finden kannst. Das Flag -t gibt an, dass wir die Nameserver abfragen wollen, die für die Auflösung von mega-bank.com verantwortlich sind.

Die Ausgabe dieses Befehls würde etwa so aussehen:

mega-bank.com name server ns1.bankhost.com
mega-bank.com name server ns2.bankhost.com

Die Strings, die uns an diesem Ergebnis interessieren, sind ns1.bankhost.com und ns2.bankhost.com. Diese beziehen sich auf die beiden Nameserver, die für mega-bank.com auflösen.

Der Versuch, mit host einen Zonentransfer anzufordern, ist sehr einfach und sollte nur eine Zeile umfassen:

host -l mega-bank.com ns1.bankhost.com

Hier zeigt das -l Flag an, dass wir eine Zonentransferdatei für mega-bank.com von ns1.bankhost.com abrufen wollen, um unsere Einträge zu aktualisieren. Wenn die Anfrage erfolgreich ist, was auf einen nicht ordnungsgemäß gesicherten DNS-Server hinweist, würdest du ein Ergebnis wie dieses sehen:

Using domain server:
Name: ns1.bankhost.com
Address: 195.11.100.25
Aliases:

mega-bank.com has address 195.250.100.195
mega-bank.com name server ns1.bankhost.com
mega-bank.com name server ns2.bankhost.com
mail.mega-bank.com has address 82.31.105.140
admin.mega-bank.com has address 32.45.105.144
internal.mega-bank.com has address 25.44.105.144

Mit diesen Ergebnissen hast du nun eine Liste der anderen Webanwendungen, die unter der Domain mega-bank.com gehostet werden, sowie deren öffentliche IP-Adressen!

Du könntest sogar versuchen, zu diesen Subdomains oder IP-Adressen zu navigieren, um zu sehen, was sich auflöst. Mit ein bisschen Glück hast du deine Angriffsfläche erheblich vergrößert!

Leider laufen Angriffe auf DNS-Zonentransfers nicht immer so ab wie im vorangegangenen Beispiel. Ein richtig konfigurierter Server gibt eine andere Ausgabe, wenn du einen Zonentransfer anforderst:

Using domain server:
Name: ns1.secure-bank.com
Address: 141.122.34.45
Aliases:

: Transfer Failed.

Der Zonentransfer-Angriff ist leicht zu stoppen, und du wirst feststellen, dass viele Anwendungen so konfiguriert sind, dass sie diese Versuche abwehren. Da der Versuch eines Zonentransfer-Angriffs jedoch nur ein paar Zeilen Bash erfordert, lohnt es sich fast immer, ihn zu versuchen. Wenn er erfolgreich ist, erhältst du eine Reihe interessanter Subdomänen, die du sonst vielleicht nicht gefunden hättest.

Brute Forcing von Subdomains

Da eine letzte Maßnahme zur Aufdeckung von Subdomains ist, können Brute-Force-Taktiken eingesetzt werden. Diese können bei Webanwendungen mit wenigen Sicherheitsmechanismen wirksam sein; bei etablierten und sicheren Webanwendungen müssen wir unsere Brute-Force-Methode jedoch sehr intelligent strukturieren. Das Brute-Force-Verfahren für Subdomains sollte unser letzter Ausweg sein, da Brute-Force-Versuche leicht protokolliert werden und aufgrund von Ratenbeschränkungen, Regex und anderen einfachen Sicherheitsmechanismen, die entwickelt wurden, um diese Art von Schnüffelei zu verhindern, oft sehr zeitaufwändig sind.

Warnung

Brute-Force-Angriffe sind sehr leicht zu erkennen und können dazu führen, dass deine IP-Adressen vom Server oder seinem Administrator protokolliert oder gesperrt werden.

Brute-Forcing bedeutet, dass wir jede mögliche Kombination von Subdomänen testen, bis wir eine Übereinstimmung finden. Bei Subdomänen kann es viele mögliche Übereinstimmungen geben, so dass es möglicherweise nicht ausreicht, bei der ersten Übereinstimmung aufzuhören.

Zunächst sollten wir bedenken, dass im Gegensatz zu einer lokalen Brute-Force-Methode eine Brute-Force-Methode von Subdomänen gegen eine Zieldomäne eine Netzwerkverbindung erfordert. Da wir dieses Brute-Force-Verfahren aus der Ferne durchführen müssen, werden unsere Versuche durch die Netzwerklatenz weiter verlangsamt. Im Allgemeinen kannst du mit einer Latenzzeit zwischen 50 und 250 ms pro Netzwerkanfrage rechnen.

Das bedeutet, dass wir unsere Anfragen asynchron stellen und sie alle so schnell wie möglich abfeuern sollten, anstatt auf die vorherige Antwort zu warten. Auf diese Weise wird die Zeit, die wir für unsere Brute-Force-Analyse benötigen, drastisch reduziert.

Die Rückkopplungsschleife, die für die Erkennung einer aktiven Subdomain erforderlich ist, ist recht einfach. Unser Brute-Force-Algorithmus generiert eine Subdomain, und wir senden eine Anfrage an <subdomain-guess>.mega-bank.com. Wenn wir eine Antwort erhalten, markieren wir sie als aktive Subdomain. Andernfalls markieren wir sie als unbenutzte Subdomain.

Da das Buch, das du liest, den Titel Web Application Security trägt, ist die wichtigste Sprache, mit der wir in diesem Zusammenhang vertraut sein müssen, JavaScript. JavaScript ist nicht nur die einzige Programmiersprache, die derzeit für die clientseitige Skripterstellung im Webbrowser zur Verfügung steht, sondern dank Node.js und der Open-Source-Community auch eine extrem leistungsfähige serverseitige Backend-Sprache.

Lass uns einen Brute-Force-Algorithmus in zwei Schritten mit JavaScript aufbauen. Unser Skript sollte Folgendes tun:

  1. Erstelle eine Liste möglicher Subdomains.

  2. Gehe die Liste der Subdomains durch und pinge jedes Mal an, um festzustellen, ob eine Subdomain aktiv ist.

  3. Zeichne die aktiven Subdomänen auf und mach nichts mit den ungenutzten Subdomänen.

Wir können Subdomains wie folgt erstellen:

/*
 * A simple function for brute forcing a list of subdomains
 * given a maximum length of each subdomain.
 */
const generateSubdomains = function(length) {

    /*
     * A list of characters from which to generate subdomains.
     *
     * This can be altered to include less common characters
     * like '-'.
     *
     * Chinese, Arabic, and Latin characters are also
     * supported by some browsers.
     */
    const charset = 'abcdefghijklmnopqrstuvwxyz'.split('');
    let subdomains = charset;
    let subdomain;
    let letter;
    let temp;

    /*
     * Time Complexity: o(n*m)
     * n = length of string
     * m = number of valid characters
     */
    for (let i = 1; i < length; i++) {
        temp = [];
        for (let k = 0; k < subdomains.length; k++) {
          subdomain = subdomains[k];
          for (let m = 0; m < charset.length; m++) {
            letter = charset[m];
            temp.push(subdomain + letter);
          }
        }
        subdomains = temp
    }

    return subdomains;
}

const subdomains = generateSubdomains(4);

Dieses Skript generiert jede mögliche Kombination von Zeichen der Länge n, wobei die Liste der Zeichen, aus denen die Subdomains zusammengesetzt werden, charset lautet. Der Algorithmus funktioniert, indem er die Zeichenfolge charset in ein Array von Zeichen aufteilt und dann die anfängliche Menge von Zeichen diesem Array von Zeichen zuweist.

Als Nächstes iterieren wir für die Dauer length und erstellen bei jeder Iteration ein Array für die temporäre Speicherung. Dann iterieren wir für jede Subdomäne und jedes Zeichen im Array charset, das unseren verfügbaren Zeichensatz angibt. Schließlich bauen wir das temporäre Array mit Kombinationen aus bestehenden Subdomains und Buchstaben auf.

Mit dieser Liste von Subdomains können wir nun eine Top-Level-Domain (.com, .org, .net, etc.) wie mega-bank.com abfragen. Dazu schreiben wir ein kurzes Skript, das die DNS-Bibliothek von Node.js-einer beliebten JavaScript-Laufzeitumgebung - nutzt.

Um dieses Skript auszuführen, brauchst du nur eine aktuelle Version von Node.js in deiner Umgebung (sofern es sich um eine Unix-basierte Umgebung wie Linux oder Ubuntu handelt):

const dns = require('dns');
const promises = [];

/*
 * This list can be filled with the previous brute force
 * script, or use a dictionary of common subdomains.
 */
const subdomains = [];

/*
 * Iterate through each subdomain, and perform an asynchronous
 * DNS query against each subdomain.
 *
 * This is much more performant than the more common `dns.lookup()`
 * because `dns.lookup()` appears asynchronous from the JavaScript,
 * but relies on the operating system's getaddrinfo(3) which is
 * implemented synchronously.
 */
subdomains.forEach((subdomain) => {
  promises.push(new Promise((resolve, reject) => {
    dns.resolve(`${subdomain}.mega-bank.com`, function (err, ip) {
      return resolve({ subdomain: subdomain, ip: ip });
    });
  }));
});

// after all of the DNS queries have completed, log the results
Promise.all(promises).then(function(results) {
  results.forEach((result) => {
    if (!!result.ip) {
      console.log(result);
    }
  });
});

In diesem Skript tun wir mehrere Dinge, um die Übersichtlichkeit und Leistung des Brute-Forcing-Codes zu verbessern. Zunächst importieren wir die Node DNS-Bibliothek. Dann erstellen wir ein Array promises, das eine Liste von promise Objekten speichert. Promises ist eine viel einfachere Art, mit asynchronen Anfragen in JavaScript umzugehen, und wird von jedem größeren Webbrowser und Node.js unterstützt.

Danach erstellen wir ein weiteres Array mit dem Namen subdomains, das mit den Subdomains aus unserem ersten Skript gefüllt werden sollte (wir werden die beiden Skripte am Ende dieses Abschnitts miteinander kombinieren). Als Nächstes verwenden wir den forEach() Operator, um jede Subdomäne im subdomains Array zu durchlaufen. Dies entspricht einer for Iteration, ist aber syntaktisch eleganter.

Auf jeder Ebene der Subdomain-Iteration schieben wir ein neues promise Objekt in das promises Array. In diesem promise Objekt rufen wir dns.resolve auf, eine Funktion der Node.js DNS-Bibliothek, die versucht, einen Domainnamen in eine IP-Adresse aufzulösen. Die promises, die wir in das Array promise verschieben, werden erst aufgelöst, wenn die DNS-Bibliothek ihre Netzwerkanfrage beendet hat.

Schließlich nimmt der Promise.all Block ein Array von promise Objekten und liefert nur dann ein Ergebnis (ruft .then() auf), wenn jedes promise im Array aufgelöst wurde (seine Netzwerkanfrage abgeschlossen hat). Der doppelte !! Operator im Ergebnis gibt an, dass wir nur Ergebnisse wollen, die definiert zurückkommen, also sollten wir Versuche ignorieren, die keine IP-Adresse zurückgeben.

Wenn wir eine Bedingung einfügen würden, die reject() aufruft, bräuchten wir auch einen catch() Block am Ende, um Fehler zu behandeln. Die DNS-Bibliothek gibt eine Reihe von Fehlern aus, für die es sich vielleicht nicht lohnt, unsere Brute Force zu unterbrechen. Wir haben das Beispiel der Einfachheit halber weggelassen, aber es wäre eine gute Übung, wenn du das Beispiel weiter ausführen möchtest.

Außerdem verwenden wir dns.resolve im Gegensatz zu dns.lookup, denn obwohl die JavaScript-Implementierung von beiden asynchron auflöst (unabhängig von der Reihenfolge, in der sie ausgelöst wurden), basiert die native Implementierung, auf die sich dns.lookup stützt, auf libuv, das die Operationen synchron ausführt.

Wir können die beiden Skripte ganz einfach in einem Programm kombinieren. Zuerst generieren wir unsere Liste potenzieller Subdomains und führen dann unseren asynchronen Brute-Force-Versuch zur Auflösung der Subdomains durch:

const dns = require('dns');

/*
 * A simple function for brute forcing a list of subdomains
 * given a maximum length of each subdomain.
 */
const generateSubdomains = function(length) {

    /*
     * A list of characters from which to generate subdomains.
     *
     * This can be altered to include less common characters
     * like '-'.
     *
     * Chinese, Arabic, and Latin characters are also
     * supported by some browsers.
     */
    const charset = 'abcdefghijklmnopqrstuvwxyz'.split('');
    let subdomains = charset;
    let subdomain;
    let letter;
    let temp;

    /*
     * Time Complexity: o(n*m)
     * n = length of string
     * m = number of valid characters
     */
    for (let i = 1; i < length; i++) {
        temp = [];
        for (let k = 0; k < subdomains.length; k++) {
          subdomain = subdomains[k];
          for (let m = 0; m < charset.length; m++) {
            letter = charset[m];
            temp.push(subdomain + letter);
          }
        }
        subdomains = temp
    }

    return subdomains;
}

const subdomains = generateSubdomains(4);
const promises = [];

/*
 * Iterate through each subdomain, and perform an asynchronous
 * DNS query against each subdomain.
 *
 * This is much more performant than the more common `dns.lookup()`
 * because `dns.lookup()` appears asynchronous from the JavaScript,
 * but relies on the operating system's getaddrinfo(3), which is
 * implemented synchronously.
 */
subdomains.forEach((subdomain) => {
  promises.push(new Promise((resolve, reject) => {
    dns.resolve(`${subdomain}.mega-bank.com`, function (err, ip) {
      return resolve({ subdomain: subdomain, ip: ip });
    });
  }));
});

// after all of the DNS queries have completed, log the results
Promise.all(promises).then(function(results) {
  results.forEach((result) => {
    if (!!result.ip) {
      console.log(result);
    }
  });
});

Nach einer kurzen Wartezeit sehen wir im Terminal eine Liste der gültigen Subdomains:

{ subdomain: 'mail', ip: '12.32.244.156' },
{ subdomain: 'admin', ip: '123.42.12.222' },
{ subdomain: 'dev', ip: '12.21.240.117' },
{ subdomain: 'test', ip: '14.34.27.119' },
{ subdomain: 'www', ip: '12.14.220.224' },
{ subdomain: 'shop', ip: '128.127.244.11' },
{ subdomain: 'ftp', ip: '12.31.222.212' },
{ subdomain: 'forum', ip: '14.15.78.136' }

Wörterbuch-Angriffe

Lieber als alle möglichen Subdomains auszuprobieren, können wir den Prozess weiter beschleunigen, indem wir einen Wörterbuchangriff anstelle eines Brute-Force-Angriffs verwenden. Ähnlich wie bei einem Brute-Force-Angriff durchläuft ein Wörterbuchangriff eine breite Palette potenzieller Subdomains, aber anstatt sie zufällig zu generieren, werden sie aus einer Liste der häufigsten Subdomains gezogen.

Wörterbuchangriffe sind viel schneller und finden in der Regel etwas Interessantes für dich. Nur die seltsamsten und ungewöhnlichsten Subdomains werden vor einem Wörterbuchangriff verborgen bleiben.

Ein beliebter Open-Source-DNS-Scanner namens dnscan ( ) enthält eine Liste der beliebtesten Subdomains im Internet, die auf Millionen von Subdomains aus über 86.000 DNS-Zoneneinträgen basiert. Laut den Subdomain-Scandaten von dnscan sind die 25 häufigsten Subdomains wie folgt:

  • www

  • mail

  • ftp

  • localhost

  • webmail

  • smtp

  • pop

  • ns1

  • webdisk

  • ns2

  • cpanel

  • whm

  • autodiscover

  • autoconfig

  • m

  • imap

  • test

  • ns

  • blog

  • pop3

  • dev

  • www2

  • admin

  • forum

  • news

Das dnscan Repository auf GitHub beherbergt Dateien mit den Top 10.000 Subdomains, die dank der sehr offenen GNU v3 Lizenz in deinen Aufklärungsprozess integriert werden können. Du findest die Subdomainlisten und den Quellcode von dnscanauf GitHub.

Wir können ein Wörterbuch wie dnscan einfach in unser Skript einfügen. Bei kleineren Listen kannst du die Zeichenketten einfach per Copy/Paste/Hardcode in das Skript einfügen. Für große Listen, wie die 10.000 Subdomains von dnscan, sollten wir die Daten getrennt vom Skript aufbewahren und erst zur Laufzeit einfügen. Das macht es viel einfacher, die Subdomainliste zu ändern oder andere Subdomainlisten zu verwenden. Die meisten dieser Listen liegen im .csv-Format vor, was die Integration in dein Subdomain-Aufklärungsskript sehr einfach macht:

const dns = require('dns');
const csv = require('csv-parser');
const fs = require('fs');

const promises = [];

/*
 * Begin streaming the subdomain data from disk (versus
 * pulling it all into memory at once, in case it is a large file).
 *
 * On each line, call `dns.resolve` to query the subdomain and
 * check if it exists. Store these promises in the `promises` array.
 *
 * When all lines have been read, and all promises have been resolved,
 * then log the subdomains found to the console.
 *
 * Performance Upgrade: if the subdomains list is exceptionally large,
 * then a second file should be opened and the results should be
 * streamed to that file whenever a promise resolves.
 */
fs.createReadStream('subdomains-10000.txt')
  .pipe(csv())
  .on('data', (subdomain) => {
    promises.push(new Promise((resolve, reject) => {
      dns.resolve(`${subdomain}.mega-bank.com`, function (err, ip) {
        return resolve({ subdomain: subdomain, ip: ip });
      });
    }));
  })
  .on('end', () => {

   // after all of the DNS queries have completed, log the results
   Promise.all(promises).then(function(results) {
     results.forEach((result) => {
       if (!!result.ip) {
         console.log(result);
       }
     });
   });
  });

Ja, so einfach ist das! Wenn du ein solides Wörterbuch mit Subdomains findest (es ist nur eine Suche entfernt), kannst du es einfach in das Brute-Force-Skript einfügen, und schon hast du auch ein Wörterbuch-Angriffsskript zur Hand. Da die Wörterbuchmethode viel effizienter ist als die Brute-Force-Methode, kann es ratsam sein, mit einem Wörterbuch zu beginnen und erst dann eine Brute-Force-Subdomain-Generierung zu verwenden, wenn das Wörterbuch nicht die gewünschten Ergebnisse liefert.

Zusammenfassung

Bei der Aufklärung einer Webanwendung sollte das Hauptziel darin bestehen, eine Karte der Anwendung zu erstellen, die später bei der Priorisierung und dem Einsatz von Angriffsnutzlasten verwendet werden kann. Eine erste Komponente dieser Suche besteht darin, zu verstehen, welche Server dafür verantwortlich sind, dass eine Anwendung funktioniert - daher unsere Suche nach Subdomänen, die mit der Hauptdomäne einer Anwendung verbunden sind.

Verbraucherorientierte Domains, wie z. B. der Client einer Bank-Website, werden in der Regel am meisten geprüft. Fehler werden schnell behoben, da die Besucher/innen täglich mit ihnen konfrontiert werden.

Server, die hinter den Kulissen laufen, wie z. B. ein Mailserver oder eine Admin-Backdoor, sind oft voller Bugs, da sie viel weniger genutzt werden und weniger exponiert sind. Oftmals kann das Auffinden einer dieser "Hinter-den-Kulissen"-APIs bei der Suche nach Schwachstellen, die in einer Anwendung ausgenutzt werden können, von Vorteil sein.

Wenn du versuchst, Subdomains zu finden, solltest du mehrere Techniken anwenden, da eine einzige Technik möglicherweise keine umfassenden Ergebnisse liefert. Sobald du glaubst, dass du genügend Erkundungen durchgeführt und einige Subdomains für die zu testende Domain gesammelt hast, kannst du zu anderen Aufklärungsmethoden übergehen - aber du kannst jederzeit zurückkommen und nach weiteren suchen, wenn du mit den offensichtlicheren Angriffsvektoren kein Glück hast.

Get Web Application Security, 2. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.