Skip to content

Commit 20e3a5d

Browse files
authored
Merge pull request #2 from jmorascalyr/alert_fix_the_third
feat: Add WEL alerts for Bridge and Enterprise machines with multi-ma…
2 parents d066ec8 + d86a6e0 commit 20e3a5d

4 files changed

Lines changed: 229 additions & 37 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"finding_info": {
3+
"uid": "placeholder_uid",
4+
"title": "HELIOS - WEL Active Directory Global Admin Group Created",
5+
"desc": "Detects the creation of security-enabled global administrative groups (4727) in Active Directory. This may indicate unauthorized modifications to privileged group memberships, posing a security risk. Extended Windows Event Log collection must be enabled for this rule to work properly."
6+
},
7+
"resources": [
8+
{
9+
"uid": "DYNAMIC_RESOURCE_UID",
10+
"name": "endpoint"
11+
}
12+
],
13+
"severity": "high",
14+
"category_uid": 2,
15+
"class_uid": 99602001,
16+
"class_name": "S1 Security Alert",
17+
"type_uid": 9960200101,
18+
"type_name": "S1 Security Alert: Create",
19+
"category_name": "Findings",
20+
"activity_id": 1,
21+
"metadata": {
22+
"version": "1.1.0",
23+
"extension": {
24+
"name": "s1",
25+
"uid": "998",
26+
"version": "0.1.0"
27+
},
28+
"product": {
29+
"name": "Windows Event Logs",
30+
"vendor_name": "Microsoft"
31+
},
32+
"logged_time": "DYNAMIC",
33+
"modified_time": "DYNAMIC"
34+
},
35+
"time": "DYNAMIC",
36+
"attack_surface_ids": [1],
37+
"severity_id": 4,
38+
"state_id": 1,
39+
"s1_classification_id": 28
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"finding_info": {
3+
"uid": "placeholder_uid",
4+
"title": "HELIOS - WEL Successful Brute Force Attack",
5+
"desc": "Detects a successful brute force attack against a Windows endpoint. Multiple failed authentication attempts followed by a successful logon indicate credential compromise. This technique is commonly used by attackers to gain initial access or escalate privileges within a network. Extended Windows Event Log collection must be enabled for this rule to work properly."
6+
},
7+
"resources": [
8+
{
9+
"uid": "DYNAMIC_RESOURCE_UID",
10+
"name": "endpoint"
11+
}
12+
],
13+
"severity": "critical",
14+
"category_uid": 2,
15+
"class_uid": 99602001,
16+
"class_name": "S1 Security Alert",
17+
"type_uid": 9960200101,
18+
"type_name": "S1 Security Alert: Create",
19+
"category_name": "Findings",
20+
"activity_id": 1,
21+
"metadata": {
22+
"version": "1.1.0",
23+
"extension": {
24+
"name": "s1",
25+
"uid": "998",
26+
"version": "0.1.0"
27+
},
28+
"product": {
29+
"name": "Windows Event Logs",
30+
"vendor_name": "Microsoft"
31+
},
32+
"logged_time": "DYNAMIC",
33+
"modified_time": "DYNAMIC"
34+
},
35+
"time": "DYNAMIC",
36+
"attack_surface_ids": [1],
37+
"severity_id": 5,
38+
"state_id": 1,
39+
"s1_classification_id": 28
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"finding_info": {
3+
"uid": "placeholder_uid",
4+
"title": "HELIOS - WEL Hidden Scheduled Task Creation",
5+
"desc": "Detects the creation of a hidden scheduled task, a tactic often used by attackers to maintain persistence on a compromised system. Hidden tasks can evade detection by administrators and standard monitoring tools, allowing adversaries to execute malicious activities stealthily. This technique is commonly employed to run malicious scripts, download payloads, or maintain remote access without raising suspicion. Extended Windows Event Log collection must be enabled for this rule to work properly."
6+
},
7+
"resources": [
8+
{
9+
"uid": "DYNAMIC_RESOURCE_UID",
10+
"name": "endpoint"
11+
}
12+
],
13+
"severity": "high",
14+
"category_uid": 2,
15+
"class_uid": 99602001,
16+
"class_name": "S1 Security Alert",
17+
"type_uid": 9960200101,
18+
"type_name": "S1 Security Alert: Create",
19+
"category_name": "Findings",
20+
"activity_id": 1,
21+
"metadata": {
22+
"version": "1.1.0",
23+
"extension": {
24+
"name": "s1",
25+
"uid": "998",
26+
"version": "0.1.0"
27+
},
28+
"product": {
29+
"name": "Windows Event Logs",
30+
"vendor_name": "Microsoft"
31+
},
32+
"logged_time": "DYNAMIC",
33+
"modified_time": "DYNAMIC"
34+
},
35+
"time": "DYNAMIC",
36+
"attack_surface_ids": [1],
37+
"severity_id": 4,
38+
"state_id": 1,
39+
"s1_classification_id": 28
40+
}

