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 dentds.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.exeest 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-ADUserde Q19 sont à00:11UTC, avant ces connexions. Une connexion plus ancienne d’adm_infraexiste peut-être. Si la réponse est refusée, relancer la requête avecgte: 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
krbtgtdeux fois et tous les mots de passe, carntds.dita é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é,rundll32en processus enfant) suffisait à identifier la technique du jump psexec. - Une réponse obtenue par une première requête (
192.168.142.132pour 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 Rubeuspttest 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)