Verdeckte Angriffspfade in Scheduled Tasks effizient aufdecken. TaskHound hilft!

TaskHound: Die Suche nach Scheduled Task Credentials in Windows-Systemen sowie deren Dokumentation im Rahmen von Pentests war mühsam und oft unvollständig. Bis jetzt.

Eine persönliche Führung von ProSec-Pentester @r0BIT durch das von ihm entwickelte Tool.

TL;DR

TaskHound listet geplante Windows-Tasks, extrahiert deren XML-Felder (UserId, Command, Arguments, LogonType etc.) und priorisiert Tasks, die gespeicherte Credentials privilegierter Nutzer enthalten.

Für Remote-Scans brauchst du SMB-Zugriff auf C$\Windows\System32\Tasks (typischerweise Local-Admin). Alternativ läuft TaskHound im Offline Modus gegen zuvor manuell exportierte Daten.

Vergleicht das Attribut „PasswordLastChanged“ aus eurem AD im Vergleich zum Task-Creation-Date. Für höhere Zuverlässigkeit können zusätzliche Quellen geprüft werden (Event-Logs bzw. Task-History per WMI).

Host-Verfügbarkeit. TaskHound kann nur die Ziele erkennen, die zum Zeitpunkt des Scans auch tatsächlich Online waren Ein direkter Vergleich von Domain-Joined Systemen zu bereits gescannten Systemen ist in Arbeit. Und die „Password-Freshness“ Erkennung braucht noch Feinschliff.

Inhaltsverzeichnis

Erstellt, Vergessen, später Ausgenutzt. Stored Cedentials in Scheduled Tasks

Hin und wieder (oft) kommt es in unseren Pentests vor, dass wir auf kompromittierten Windows Systemen auf geplante Aufgaben (Scheduled Tasks) stoßen, die im Kontext privilegierter Nutzer ausgeführt werden und deren Kennwörter (Credentials) auf dem System gespeichert sind.

Wenn wir den Kunden dann darüber in Kenntnis gesetzt und aufgezeigt haben, wie schnell man sich damit Löcher in das mühsam aufgebaute Tiering Konzept kloppt, folgt meist die gleiche Frage:

“Waren das alle oder gibt’s da noch mehr?”

Die Antwort darauf zu finden endet dann in der Regel damit, eine ganze Armada an PowerShell- oder Python Scripts fragwürdiger Herkunft durch die Domäne zu jagen, sämtliche Task XMLs einzusammeln, die nächste Flut an Scripts zum Parsen der Informationen loszutreten, um letztendlich CSV Dateien zu erhalten, die eigentlich genauso unübersichtlich sind. Klingt nach Spaß, oder?

Aus rein offensiver Perspektive wird es nicht besser. Folgendes Szenario: Man hat bereits etwa 20 Maschinen kompromittiert und sucht nach Wegen, diese für eine vollständige Kompromittierung der Domäne zu nutzen. Um nicht dutzende von Scheduled Task XMLs lesen zu müssen, schmeißt man einfach blind einen DPAPI-Dump gegen alles, was man bisher hochgenommen hat, und hofft auf das Beste.

Selbst wenn das funktioniert, bleiben noch weitere Fragen offen:

  • Ist der User, unter dessen Kontext der Task läuft, überhaupt ein “High-Value” Target?

  • Ist das hinterlegte Passwort des Benutzers im Scheduled Task überhaupt noch aktuell?

Ich wollte genau hier ansetzen. Um mir und anderen die Nächte vor dem Rechner zu erleichtern, in denen man solche Texte gefühlt 100 Mal lesen muss und dann zu Recht seine Karriereentscheidungen hinterfragt:

				
					<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2025-09-18T23:04:37.3089851</Date>
    <Author>DEMO\Administrator</Author>
    <URI>\HIGH_VAL_WITH_CREDS</URI>
  </RegistrationInfo>
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>2025-09-18T23:04:16</StartBoundary>
      <Enabled>true</Enabled>
      <ScheduleByDay>
        <DaysInterval>1</DaysInterval>
      </ScheduleByDay>
    </CalendarTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <RunLevel>HighestAvailable</RunLevel>
      <UserId>DEMO\Administrator</UserId>
      <LogonType>Password</LogonType>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>C:\Windows\System32\cmd.exe</Command>
      <Arguments>/c whoami</Arguments>
    </Exec>
  </Actions>
