Investigation secdojo.net : d’un fichier HTA piégé à la compromission du domaine

Source : OpenSearch, index winlogbeat-* (21 au 28 mars 2024) Heures : UTC Résultat : ✅ chaîne d’attaque complète reconstituée, du phishing initial jusqu’à l’exfiltration de ntds.dit

1. Synthèse

L’utilisateur Vhagar télécharge invoice.hta sur le poste helpdesk (Sysmon 15). L’attaquant y installe un beacon Cobalt Strike. Il lance mimikatz, puis pivote vers SRV01 avec le compte joe en Pass-the-Hash.

Sur SRV01, il abuse d’ADCS avec Certify pour usurper adm_infra, vole et injecte un TGT avec Rubeus (Pass-the-Ticket), puis atteint le contrôleur de domaine. Sur le DC, il copie ntds.dit, tente de créer le compte Aegon et consulte une GPO personnalisée.

2. Chronologie

Heure (UTC) Machine Événement
24/03 16:06 helpdesk powershell.exe contacte 192.168.142.128:80 chaque minute (beacon)
24/03 21:25:42 helpdesk Exécution de C:\Windows\mimikatz.exe
27/03 22:46:37 SRV01 PsExec : 5ac8f30.exe écrit dans ADMIN$, service installé
27/03 23:23:12 SRV01 2 échecs 4625 (secdojo.net\joe) depuis 192.168.142.128
27/03 23:23:41 SRV01 Connexions de joe depuis .128 en NTLM V2 (Pass-the-Hash)
27/03 23:29:13 SRV01 Certify.exe request ... /template:vulnct /altname:adm_infra
27/03 23:34 à 23:46 SRV01 Rubeus dump, ptt /luid:0x1db074 (x3), monitor /interval:10
28/03 00:02:45 SRV01 Rubeus ptt /luid:0x20a882
28/03 00:11 et 00:16 DC New-ADUser -Name "Aegon" échoue (événement 4100)
28/03 01:10:58 DC Connexion adm_infra (4624, type 3)
28/03 01:11:20 DC PsExec : \\dc\ADMIN$\54900d2.exe
28/03 01:13:26 DC HELPDESK$ lit la GPO {87EA95E6-...} sur SYSVOL
28/03 01:15:27 DC PowerShell encodé : copie de ntds.dit

3. Détail par question, commandes et sorties

ℹ️ Les sorties sont résumées : seuls les champs utiles sont conservés.

Q1 — Première machine attaquée

GET winlogbeat-*/_search
{ "size": 100, "sort": [{ "@timestamp": { "order": "asc" } }],
  "_source": ["@timestamp","host.name","event.code","winlog.event_id","winlog.event_data","message","user.name"],
  "query": { "query_string": { "query": "invoice OR invoice.hta OR helpdesk" } } }

Sortie pertinente : host.name = helpdesk.secdojo.net, event.code = 15, message contenant invoice.hta.

Réponse : helpdesk.secdojo.net

Q2 — Fichier malveillant téléchargé

GET winlogbeat-*/_search
{ "size": 100, "sort": [{ "@timestamp": { "order": "asc" } }],
  "_source": ["@timestamp","host.name","event.code","winlog.event_id","winlog.event_data.TargetFilename","winlog.event_data.Hashes","message","user.name"],
  "query": { "query_string": { "query": "invoice.hta" } } }

Sortie pertinente : winlog.event_id = 15, TargetFilename = ...\invoice.hta, sur helpdesk.secdojo.net.

Réponse : invoice.hta

Q3 — Utilisateur victime

GET winlogbeat-*/_search
{ "size": 100, "sort": [{ "@timestamp": { "order": "asc" } }],
  "_source": ["@timestamp","host.name","user.name","winlog.event_data.SubjectUserName","winlog.event_data.User","winlog.event_data.TargetUserName","message"],
  "query": { "query_string": { "query": "invoice.hta" } } }

Sortie pertinente : user.name = Vhagar, host.name = helpdesk.secdojo.net. Cohérent avec les événements Kerberos du DC (28/03 01:22:21), où Vhagar demande un TGT depuis 192.168.142.149.

Réponse : Vhagar

Q4 — Hash SHA-256 du fichier téléchargé

GET winlogbeat-*/_search
{ "size": 100, "sort": [{ "@timestamp": { "order": "asc" } }],
  "_source": ["@timestamp","host.name","event.code","winlog.event_id","winlog.event_data.TargetFilename","winlog.event_data.Hashes","file.hash.sha256","hash.sha256","message"],
  "query": { "query_string": { "query": "invoice.hta" } } }