Backend/scenarios/apollo_ransomware_scenario.py

Lines changed: 109 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -152,26 +152,61 @@
152152
"📬 PHASE 2: Email Interaction": {
153153
"template": "proofpoint_email_alert",
154154
"offset_minutes": 2, # 2 min after delivery (user clicks link)
155+
"target_machine": "email",
155156
"overrides": {
156-
"finding_info.title": "Malicious Email Link Clicked",
157+
"finding_info.title": "HELIOS - Malicious Email Link Clicked",
157158
"finding_info.desc": f"User {VICTIM_PROFILE['email']} clicked malicious link in phishing email from {ATTACKER_PROFILE['sender_email']}"
158159
}
159160
},
160161
"📤 PHASE 4: Data Exfiltration": {
161162
"template": "sharepoint_data_exfil_alert",
162163
"offset_minutes": 25, # After last document download (base+24:30)
164+
"target_machine": "email",
163165
"overrides": {
164-
"finding_info.title": "Data Exfiltration from SharePoint",
166+
"finding_info.title": "HELIOS - Data Exfiltration from SharePoint",
165167
"finding_info.desc": f"User {VICTIM_PROFILE['email']} downloaded sensitive documents including Personnel Records and Command Codes"
166168
}
167169
},
168170
"rdp_download": {
169171
"template": "o365_rdp_sharepoint_access",
170172
"offset_minutes": 35, # After RDP file download event (base+25)
173+
"target_machine": "email",
171174
"overrides": {
172-
"finding_info.title": "OneDrive RDP Files Downloaded",
175+
"finding_info.title": "HELIOS - OneDrive RDP Files Downloaded",
173176
"finding_info.desc": f"User {VICTIM_PROFILE['email']} downloaded RDP files from SharePoint - potential lateral movement preparation"
174177
}
178+
},
179+
"wel_hidden_schtask_bridge": {
180+
"template": "wel_hidden_scheduled_task",
181+
"offset_minutes": 8, # After PowerShell spawns and creates persistence task on bridge
182+
"target_machine": "bridge",
183+
"overrides": {
184+
"finding_info.desc": f"Hidden scheduled task 'WindowsUpdate' created on {VICTIM_PROFILE['machine_bridge']} by {VICTIM_PROFILE['domain']}\\{VICTIM_PROFILE['username']} to execute {ATTACKER_PROFILE['malware_name']}. This persistence mechanism allows the attacker to maintain access even after system reboots."
185+
}
186+
},
187+
"wel_hidden_schtask_enterprise": {
188+
"template": "wel_hidden_scheduled_task",
189+
"offset_minutes": 40, # After lateral movement to Enterprise
190+
"target_machine": "enterprise",
191+
"overrides": {
192+
"finding_info.desc": f"Hidden scheduled task created on {VICTIM_PROFILE['machine_enterprise']} Domain Controller by {VICTIM_PROFILE['domain']}\\{VICTIM_PROFILE['username']} to execute {ATTACKER_PROFILE['malware_name']}. Lateral movement persistence established on critical infrastructure."
193+
}
194+
},
195+
"wel_brute_force_enterprise": {
196+
"template": "wel_brute_force_success",
197+
"offset_minutes": 30, # After credential dump and brute force attempts
198+
"target_machine": "enterprise",
199+
"overrides": {
200+
"finding_info.desc": f"Successful brute force attack detected on {VICTIM_PROFILE['machine_enterprise']}. Multiple failed logon attempts from {VICTIM_PROFILE['machine_bridge']} ({VICTIM_PROFILE['client_ip']}) followed by successful authentication using stolen credentials from Mimikatz dump."
201+
}
202+
},
203+
"wel_ad_admin_group_enterprise": {
204+
"template": "wel_ad_global_admin_group",
205+
"offset_minutes": 45, # After gaining access to Enterprise DC
206+
"target_machine": "enterprise",
207+
"overrides": {
208+
"finding_info.desc": f"Security-enabled global admin group created on {VICTIM_PROFILE['machine_enterprise']} Domain Controller by {VICTIM_PROFILE['domain']}\\{VICTIM_PROFILE['username']}. This may indicate privilege escalation after lateral movement from {VICTIM_PROFILE['machine_bridge']}."
209+
}
175210
}
176211
}
177212