</Task>
				
			

Introducing: TaskHound

Hier kommt TaskHound ins Spiel. Das Tool nimmt dir das mühselige Aufräumen der Datenflut ab und liefert das, was in dem Moment zählt: Tasks mit PrivEsc potential.

Du startest entweder Remote via SMB oder offline mit zuvor manuell exportierten Daten, lässt das Tool die Arbeit übernehmen und bekommst wahlweise eine fokussierte Liste mit lohnenswerten Zielen oder eine für den direkten Import bereite BloodHound/OpenGraph JSON. Das spart dir das Blindscannen mit DPAPI-Dumps und gibt dir stattdessen handfeste Ansatzpunkte für Lateral Movement, Privilege Escalation oder sofortiges Hardening. Je nach Blickwinkel.

Im nächsten Abschnitt zeige ich dir den generellen Workflow und wie du die Exporte in BloodHound nutzen kannst.

Windows Privileged Scheduled Task Discovery Tool for fun and profit.

TaskHound macht jagt auf Scheduled Tasks in Windows Umgebungen, die im Kontext privilegierter User laufen. Es enumeriert Tasks über SMB, wertet die Task XML Dateien auf und identifiziert Angriffsvektoren durch BloodHound Export Support.

Zum GitHub Repository

Workflow & Features

Der generelle Workflow mit TaskHound ist denkbar einfach und schnell erklärt:

				
					+----------------------+      +---------------------+
|    SMB Verbindung    | ---> | Local-Admin-Rechte? |
+----------------------+      +---------------------+
                                         |
                                         v
+----------------------+      +----------------------+
|     XML Parsing      | <--- |   Tasks einsammeln   |
+----------------------+      +----------------------+
           |                          
           v                          
+----------------------+      +----------------------+
|     Filter-Logik     | ---> |  High Value Analyse  |
+----------------------+      +----------------------+   
                                          |
                                          v
                              +----------------------+
                              |        Output        |
                              +----------------------+
				
			

Wir nutzen zuvor erbeutete Local Admin Berechtigungen um das C$ Laufwerk des Zielsystems zu mounten, navigieren in den C:\Windows\System32\Tasks Ordner, schnappen uns rekursiv Alles, was da drin liegt, werfen die Ergebnisse in einen Parser und analysieren verschiedene Felder, die für uns von Interesse sind.

				
					+------------+----------------------------------------+--------------------------------------------+
| Feldname   | Zweck                                  | Mögliche Werte                             |
+------------+----------------------------------------+--------------------------------------------+
| UserId     | Das Benutzerkonto, unter dem der Task  | DOMAIN\User                                |
|            | ausgeführt wird                        | SID                                        |
|            |                                        | SamAccountName                             |
+------------+----------------------------------------+--------------------------------------------+
| Command    | Die ausführbare Datei oder das Skript, | C:\Windows\System32\cmd.exe                |
|            | das der Task startet                   |                                            |
+------------+----------------------------------------+--------------------------------------------+
| Arguments  | Kommandozeilen-Parameter, die an das   | /c whoami                                  |
|            | Kommando übergeben werden              |                                            |
+------------+----------------------------------------+--------------------------------------------+
| LogonType  | Bestimmt, ob Anmeldedaten gespeichert  | Password                                   |
|            | sind oder nicht                        | InteractiveToken                           |
|            |                                        | S4U                                        |
+------------+----------------------------------------+--------------------------------------------+
| Author     | Wer den Task erstellt oder registriert | DOMAIN\User                                |
|            | hat                                    | SID                                        |
|            |                                        | SamAccountName                             |
+------------+----------------------------------------+--------------------------------------------+
| Date       | Erstellungsdatum des Tasks             | Zeitstempel                                |
+------------+----------------------------------------+--------------------------------------------+
| Enabled    | Gibt an, ob der Task noch aktiv ist    | Boolean (True)                             |
|            | oder nicht                             | Boolean (False)                            |
+------------+----------------------------------------+--------------------------------------------+
| Und weitere|                                        |                                            |
| ...        |                                        |                                            |
+------------+----------------------------------------+--------------------------------------------+

				
			

