Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"attack_surface_ids": [1],
"severity_id": 4,
"state_id": 1,
"s1_classification_id": 1
"s1_classification_id": 28
}
4 changes: 4 additions & 0 deletions Backend/api/app/routers/scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class CorrelationRunRequest(BaseModel):
tag_trace: bool = True
workers: int = 10
overwrite_parser: bool = False
suppress_alerts: bool = False
strip_helios_prefix: bool = False

# Initialize scenario service
scenario_service = ScenarioService()
Expand Down Expand Up @@ -321,6 +323,8 @@ async def run_correlation_scenario(
tag_phase=request.tag_phase,
tag_trace=request.tag_trace,
overwrite_parser=request.overwrite_parser,
suppress_alerts=request.suppress_alerts,
strip_helios_prefix=request.strip_helios_prefix,
background_tasks=background_tasks
)

Expand Down
18 changes: 15 additions & 3 deletions Backend/api/app/services/scenario_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ async def start_correlation_scenario(
speed: str = "fast",
dry_run: bool = False,
overwrite_parser: bool = False,
suppress_alerts: bool = False,
strip_helios_prefix: bool = False,
background_tasks=None
) -> str:
"""Start correlation scenario execution with SIEM context and trace ID support"""
Expand All @@ -309,6 +311,8 @@ async def start_correlation_scenario(
"tag_phase": tag_phase,
"tag_trace": tag_trace,
"overwrite_parser": overwrite_parser,
"suppress_alerts": suppress_alerts,
"strip_helios_prefix": strip_helios_prefix,
"progress": 0
}

Expand All @@ -320,7 +324,9 @@ async def start_correlation_scenario(
siem_context,
trace_id,
tag_phase,
tag_trace
tag_trace,
suppress_alerts,
strip_helios_prefix
)