Sortie pertinente : winlog.event_data.Hashes = SHA256=c9b035...7883c (ou file.hash.sha256 selon le mapping). Le hash est lu dans le résultat de la recherche sur invoice.hta, pas fourni dans la requête.

Réponse : c9b035243c1e4dee78314fe8fe802ae96aa693723ec2c0ae0d099a571427883c

Q5 — Journal et identifiant d’événement

GET winlogbeat-*/_search
{ "size": 50, "sort": [{ "@timestamp": { "order": "asc" } }],
  "_source": ["@timestamp","host.name","event.code","winlog.event_id","log","message"],
  "query": { "bool": { "should": [
    { "query_string": { "query": "invoice.hta" } },
    { "term": { "event.code": "15" } } ],
  "minimum_should_match": 1 } } }

Sortie pertinente : event.code = 15 avec le message invoice.hta. Journal Windows : Microsoft-Windows-Sysmon/Operational. L’événement Sysmon 15 est FileCreateStreamHash : il enregistre la création d’un flux de fichier (ici le Zone.Identifier d’un fichier téléchargé) avec son hash.

Réponse : log file : Sysmon, eventID : 15

Q6 — C2 associé au malware

GET winlogbeat-*/_search
{ "size": 100, "sort": [{ "@timestamp": { "order": "asc" } }],
  "_source": ["@timestamp","host.name","event.code","winlog.event_data","process.name","process.command_line","process.executable","message"],
  "query": { "query_string": { "query": "5ac8f30 OR ADMIN$ OR beacon OR Cobalt OR invoice.hta" } } }

Sortie pertinente : \\srv01\ADMIN$\5ac8f30.exe, puis 5ac8f30.exe, services.exe et rundll32.exe sur SRV01. Cette chaîne (binaire écrit dans ADMIN$, service installé, rundll32 en processus enfant) correspond au jump psexec de Cobalt Strike. Les connexions régulières de powershell.exe (helpdesk) vers 192.168.142.128:80, toutes les minutes, sont compatibles avec un beacon HTTP.

Réponse : CobaltStrike

⚠️ Remarque : 5ac8f30.exe est un nom de payload, pas la réponse. Cette identification est une déduction à partir du comportement observé ; aucun log ne contient le nom « Cobalt Strike » en clair.

Q7 — Identifiant de la GPO

GET winlogbeat-*/_search
{ "size": 30, "sort": [{ "@timestamp": { "order": "asc" } }],
  "_source": ["@timestamp","host.name","event.code","message"],
  "query": { "bool": {
    "filter": [ { "term": { "event.code": "5145" } },
                { "range": { "@timestamp": { "gte": "2024-03-27T22:00:00Z" } } } ],
    "must": [ { "match_phrase": { "message": "Policies" } } ] } } }

Sortie : 42 événements 5145 sur \\*\SYSVOL du DC. Trois GUID apparaissent :

GUID Nature
{31B2F340-016D-11D2-945F-00C04FB984F9} Default Domain Policy (standard)
{6AC1786C-016F-11D2-945F-00C04fB984F9} Default Domain Controllers Policy (standard)
{87EA95E6-7798-4573-A46B-808518540F33} GPO personnalisée, lue à 01:13:26 par HELPDESK$ (gpt.ini, Machine, Machine\Scripts\scripts.ini, Machine\Scripts\Startup)

Réponse : {87EA95E6-7798-4573-A46B-808518540F33}

⚠️ Réserve : tous les accès visibles sont des lectures. La conclusion repose sur le GUID non standard et sur le dossier Scripts. Une recherche d’accès en écriture sur ce GUID reste à faire.

Q8 et Q9 — Mimikatz et son hash

GET winlogbeat-*/_search
{ "size": 20, "_source": ["@timestamp","host.name","event.code","process.name","process.executable","process.command_line","process.hash.sha256"],
  "query": { "bool": { "should": [
    { "match": { "process.name": "mimikatz.exe" } },
    { "match": { "process.executable": "mimikatz" } },
    { "exists": { "field": "process.hash.sha256" } } ],
  "minimum_should_match": 1, "filter": [{ "term": { "event.code": "1" } }] } } }

Sortie (hit 1, score 8.89) : C:\Windows\mimikatz.exe sur helpdesk, le 24/03 à 21:25:42.