Die Ergebnisse werden anschließend nach einem definierten Regelwerk gefiltert, wobei einige Tasks von Haus aus übersprungen werden. Zum Beispiel:

  • Tasks unter C:\Windows\System32\Tasks\Windows

    • Hier liegen standardmäßig die Default Tasks von Windows ab, die selten von besonderem Wert aus Angreiferperspektive sind.

  • Tasks ohne gespeicherte Passwörter (LogonType != Password)

    • Diese Tasks laufen zwar automatisch zur festgelegten Zeit, allerdings nur, wenn der verantwortliche Benutzer zu diesem Zeitpunkt auch angemeldet ist.

  • Tasks, die im Kontext lokaler Entitäten aufgerufen werden

    • SID S-1-5-18 aka LocalSystem ist da ein Beispiel. Diese Tasks laufen zwar mit NT\SYSTEM Berechtigungen, allerdings nur lokal auf der Maschine. Da wir für die Verwendung von TaskHound sowieso schon lokal administrative Berechtigungen brauchen, ist das nur in Ausnahmefällen interessant.

Sobald die Aufbereitung abgeschlossen ist und idealerweise Tasks identifiziert wurden, deren Passwort im System gespeichert ist, kommt das eigentliche Kernstück des Tools: BloodHound Integration.

BloodHound Integration

TaskHound bietet verschiedene Möglichkeiten, die eingesammelten Tasks aufzubereiten und gezielt nach High-Value Targets zu filtern.

Dazu gibt’s mehrere Wege, wie man die Daten einbinden kann:

  • Live DB Connector für Legacy BloodHound und BloodHound Community Edition

  • Offline-Kompatibilität durch das Parsen zuvor generierter Exports

  • BloodHound OpenGraph Integration zur visuellen Aufbereitung der Ergebnisse

Der letztendliche Workflow ist in der Regel unabhängig von der verwendeten Datenquelle: Wir Mappen die UserId und den LogonType eines Tasks und können daraus ableiten, ob ein Task für uns als Angreifer besonders interessant ist oder ob es sich eher um nebensächliche Daten handelt.

Darüber hinaus bringt TaskHound noch zusätzliche Checks und Features mit, wie zum Beispiel ein automatischer Download der verschlüsselten DPAPI Credential-Blobs.

An der Stelle würde ich aber einfach auf die Tool-Usage verweisen:

				
					TTTTT  AAA   SSS  K   K H   H  OOO  U   U N   N DDDD
  T   A   A S     K  K  H   H O   O U   U NN  N D   D
  T   AAAAA  SSS  KKK   HHHHH O   O U   U N N N D   D
  T   A   A     S K  K  H   H O   O U   U N  NN D   D
  T   A   A SSSS  K   K H   H  OOO   UUU  N   N DDDD

                     by 0xr0BIT