return execution_id
Expand All @@ -332,7 +338,9 @@ async def _execute_correlation_scenario(
siem_context: Dict[str, Any],
trace_id: Optional[str] = None,
tag_phase: bool = True,
tag_trace: bool = True
tag_trace: bool = True,
suppress_alerts: bool = False,
strip_helios_prefix: bool = False
):
"""Execute correlation scenario with SIEM context and trace ID support"""
import sys
Expand All @@ -357,7 +365,11 @@ async def _execute_correlation_scenario(

# Import and run the scenario
module = __import__(scenario_id)
scenario_result = module.generate_apollo_ransomware_scenario(siem_context=siem_context)
scenario_result = module.generate_apollo_ransomware_scenario(
siem_context=siem_context,
suppress_alerts=suppress_alerts,
strip_helios_prefix=strip_helios_prefix,
)

# Update execution status
if execution_id in self.running_scenarios:
Expand Down
42 changes: 29 additions & 13 deletions Backend/scenarios/apollo_ransomware_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ def load_alert_template(template_id: str) -> Optional[Dict]:
def send_phase_alert(
phase_name: str,
base_time: datetime,
uam_config: dict
uam_config: dict,
strip_helios_prefix: bool = False
) -> bool:
"""Send alert for a specific phase with correct timing.

Expand Down Expand Up @@ -341,16 +342,12 @@ def send_phase_alert(
alert["metadata"]["logged_time"] = time_ms
alert["metadata"]["modified_time"] = time_ms

# Set resource - use XDR Asset ID for endpoint alerts, shared GUID for email/user alerts
# Set resource - use XDR Asset ID for endpoint alerts, fresh UUID for email/user alerts
target_machine = mapping.get("target_machine", "bridge")
if target_machine == "email":
# Proofpoint/M365 alerts link to the user email with a consistent GUID
email_asset_uid = uam_config.get('email_asset_uid')
if not email_asset_uid:
email_asset_uid = str(uuid.uuid5(uuid.NAMESPACE_DNS, VICTIM_PROFILE["email"]))
uam_config['email_asset_uid'] = email_asset_uid
# Fresh UUID each time — S1 UAM silently drops site-scoped alerts with static resource UIDs
alert["resources"] = [{
"uid": email_asset_uid,
"uid": str(uuid.uuid4()),
"name": VICTIM_PROFILE["email"]
}]
elif target_machine == "enterprise":
Expand Down Expand Up @@ -382,6 +379,12 @@ def send_phase_alert(
else:
alert[key] = value

# Strip "HELIOS - " prefix from alert title if requested
if strip_helios_prefix:
title = alert.get("finding_info", {}).get("title", "")
if title.startswith("HELIOS - "):
alert["finding_info"]["title"] = title[len("HELIOS - "):]

# Send alert via UAM ingest API
try:
ingest_url = uam_config['uam_ingest_url'].rstrip('/') + '/v1/alerts'
Expand Down Expand Up @@ -667,15 +670,26 @@ def generate_m365_sharepoint_exfil(base_time: datetime) -> List[Dict]:
return events


def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> Dict:
def generate_apollo_ransomware_scenario(
siem_context: Optional[Dict] = None,
suppress_alerts: Optional[bool] = None,
strip_helios_prefix: Optional[bool] = None,
) -> Dict:
"""Generate the complete Apollo ransomware scenario (Proofpoint + M365 only)

Args:
siem_context: Optional dict with SIEM query results for timestamp correlation.
Expected format: {"results": [...], "anchors": {...}}
If provided, timestamps are calculated relative to existing EDR/WEL data.
If None, falls back to offset from current time.
suppress_alerts: If True, skip sending UAM alerts. Env fallback: SCENARIO_SUPPRESS_ALERTS.
strip_helios_prefix: If True, remove "HELIOS - " prefix from alert titles. Env fallback: SCENARIO_STRIP_HELIOS_PREFIX.
"""
# Resolve options from args or env vars
if suppress_alerts is None:
suppress_alerts = os.getenv('SCENARIO_SUPPRESS_ALERTS', 'false').lower() == 'true'
if strip_helios_prefix is None:
strip_helios_prefix = os.getenv('SCENARIO_STRIP_HELIOS_PREFIX', 'false').lower() == 'true'

# Determine base time based on SIEM context or fallback
use_correlation = False
Expand Down Expand Up @@ -718,7 +732,7 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
print("=" * 80 + "\n")

# Initialize alert detonation from env vars
alerts_enabled = os.getenv('SCENARIO_ALERTS_ENABLED', 'false').lower() == 'true'
alerts_enabled = (not suppress_alerts) and os.getenv('SCENARIO_ALERTS_ENABLED', 'false').lower() == 'true'
uam_config = None

if alerts_enabled:
Expand Down Expand Up @@ -783,6 +797,8 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
print("\n🚨 ALERT DETONATION ENABLED")
print(f" UAM Ingest: {uam_ingest_url}")
print(f" Account ID: {uam_account_id}")
if strip_helios_prefix:
print(f" Strip HELIOS prefix: Yes")
print("=" * 80)
else:
print("⚠️ SCENARIO_ALERTS_ENABLED=true but UAM credentials missing")
Expand Down Expand Up @@ -825,13 +841,13 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
# Send corresponding alert if enabled and phase has alert mapping
if alerts_enabled and phase_name in ALERT_PHASE_MAPPING:
print(f" 📤 Sending alert for {phase_name}...", end=" ")
success = send_phase_alert(phase_name, phase_base_time, uam_config)
success = send_phase_alert(phase_name, phase_base_time, uam_config, strip_helios_prefix=strip_helios_prefix)
print(f"{'✓' if success else '✗'}")

# Send RDP alert after data exfiltration phase
if alerts_enabled and phase_name == "📤 PHASE 4: Data Exfiltration":
print(f" 📤 Sending RDP download alert...", end=" ")
success = send_phase_alert("rdp_download", phase_base_time, uam_config)
success = send_phase_alert("rdp_download", phase_base_time, uam_config, strip_helios_prefix=strip_helios_prefix)
print(f"{'✓' if success else '✗'}")

# Send standalone WEL alerts (not tied to a specific event generation phase)
Expand All @@ -845,7 +861,7 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
]
for alert_key, alert_desc in wel_alerts:
print(f" 📤 {alert_desc}...", end=" ")
success = send_phase_alert(alert_key, base_time, uam_config)
success = send_phase_alert(alert_key, base_time, uam_config, strip_helios_prefix=strip_helios_prefix)
print(f"{'✓' if success else '✗'}")

all_events.sort(key=lambda x: x["timestamp"])
Expand Down
11 changes: 10 additions & 1 deletion Frontend/log_generator_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,8 @@ def run_correlation_scenario():
trace_id = (data.get('trace_id') or '').strip()
local_token = data.get('hec_token')
overwrite_parser = data.get('overwrite_parser', False)
suppress_alerts = data.get('suppress_alerts', False)
strip_helios_prefix = data.get('strip_helios_prefix', False)

if not scenario_id:
return jsonify({'error': 'scenario_id is required'}), 400
Expand Down Expand Up @@ -815,7 +817,14 @@ def generate_and_stream():
yield "INFO: ⚠️ S1 asset linking disabled (no S1 API token on destination)\n"
except Exception as s1e:
logger.warning(f"Could not resolve S1 API token: {s1e}")
yield "INFO: 🚨 Alert detonation enabled (UAM credentials found)\n"
if suppress_alerts:
env['SCENARIO_SUPPRESS_ALERTS'] = 'true'
yield "INFO: 🔇 Alert detonation suppressed by user\n"
else:
yield "INFO: 🚨 Alert detonation enabled (UAM credentials found)\n"
if strip_helios_prefix:
env['SCENARIO_STRIP_HELIOS_PREFIX'] = 'true'
yield "INFO: ✂️ HELIOS prefix will be stripped from alert titles\n"
else:
yield "INFO: ⚠️ Alert detonation disabled (no UAM credentials on destination)\n"

Expand Down
19 changes: 19 additions & 0 deletions Frontend/templates/log_generator.html
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,21 @@ <h4 class="text-sm font-semibold text-[#c8b3e8] mb-3">Discovered Time Anchors</h
<p class="text-xs text-[#b8a0d2]">If checked, will overwrite existing parsers in the destination SIEM instead of skipping them. Use with caution.</p>
</div>
</details>
<details>
<summary class="cursor-pointer text-sm font-semibold text-[#c8b3e8]">Alert Options</summary>
<div class="mt-3 space-y-2 text-sm">
<label class="flex items-center gap-2">
<input type="checkbox" id="correlation-suppress-alerts">
<span class="text-[#f3e8ff]">Suppress alerts</span>
</label>
<p class="text-xs text-[#b8a0d2]">Skip sending UAM alerts even if credentials are configured on the destination.</p>
<label class="flex items-center gap-2">
<input type="checkbox" id="correlation-strip-helios-prefix">
<span class="text-[#f3e8ff]">Strip <code>HELIOS -</code> prefix from alert titles</span>
</label>
<p class="text-xs text-[#b8a0d2]">Remove the "HELIOS - " prefix from alert titles before sending to S1.</p>
</div>
</details>

<div class="flex gap-3">
<button type="button" id="run-correlation-scenario-btn" class="btn flex-1" disabled>
Expand Down Expand Up @@ -3487,6 +3502,8 @@ <h3 class="text-xl font-bold text-[#f3e8ff]">Select Parser Version</h3>
const tagPhase = document.getElementById('correlation-tag-phase').checked;
const tagTrace = document.getElementById('correlation-tag-trace').checked;
const overwriteParser = document.getElementById('correlation-overwrite-parser').checked;
const suppressAlerts = document.getElementById('correlation-suppress-alerts').checked;
const stripHeliosPrefix = document.getElementById('correlation-strip-helios-prefix').checked;
let traceId = correlationTraceIdInput.value.trim();

if (tagTrace && !traceId) {
Expand Down Expand Up @@ -3519,6 +3536,8 @@ <h3 class="text-xl font-bold text-[#f3e8ff]">Select Parser Version</h3>
tag_trace: tagTrace,
trace_id: traceId,
overwrite_parser: overwriteParser,
suppress_alerts: suppressAlerts,
strip_helios_prefix: stripHeliosPrefix,
hec_token: localToken
})
});
Expand Down
Loading