Réponse : 92804faaab2175dc501d73e814663058c78c0a042675a8937266357bcfb96c50

Q10 — Seconde machine attaquée

Requête sur les événements Security de SRV01 autour de 22:46 (27/03).

Sortie : 22 événements. joe (source 192.168.142.132) écrit 5ac8f30.exe dans ADMIN$ (5145), un service est installé (7045, 4697), services.exe lance le binaire (4688), puis rundll32.exe apparaît. C’est la signature d’un jump psexec Cobalt Strike.

Réponse : SRV01.secdojo.net

Q11 — IP source et compte

GET winlogbeat-*/_search
{ "size": 20, "_source": ["@timestamp","host.name","process.name","destination.ip","destination.port"],
  "query": { "bool": { "filter": [ { "term": { "event.code": "3" } },
    { "match": { "host.name": "helpdesk.secdojo.net" } } ] } } }

Sortie : powershell.exe vers 192.168.142.128:80 chaque minute (le C2).

GET winlogbeat-*/_search
{ "size": 30, "sort": [{ "@timestamp": { "order": "asc" } }],
  "query": { "bool": { "filter": [
    { "terms": { "event.code": ["4624","4625","4648","4768","4769"] } },
    { "match_phrase": { "message": "192.168.142.128" } } ] } } }

Sortie : 10 événements sur SRV01. Les 4625 (23:23) viennent de secdojo.net\joe, puis les 4624 réussis (type 3, NTLM V2) de joe (SECDOJO).

Réponse : Adresse IP : 192.168.142.128, compte : joe

⚠️ Le premier essai (192.168.142.132) était faux : cette IP appartient à helpdesk.

Q12 — Type d’attaque

Les 4624 de 192.168.142.128 sont en NtLmSsp / NTLM V2, avec un Logon GUID nul, après le dump mimikatz.

Réponse : E. Pass-the-Hash

Q13 — Outil ADCS

GET winlogbeat-*/_search
{ "size": 20, "query": { "bool": { "should": [
  { "match": { "process.name": "certify.exe" } }, { "match": { "process.name": "certipy" } } ],
  "minimum_should_match": 1 } } }

Sortie (1 hit, 4688, SRV01, 23:29:13) :

Certify.exe request /ca:WIN-5RB7E9B5NTV.secdojo.net\secdojo-WIN-5RB7E9B5NTV-CA /template:vulnct /altname:adm_infra

C’est l’abus ESC1 (le demandeur choisit le SAN).

Réponse : Certify

Q14 et Q15 — TGT volé et outil

GET winlogbeat-*/_search
{ "size": 30, "sort": [{ "@timestamp": { "order": "asc" } }],
  "query": { "match": { "process.name": "Rubeus.exe" } } }

Sortie : dump, monitor /interval:10 et ptt /luid:... /ticket:doIFvj.... Le blob Base64 contient krbtgt, SECDOJO.NET et YWRtX2luZnJh (= adm_infra).

Réponse Q14 : adm_infra Réponse Q15 : Rubeus

Q16 — Les deux LUID

Sortie (7 événements Rubeus sur SRV01) : ptt /luid:0x1db074 (x3, 23:38 à 23:46), ptt /luid:0x20a882 (28/03 00:02:45).

Réponse : 0x1db074 et 0x20a882

Q17 — Action avec un TGT volé

Rubeus ptt injecte le TGT d’adm_infra, ce qui permet de s’authentifier comme lui.

Réponse : A. Pass-the-Ticket

Q18 — Heure de connexion au DC (à confirmer)

GET winlogbeat-*/_search
{ "size": 20, "sort": [{ "@timestamp": { "order": "asc" } }],
  "query": { "bool": { "filter": [ { "term": { "event.code": "4624" } },
    { "match": { "host.name": "DC.secdojo.net" } },
    { "match_phrase": { "message": "adm_infra" } } ] } } }

Sortie : 4 hits. 01:10:58 et 01:11:20 (type 3, NTLM, depuis HELPDESK 192.168.142.149), puis 2 à 01:18:39 (type 7).

Réponse : Mar 28, 2024 @ 02:10 (GMT+1)

⚠️ Réserve : les New-ADUser de Q19 sont à 00:11 UTC, avant ces connexions. Une connexion plus ancienne d’adm_infra existe peut-être. Si la réponse est refusée, relancer la requête avec gte: 2024-03-27T23:30:00Z.