usage: taskhound [-h] [-u USERNAME] [-p PASSWORD] [-d DOMAIN] [--hashes HASHES] [-k] [-t TARGET] [--targets-file TARGETS_FILE] [--dc-ip DC_IP] [--offline OFFLINE]
                 [--bh-data BH_DATA] [--bh-live] [--bh-user BH_USER] [--bh-password BH_PASSWORD] [--bh-connector BH_CONNECTOR] [--bhce | --legacy]
                 [--bh-save BH_SAVE] [--bh-opengraph] [--bh-output BH_OUTPUT] [--bh-no-upload] [--bh-set-icon] [--bh-force-icon] [--bh-icon BH_ICON]
                 [--bh-color BH_COLOR] [--include-ms] [--include-local] [--include-all] [--unsaved-creds] [--credguard-detect] [--loot] [--dpapi-key DPAPI_KEY]
                 [--no-ldap] [--ldap-user LDAP_USER] [--ldap-password LDAP_PASSWORD] [--ldap-domain LDAP_DOMAIN] [--plain PLAIN] [--json JSON] [--csv CSV]
                 [--opengraph OPENGRAPH] [--backup BACKUP] [--no-summary] [--debug]

TaskHound - Scheduled Task privilege checker with optional High Value enrichment

options:
  -h, --help            show this help message and exit

Authentication options:
  -u, --username USERNAME
                        Username (required for online mode)
  -p, --password PASSWORD
                        Password (omit with -k if using Kerberos/ccache)
  -d, --domain DOMAIN   Domain (required for online mode)
  --hashes HASHES       NTLM hashes in LM:NT format (or NT-only 32-hex) to use instead of password
  -k, --kerberos        Use Kerberos authentication (supports ccache)

Target options:
  -t, --target TARGET   Single target
  --targets-file TARGETS_FILE
                        File with targets, one per line
  --dc-ip DC_IP         Domain controller IP (required when using Kerberos without DNS)

Scanning options:
  --offline OFFLINE     Offline mode: parse previously collected XML files from directory (no authentication required)
  --bh-data BH_DATA     Path to High Value Target export (csv/json from Neo4j)
  --include-ms          Also include \Microsoft scheduled tasks (WARNING: very slow)
  --include-local       Include tasks running as local system accounts (NT AUTHORITY\SYSTEM, S-1-5-18, etc.)
  --include-all         Include ALL tasks (equivalent to --include-ms --include-local --unsaved-creds) - WARNING: VERY SLOW AND NOISY!
  --unsaved-creds       Show scheduled tasks that do not store credentials (unsaved credentials)
  --credguard-detect    EXPERIMENTAL: Attempt to detect Credential Guard status via remote registry (default: off). Only use if you know your environment supports
                        it.