@@ -254,13 +289,18 @@ def create_event(timestamp: str, source: str, phase: str, event_data: dict) -> D
254289

255290
def load_alert_template(template_id: str) -> Optional[Dict]:
256291
"""Load an alert template JSON from the templates directory"""
257-
templates_dir = os.path.join(backend_dir, 'api', 'app', 'alerts', 'templates')
258-
template_path = os.path.join(templates_dir, f"{template_id}.json")
259-
if not os.path.exists(template_path):
260-
print(f" ⚠️ Template not found: {template_path}")
261-
return None
262-
with open(template_path, 'r') as f:
263-
return json.load(f)
292+
# Try multiple paths: local dev layout and Docker container layout
293+
candidate_dirs = [
294+
os.path.join(backend_dir, 'api', 'app', 'alerts', 'templates'), # local dev
295+
os.path.join(backend_dir, 'app', 'alerts', 'templates'), # Docker (/app/app/alerts/templates)
296+
]
297+
for templates_dir in candidate_dirs:
298+
template_path = os.path.join(templates_dir, f"{template_id}.json")
299+
if os.path.exists(template_path):
300+
with open(template_path, 'r') as f:
301+
return json.load(f)
302+
print(f" ⚠️ Template not found: {template_id}.json (searched {candidate_dirs})")
303+
return None
264304

265305