Q19 — Compte dont la création a été tentée

GET winlogbeat-*/_search
{ "size": 30, "sort": [{ "@timestamp": { "order": "asc" } }],
  "query": { "bool": {
    "filter": [ { "range": { "@timestamp": { "gte": "2024-03-27T23:00:00Z" } } } ],
    "must_not": [ { "match": { "host.name": "EXCHANGE_SRV.secdojo.net" } } ],
    "must": [ { "query_string": { "fields": ["message","process.command_line","winlog.event_data.CommandLine"],
      "query": "\"net user\" OR \"New-ADUser\" OR \"New-MachineAccount\" OR \"dsadd\"" } } ] } } }

Sortie : 2 événements 4100 sur le DC (00:11:19 et 00:16:33), erreur « Directory object not found », exécutés en SECDOJO\SYSTEM. Commande décodée :

New-ADUser -Name "Aegon" -SamAccountName "Aegon" -Path "OU=Users,DC=secdojo,DC=net" `
  -AccountPassword (ConvertTo-SecureString "P@ssw0rd" ...) -Enabled $true

Réponse : Aegon

4. Indicateurs de compromission

Type Valeur
IP attaquant (C2) 192.168.142.128
IP de HELPDESK vues 192.168.142.132, .149, .150 (non départagées)
Fichiers invoice.hta, mimikatz.exe, 5ac8f30.exe (SRV01), 54900d2.exe (DC), Certify.exe, Rubeus.exe
Hash mimikatz 92804faaab2175dc501d73e814663058c78c0a042675a8937266357bcfb96c50
Comptes abusés Vhagar, joe, adm_infra
Compte tenté Aegon
LUID Rubeus 0x1db074, 0x20a882
Template ADCS vulnct (CA secdojo-WIN-5RB7E9B5NTV-CA)
GPO {87EA95E6-7798-4573-A46B-808518540F33}

5. Techniques MITRE ATT&CK

Technique Nom
T1566 Phishing
T1218.005, T1059.001 HTA, PowerShell
T1071.001 C2 web
T1003 Dump d’identifiants
T1550.002 Pass-the-Hash
T1021.002, T1569.002 PsExec
T1649 ADCS
T1550.003 Pass-the-Ticket
T1003.003 ntds.dit
T1136.002 Création de compte
T1484.001 GPO (à confirmer)

6. Recommandations

  • Corriger le template vulnct (retirer « le demandeur fournit le SAN ») et auditer ADCS.
  • Réduire NTLM, activer Credential Guard et protéger LSASS.
  • Réinitialiser krbtgt deux fois et tous les mots de passe, car ntds.dit a été copié.
  • Restreindre ADMIN$ et la création de services à distance. Surveiller les événements 4697 et 7045.
  • Auditer la GPO {87EA95E6-...} et ses scripts de démarrage.
  • Activer et centraliser les événements 4720, 4741 et 5136, absents de cet index.

Leçons retenues

  • Une détection peut reposer sur une chaîne comportementale plutôt qu’une signature explicite : aucun log ne contenait le nom « Cobalt Strike » en clair, mais la combinaison (écriture dans ADMIN$, service installé, rundll32 en processus enfant) suffisait à identifier la technique du jump psexec.
  • Une réponse obtenue par une première requête (192.168.142.132 pour Q11) peut être incorrecte si elle correspond en réalité à une autre machine du même scénario — toujours croiser avec plusieurs event codes avant de conclure.
  • Documenter explicitement les réserves méthodologiques (comme pour Q7 et Q18) est une bonne pratique DFIR : cela distingue ce qui est prouvé de ce qui est déduit, et donne une piste claire pour affiner l’investigation si une réponse est contestée.
  • Le même LUID Kerberos (0x1db074, 0x20a882) réutilisé dans plusieurs commandes Rubeus ptt est un indicateur fiable pour relier des actions d’un attaquant entre elles, même à travers plusieurs étapes de la chaîne.

Références

  • Event ID Sysmon 15 — FileCreateStreamHash
  • Event ID Windows Security 4624/4625/4648/4768/4769 — authentification et tickets Kerberos
  • Event ID Windows Security 5145/4697/7045 — accès réseau et création de service
  • Event ID Windows 4100 — erreur d’exécution PowerShell (journal PowerShell)
  • Outils offensifs : Certify (abus ADCS), Rubeus (Pass-the-Ticket), mimikatz (dump d’identifiants)