BloodHound Live Connection:
  --bh-live             Use live BloodHound connection (parameters can be provided via CLI or bh_connector.config file)
  --bh-user BH_USER     BloodHound username (or set in config file)
  --bh-password BH_PASSWORD
                        BloodHound password (or set in config file)
  --bh-connector BH_CONNECTOR
                        BloodHound connector URI (default: http://127.0.0.1:8080, or set in config file). Examples: localhost, http://localhost:8080,
                        https://bh.domain.com, bolt://neo4j.local:7687. Supports both BHCE (http/https) and Legacy (bolt) protocols. If no protocol specified:
                        defaults to http:// for BHCE, bolt:// for Legacy.
  --bhce                Use BloodHound Community Edition (or set type=bhce in config)
  --legacy              Use Legacy BloodHound (or set type=legacy in config)
  --bh-save BH_SAVE     Save BloodHound query results to file (or set save_file in config)

BloodHound OpenGraph Integration:
  Automatically generate and optionally upload OpenGraph data to BloodHound CE (BHCE ONLY)

  --bh-opengraph        Generate BloodHound OpenGraph JSON files (auto-enabled if bh_connector.config has valid BHCE credentials). REQUIRES --bhce or type=bhce in
                        config - NOT compatible with Legacy BloodHound!
  --bh-output BH_OUTPUT
                        Directory to save BloodHound OpenGraph files (default: ./opengraph)
  --bh-no-upload        Generate OpenGraph files but skip automatic upload to BloodHound (files still saved)
  --bh-set-icon         Automatically set custom icon for ScheduledTask nodes after upload
  --bh-force-icon       Force icon update even if ScheduledTask icon already exists (requires --bh-set-icon)
  --bh-icon BH_ICON     Font Awesome icon name for ScheduledTask nodes (default: heart)
  --bh-color BH_COLOR   Hex color code for ScheduledTask node icon (default: #8B5CF6 - vibrant purple)

DPAPI Credential Decryption:
  --loot                Automatically download and decrypt ALL Task Scheduler credential blobs (requires --dpapi-key)
  --dpapi-key DPAPI_KEY
                        DPAPI_SYSTEM userkey from LSA secrets dump (hex format, e.g., 0x51e43225e5b43b25d3768a2ae7f99934cb35d3ea)

LDAP/SID Resolution options:
  Alternative credentials for SID lookups. Useful when you only have NTLM hashes or local admin access (domain="."). SID resolution can use lower-privilege
  plaintext credentials.

  --no-ldap             Disable LDAP queries for SID resolution (improves OPSEC but reduces user-friendliness)
  --ldap-user LDAP_USER
                        Alternative username for SID lookup (can be different from main auth credentials)
  --ldap-password LDAP_PASSWORD
                        Alternative password for SID lookup (plaintext only - hashes not supported)
  --ldap-domain LDAP_DOMAIN
                        Alternative domain for SID lookup (can be different from main auth domain)

Output options:
  --plain PLAIN         Directory to save normal text output (per target)
  --json JSON           Write all results to a JSON file
  --csv CSV             Write all results to a CSV file
  --opengraph OPENGRAPH
                        Directory to save BloodHound OpenGraph JSON files
  --backup BACKUP       Directory to save raw XML task files (per target)
  --no-summary          Disable summary table at the end of the run

Misc:
  --debug               Enable debug output (print full stack traces)

				
			

In Aktion sieht das Ganze dann so aus:

TaskHound – Initialisierung und erste Ergebnisse
TaskHound Initialisierung und erste Ergebnisse
Automatischer Upload zur angebundenen BloodHound-Instanz
Automatischer Upload zur angebundenen BloodHound-Instanz
Zusammenfassung
Zusammenfassung

Enter OpenGraph

Ihr könnt euch nicht vorstellen, wie lange ich von so etwas geträumt habe. Die Fähigkeit von BloodHound, komplexe Active-Directory-Beziehungen zu visualisieren, war schon bei Einführung ein absoluter Gamechanger. Aber eigene Nodes und Edges erstellen zu können, ohne sich dabei durch unzählige Hürden zu quälen? Und das Ganze dann auch noch so zugänglich zu machen, dass andere nicht dieselben Hürden nehmen müssen? Das ist einfach unbezahlbar.

Aktuell erzeugt TaskHound die folgenden neuen Objekte und Verbindungen:

  • Node — ScheduledTask: Repräsentiert die Scheduled Tasks selbst, mit Eigenschaften wie Command, Trigger, etc.
  • Edge — HasTask: Verknüpft Computer → ScheduledTask (zeigt an, welche Tasks auf welchem System existieren).
  • Edge — HasTaskWithStoredCreds: Spezielle Beziehung für Tasks, die gespeicherte Zugangsdaten enthalten. Nützlich, um direkt mögliche PrivEsc zu identifizieren.
  • Edge — RunsAs: Verknüpft ScheduledTask → User und zeigt an, unter welchem Benutzerkontext der Task ausgeführt wird.

Um dieses Feature auf die schnelle auszuprobieren könnt ihr folgenden Cypher Query benutzen:

				
					// Find all computers that have ScheduledTasks with stored credentials
// and the user context they run as
  MATCH p = (c:Computer)-[:HasTaskWithStoredCreds]->(t:scheduledtask)-[:RunsAs]->(u)
  RETURN p
				
			
Nach dem BloodHound-Import
Nach dem BloodHound-Import

Zugaben

Natürlich gibt es für all diejenigen unter euch, denen OPSEC am Herzen liegt auch eine passende Beacon Object File (BOF) mit den nötigsten Kernfunktionen (Connect, Crawl, Parse, Backup):

https://github.com/1r0BIT/TaskHound/blob/main/BOF/README.md 

Der Pull-Request für AdaptixC2 wurde bereits in den Main Branch gemerged und standardmäßig mit v0.10 des Extension-Kits ausgerollt:

https://github.com/Adaptix-Framework/Extension-Kit

TaskHound BOF Execution Demo
TaskHound BOF Execution Demo

Primär entwickelt wurde die BOF für AdaptixC2. Die verwendeten BeaconAPIs sind aber allesamt mit Cobalt Strike kompatibel. Das ganze sollte sich also auch ohne zu viel Aufwand für andere gängige C2 Frameworks adaptieren lassen.

Für alle Pentests, bei denen Stealth/OPSEC nicht so wichtig bzw. keine Anforderung ist: Der Pull Request für das passende NetExec Modul ist gerade im Review.

https://github.com/Pennyw0rth/NetExec/pull/933

NetExec Pull Request: TaskHound
NetExec Pull Request
TaskHound NetExec Tool Demo
Tool Demo

Einschränkungen

Wie bei so ziemlich Allem, was Spaß macht, gibt es auch bei TaskHound die ein oder andere Einschränkung.

  • Die Prüfung, ob das hinterlegte Passwort des Scheduled Tasks noch aktuell ist und sich ein DPAPI Dump lohnt ist schwieriger als gedacht. Bisher gibt es nur einen sicheren Indikator: Wenn der Wert von PasswordLastChanged in BloodHound älter als das CreationDate des Scheduled Tasks ist.

    • In der Task XML steht kein TimeStamp der letzten Modifikation und bei Änderung des Passworts im Task ändern sich auch leider die Metadaten der Datei nicht.

    • Eine Idee wäre es z.Bsp. die Windows Event-Logs von der Platte zu ziehen oder per WMI abzufragen und nach der Task-History durchsuchen, um den Timestamp der letzten erfolgreichen Ausführung zu vergleichen. #OverEngineering

    • Wer hier bessere Ideen hat: Gerne Pull Request stellen! grinning face with smiling eyes

  • Die TIER-0 Klassifizierung basiert aktuell noch auf den Standard TIER-0 Gruppen oder Merkmalen in Active Directory (AdminSDHolder, Domain Admins, Enterprise Admins, etc.)

    • Das ist eine Einschränkung von Legacy BloodHound, die nur “High Value” oder nicht kennt.

    • BloodHound Community Edition trifft da zumindest in den Attributen noch eine Unterscheidung zwischen highvalue und isTierZero, auch wenn das auch noch nicht richtig zu funktionieren scheint und ich dafür eine eher merkwürdige Lösung implementiert habe.

Roadmap

Outro

Bitte hier kreatives Outro denken. Es ist 01:00 Nachts und mein Kaffee ist alle.

Weiterführende Links

Newsletter Form

Become a Cyber Security Insider

Sichere dir frühen Zugang und exklusive Inhalte!


ANDERE BEITRÄGE
Professionalisiere deinen Attack Workflow
Ethical Hacking Kurse mit persönlichem Mentoring

Inhaltsverzeichnis

Hast du Fragen oder Ergänzungen? Immer her damit!
Schreibe einen Kommentar und wir antworten so bald wie möglich!

Dein E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Teile Dein Feedback und hilf uns, unsere Services zu verbessern!

Teile dein Feedback und hilf uns, unsere Services zu verbessern!

Nimm Dir 1 Minute Zeit für ein kurzes Feedback. So stellen wir sicher, dass unsere IT-Sicherheitslösungen genau deinen Bedürfnissen entsprechen.

PSN_KU_Cover
NewsLetter Form Pop Up New

Become a Cyber Security Insider

Abonniere unsere Knowledge Base und erhalte:

Frühen Zugriff auf neue Blogbeiträge
Exklusive Inhalte
Regelmäßige Updates zu Branchentrends und Best Practices


Bitte akzeptiere die Cookies unten auf dieser Seite, um das Formular abschicken zu können!