266306
def send_phase_alert(
@@ -301,19 +341,32 @@ def send_phase_alert(
301341
alert["metadata"]["logged_time"] = time_ms
302342
alert["metadata"]["modified_time"] = time_ms
303343

304-
# Set resource - use XDR Asset ID if available for linking to real endpoint
305-
xdr_asset_id = uam_config.get('xdr_asset_id')
306-
if xdr_asset_id:
307-
resource_name = uam_config.get('xdr_asset_name', VICTIM_PROFILE["email"])
308-
alert["resources"] = [{
309-
"uid": xdr_asset_id,
310-
"name": resource_name
311-
}]
312-
else:
344+
# Set resource - use XDR Asset ID for endpoint alerts, shared GUID for email/user alerts
345+
target_machine = mapping.get("target_machine", "bridge")
346+
if target_machine == "email":
347+
# Proofpoint/M365 alerts link to the user email with a consistent GUID
348+
email_asset_uid = uam_config.get('email_asset_uid')
349+
if not email_asset_uid:
350+
email_asset_uid = str(uuid.uuid5(uuid.NAMESPACE_DNS, VICTIM_PROFILE["email"]))
351+
uam_config['email_asset_uid'] = email_asset_uid
313352
alert["resources"] = [{
314-
"uid": str(uuid.uuid4()),
353+
"uid": email_asset_uid,
315354
"name": VICTIM_PROFILE["email"]
316355
}]
356+
elif target_machine == "enterprise":
357+
xdr_asset_id = uam_config.get('xdr_asset_id_enterprise')
358+
xdr_asset_name = uam_config.get('xdr_asset_name_enterprise', VICTIM_PROFILE["machine_enterprise"])
359+
if xdr_asset_id:
360+
alert["resources"] = [{"uid": xdr_asset_id, "name": xdr_asset_name}]
361+
else:
362+
alert["resources"] = [{"uid": str(uuid.uuid4()), "name": VICTIM_PROFILE["machine_enterprise"]}]
363+
else:
364+
xdr_asset_id = uam_config.get('xdr_asset_id_bridge')
365+
xdr_asset_name = uam_config.get('xdr_asset_name_bridge', VICTIM_PROFILE["machine_bridge"])
366+
if xdr_asset_id:
367+
alert["resources"] = [{"uid": xdr_asset_id, "name": xdr_asset_name}]
368+
else:
369+
alert["resources"] = [{"uid": str(uuid.uuid4()), "name": VICTIM_PROFILE["machine_bridge"]}]
317370

318371
# Apply overrides
319372
overrides = mapping.get("overrides", {})
@@ -682,16 +735,16 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
682735
'uam_site_id': uam_site_id,
683736
}
684737

685-
# Look up bridge XDR Asset ID for linking alerts to real endpoint
738+
# Look up Bridge and Enterprise XDR Asset IDs for linking alerts to real endpoints
686739
s1_mgmt_url = os.getenv('S1_MANAGEMENT_URL', '')
687740
s1_api_token = os.getenv('S1_API_TOKEN', '')
688741
if s1_mgmt_url and s1_api_token:
689742
bridge_name = VICTIM_PROFILE['machine_bridge']
690-
print(f"\n🔍 Looking up XDR asset '{bridge_name}' for alert linking...")
743+
enterprise_name = VICTIM_PROFILE['machine_enterprise']
744+
print(f"\n🔍 Looking up XDR assets for alert linking...")
691745
try:
692746
import urllib.request
693747
import urllib.parse
694-
# Use XDR assets endpoint — returns the Asset ID needed for resource UID linking
695748
params = {"accountIds": uam_account_id}
696749
if uam_site_id:
697750
params["siteIds"] = uam_site_id
@@ -703,22 +756,27 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
703756
with urllib.request.urlopen(req, timeout=15) as resp:
704757
assets_data = json.loads(resp.read().decode())
705758
assets = assets_data.get("data", [])
706-
# Find the real agent asset (has 'agent' field), matching by name
759+
# Find real agent assets (have 'agent' field) for both machines
707760
for asset in assets:
708-
if asset.get("name", "").lower() == bridge_name.lower() and asset.get("agent"):
709-
asset_id = asset.get("id", "")
710-
agent_name = asset.get("name", "")
711-
agent_uuid = asset.get("agent", {}).get("uuid", "")
712-
print(f" ✓ XDR Asset found: {agent_name}")
713-
print(f" Asset ID: {asset_id}")
714-
print(f" Agent UUID: {agent_uuid}")
715-
print(f" Category: {asset.get('category')}")
716-
uam_config['xdr_asset_id'] = asset_id
717-
uam_config['xdr_asset_name'] = agent_name
718-
break
719-
else:
761+
if not asset.get("agent"):
762+
continue
763+
name = asset.get("name", "").lower()
764+
asset_id = asset.get("id", "")
765+
agent_uuid = asset.get("agent", {}).get("uuid", "")
766+
if name == bridge_name.lower():
767+
print(f" ✓ Bridge asset: {asset.get('name')}{asset_id}")
768+
uam_config['xdr_asset_id_bridge'] = asset_id
769+
uam_config['xdr_asset_name_bridge'] = asset.get("name", "")
770+
elif name == enterprise_name.lower():
771+
print(f" ✓ Enterprise asset: {asset.get('name')}{asset_id}")
772+
uam_config['xdr_asset_id_enterprise'] = asset_id
773+
uam_config['xdr_asset_name_enterprise'] = asset.get("name", "")
774+
775+
if not uam_config.get('xdr_asset_id_bridge'):
720776
print(f" ⚠ No XDR agent asset found for '{bridge_name}'")
721-
print(f" Found {len(assets)} total assets")
777+
if not uam_config.get('xdr_asset_id_enterprise'):
778+
print(f" ⚠ No XDR agent asset found for '{enterprise_name}'")
779+
print(f" Scanned {len(assets)} total assets")
722780
except Exception as e:
723781
print(f" ⚠ XDR asset lookup failed: {e}")
724782

@@ -776,6 +834,20 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
776834
success = send_phase_alert("rdp_download", phase_base_time, uam_config)
777835
print(f"{'✓' if success else '✗'}")
778836

837+
# Send standalone WEL alerts (not tied to a specific event generation phase)
838+
if alerts_enabled:
839+
print(f"\n🔔 SENDING WEL ALERTS")
840+
wel_alerts = [
841+
("wel_hidden_schtask_bridge", "Hidden Scheduled Task → Bridge"),
842+
("wel_hidden_schtask_enterprise", "Hidden Scheduled Task → Enterprise"),
843+
("wel_brute_force_enterprise", "Brute Force Success → Enterprise"),
844+
("wel_ad_admin_group_enterprise", "AD Global Admin Group → Enterprise"),
845+
]
846+
for alert_key, alert_desc in wel_alerts:
847+
print(f" 📤 {alert_desc}...", end=" ")
848+
success = send_phase_alert(alert_key, base_time, uam_config)
849+
print(f"{'✓' if success else '✗'}")
850+
779851
all_events.sort(key=lambda x: x["timestamp"])
780852

781853
scenario = {

0 commit comments

Comments
 (0)