Download tasks.py from netha01/SOCEnv: direct link, hf CLI and curl.
- Browser
- Download file 20.7 kB
-
https://huggingface.co/spaces/netha01/SOCEnv/resolve/main/tasks.py
- Command line
-
hf download hf://spaces/netha01/SOCEnv/tasks.py
-
curl -L -o tasks.py https://huggingface.co/spaces/netha01/SOCEnv/resolve/main/tasks.py
20.7 kB
| # tasks.py – scenario definitions for the AI SOC Gym | |
| """ | |
| This module defines the attack scenarios (tasks) that drive the hidden | |
| `AttackState` inside `environment.py`. Each task implements a tiny | |
| interface (`BaseTask`) used by the environment to generate logs and to | |
| report whether the analyst has successfully mitigated the attack. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import TYPE_CHECKING, List, Set | |
| from models import Action, ActionType, LogEntry | |
| if TYPE_CHECKING: | |
| from environment import AIGymEnv | |
| # ---------------------------------------------------------------------- | |
| # BaseTask – minimal contract for every scenario | |
| # ---------------------------------------------------------------------- | |
| class BaseTask: | |
| def initialize(self, env: AIGymEnv) -> None: | |
| """Set up hidden state before the first reset.""" | |
| raise NotImplementedError | |
| def advance(self, env: AIGymEnv) -> List[LogEntry]: | |
| """Return task‑specific logs for the current step.""" | |
| raise NotImplementedError | |
| # Most tasks will override this; default = never mitigated. | |
| def is_mitigated(self, env: AIGymEnv) -> bool: # pragma: no cover | |
| return False | |
| # ---------------------------------------------------------------------- | |
| # Task 1 – Brute‑Force SSH (easy) | |
| # ---------------------------------------------------------------------- | |
| class BruteForceSSHTask(BaseTask): | |
| attacker_ip: str = "203.0.113.5" | |
| max_failures: int = 4 | |
| investigation_done: bool = False | |
| _failed: int = field(default=0, init=False) | |
| _success: bool = field(default=False, init=False) | |
| def initialize(self, env: AIGymEnv) -> None: | |
| env._state.stage = "initial_access" | |
| env._state.attacker_ips = [self.attacker_ip] | |
| self._failed = 0 | |
| self._success = False | |
| self.investigation_done = False | |
| def advance(self, env: AIGymEnv) -> List[LogEntry]: | |
| logs: List[LogEntry] = [] | |
| if not self._success: | |
| if self._failed < self.max_failures: | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="ssh", | |
| severity="WARN", | |
| message=f"Failed password for invalid user admin from {self.attacker_ip}", | |
| ip=self.attacker_ip, | |
| user="admin", | |
| hostname="gateway.corp", | |
| event_type="login", | |
| ) | |
| ) | |
| self._failed += 1 | |
| return logs | |
| # success path – attacker gains foothold on gateway | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="ssh", | |
| severity="INFO", | |
| message=f"Accepted password for root from {self.attacker_ip}", | |
| ip=self.attacker_ip, | |
| user="root", | |
| hostname="gateway.corp", | |
| event_type="login", | |
| ) | |
| ) | |
| self._success = True | |
| env._state.compromised_hosts.append("gateway.corp") | |
| env._state.data_exfiltrated = True | |
| return logs | |
| def is_mitigated(self, env: AIGymEnv) -> bool: | |
| return self.attacker_ip in env._blocked_ips | |
| # ---------------------------------------------------------------------- | |
| # Task 2 – Lateral Movement (medium difficulty) | |
| # ---------------------------------------------------------------------- | |
| class LateralMovementTask(BaseTask): | |
| """Medium scenario – external brute‑force, pivot host, internal hop. | |
| Steps: | |
| 1️⃣ Brute‑force external login. | |
| 2️⃣ Compromise **pivot_host** and SSH to **internal_host**. | |
| 3️⃣ Create admin on **internal_host**. | |
| The analyst must eventually isolate a compromised host. | |
| """ | |
| attacker_ip: str = "203.0.113.5" | |
| max_failures: int = 3 | |
| pivot_host: str = "host-12.corp" | |
| internal_host: str = "host-27.corp" | |
| # internal counters – kept on the task instance | |
| _external_failed: int = field(default=0, init=False) | |
| _internal_failed: int = field(default=0, init=False) | |
| _pivot_done: bool = field(default=False, init=False) | |
| _internal_done: bool = field(default=False, init=False) | |
| hosts_investigated: Set[str] = field(default_factory=set, init=False) | |
| def initialize(self, env: AIGymEnv) -> None: | |
| env._state.stage = "initial_access" | |
| env._state.attacker_ips = [self.attacker_ip] | |
| env._state.pivot_host = self.pivot_host | |
| env._state.internal_host = self.internal_host | |
| self._external_failed = 0 | |
| self._internal_failed = 0 | |
| self._pivot_done = False | |
| self._internal_done = False | |
| self.hosts_investigated = set() | |
| def advance(self, env: AIGymEnv) -> List[LogEntry]: | |
| logs: List[LogEntry] = [] | |
| # ----- Phase 1 – external brute‑force ----- | |
| if not self._pivot_done: | |
| if self._external_failed < self.max_failures: | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="ssh", | |
| severity="WARN", | |
| message=f"Failed password for invalid user admin from {self.attacker_ip}", | |
| ip=self.attacker_ip, | |
| user="admin", | |
| hostname="gateway.corp", | |
| event_type="login", | |
| ) | |
| ) | |
| self._external_failed += 1 | |
| return logs | |
| # success – pivot host compromised | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="ssh", | |
| severity="INFO", | |
| message=f"Accepted password for svc_user from {self.attacker_ip} on {self.pivot_host}", | |
| ip=self.attacker_ip, | |
| user="svc_user", | |
| hostname=self.pivot_host, | |
| event_type="login", | |
| ) | |
| ) | |
| env._state.compromised_hosts.append(self.pivot_host) | |
| self._pivot_done = True | |
| return logs | |
| # ----- Phase 2 – internal SSH hop ----- | |
| if self._pivot_done and not self._internal_done: | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="ssh", | |
| severity="INFO", | |
| message=f"{self.pivot_host} initiated SSH connection to {self.internal_host}", | |
| ip=self.pivot_host, | |
| user="svc_user", | |
| hostname=self.internal_host, | |
| event_type="network_conn", | |
| ) | |
| ) | |
| # after a couple of steps the internal host gets admin rights | |
| if self._internal_failed >= 2: | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="ssh", | |
| severity="INFO", | |
| message=f"New admin account created on {self.internal_host} by svc_user", | |
| ip=self.internal_host, | |
| user="admin", | |
| hostname=self.internal_host, | |
| event_type="login", | |
| ) | |
| ) | |
| env._state.compromised_hosts.append(self.internal_host) | |
| self._internal_done = True | |
| env._state.data_exfiltrated = True | |
| else: | |
| self._internal_failed += 1 | |
| return logs | |
| # Phase 3 – attack already succeeded – nothing extra to emit | |
| return logs | |
| def is_mitigated(self, env: AIGymEnv) -> bool: | |
| compromised = set(env._state.compromised_hosts) | |
| isolated = set(env._isolated_hosts) | |
| return len(compromised & isolated) > 0 | |
| # ---------------------------------------------------------------------- | |
| # Task 3 – APT Multi‑Stage (hard difficulty) | |
| # ---------------------------------------------------------------------- | |
| class APTMultiStageTask(BaseTask): | |
| """Hard scenario — a full Advanced Persistent Threat kill-chain. | |
| Phases: | |
| 1️⃣ **Phishing** — Spear‑phishing email delivers a malicious document | |
| to a user workstation. The logs show an inbound email with a | |
| suspicious attachment, followed by an outbound C2 beacon. | |
| 2️⃣ **Credential Theft** — The compromised workstation runs a | |
| credential‑dumping tool (Mimikatz‑style). Logs show LSASS | |
| access and new logon events with harvested service accounts. | |
| 3️⃣ **Lateral Movement** — Stolen creds are used to hop through | |
| two internal hosts. Logs show WinRM / SSH connections between | |
| internal machines. | |
| 4️⃣ **Exfiltration** — Attacker stages data on a staging host and | |
| tunnels it out via DNS over HTTPS. Logs show large DNS‑TXT | |
| responses and unusual outbound traffic volume. | |
| Mitigation requires the analyst to: | |
| • Isolate the phished workstation **AND** | |
| • Block the C2 IP | |
| Partial credit is given for blocking C2 alone or isolating any | |
| compromised host. | |
| """ | |
| # ---------- configurable knobs ---------- | |
| c2_ip: str = "198.51.100.23" | |
| phish_target_user: str = "jdoe" | |
| phished_host: str = "ws-PC042.corp" | |
| staging_host: str = "file-srv-01.corp" | |
| dc_host: str = "dc-01.corp" | |
| max_phish_steps: int = 2 # steps before the user clicks the link | |
| # ---------- internal counters ---------- | |
| _phase: int = field(default=0, init=False) # 0–4 | |
| _phase_counter: int = field(default=0, init=False) # steps within phase | |
| _user_clicked: bool = field(default=False, init=False) | |
| _creds_dumped: bool = field(default=False, init=False) | |
| _lateral_hops: int = field(default=0, init=False) | |
| _exfil_started: bool = field(default=False, init=False) | |
| hosts_investigated: Set[str] = field(default_factory=set, init=False) | |
| def initialize(self, env: AIGymEnv) -> None: | |
| env._state.stage = "initial_access" | |
| env._state.attacker_ips = [self.c2_ip] | |
| env._state.pivot_host = self.phished_host | |
| env._state.internal_host = self.staging_host | |
| self._phase = 0 | |
| self._phase_counter = 0 | |
| self._user_clicked = False | |
| self._creds_dumped = False | |
| self._lateral_hops = 0 | |
| self._exfil_started = False | |
| self.hosts_investigated = set() | |
| # ------------------------------------------------------------------ | |
| def advance(self, env: AIGymEnv) -> List[LogEntry]: | |
| logs: List[LogEntry] = [] | |
| self._phase_counter += 1 | |
| # ---- Phase 0 : Phishing delivery ---- | |
| if self._phase == 0: | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="email", | |
| severity="INFO", | |
| message=( | |
| f"Inbound email to {self.phish_target_user}@corp.local " | |
| f"from hr-benefits@secure‑docs.com " | |
| f"subject='Q1 Compensation Update' attachment=Q1_Update.xlsm" | |
| ), | |
| ip=self.c2_ip, | |
| user=self.phish_target_user, | |
| hostname=self.phished_host, | |
| event_type="email", | |
| ) | |
| ) | |
| if self._phase_counter >= self.max_phish_steps: | |
| # User clicks the doc — macro fires | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="endpoint", | |
| severity="WARN", | |
| message=( | |
| f"EXCEL.EXE spawned powershell.exe on {self.phished_host} " | |
| f"(user={self.phish_target_user})" | |
| ), | |
| ip=None, | |
| user=self.phish_target_user, | |
| hostname=self.phished_host, | |
| event_type="process", | |
| ) | |
| ) | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="firewall", | |
| severity="WARN", | |
| message=( | |
| f"Outbound HTTPS connection from {self.phished_host} " | |
| f"to {self.c2_ip}:443 (TLS SNI: cdn‑assets.com)" | |
| ), | |
| ip=self.c2_ip, | |
| user=self.phish_target_user, | |
| hostname=self.phished_host, | |
| event_type="network_conn", | |
| ) | |
| ) | |
| self._user_clicked = True | |
| env._state.compromised_hosts.append(self.phished_host) | |
| self._phase = 1 | |
| self._phase_counter = 0 | |
| return logs | |
| # ---- Phase 1 : Credential theft ---- | |
| if self._phase == 1: | |
| # First step in this phase — LSASS access | |
| if not self._creds_dumped: | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="endpoint", | |
| severity="CRITICAL", | |
| message=( | |
| f"Suspicious LSASS memory access on {self.phished_host} " | |
| f"by process rundll32.exe (PID 4728)" | |
| ), | |
| ip=None, | |
| user="SYSTEM", | |
| hostname=self.phished_host, | |
| event_type="process", | |
| ) | |
| ) | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="auth", | |
| severity="WARN", | |
| message=( | |
| f"New logon: svc_backup@corp.local on {self.phished_host} " | |
| f"(logon type 9 — NewCredentials)" | |
| ), | |
| ip=None, | |
| user="svc_backup", | |
| hostname=self.phished_host, | |
| event_type="login", | |
| ) | |
| ) | |
| self._creds_dumped = True | |
| env._state.stage = "lateral_movement" | |
| self._phase = 2 | |
| self._phase_counter = 0 | |
| return logs | |
| # ---- Phase 2 : Lateral movement (2 hops) ---- | |
| if self._phase == 2: | |
| if self._lateral_hops == 0: | |
| # Hop 1: phished_host → dc_host | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="auth", | |
| severity="WARN", | |
| message=( | |
| f"Kerberos TGS request for host/{self.dc_host} " | |
| f"from {self.phished_host} (user=svc_backup)" | |
| ), | |
| ip=self.phished_host, | |
| user="svc_backup", | |
| hostname=self.dc_host, | |
| event_type="login", | |
| ) | |
| ) | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="ssh", | |
| severity="INFO", | |
| message=( | |
| f"WinRM session opened on {self.dc_host} " | |
| f"by svc_backup from {self.phished_host}" | |
| ), | |
| ip=self.phished_host, | |
| user="svc_backup", | |
| hostname=self.dc_host, | |
| event_type="network_conn", | |
| ) | |
| ) | |
| env._state.compromised_hosts.append(self.dc_host) | |
| self._lateral_hops = 1 | |
| return logs | |
| if self._lateral_hops == 1: | |
| # Hop 2: dc_host → staging_host | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="ssh", | |
| severity="INFO", | |
| message=( | |
| f"{self.dc_host} initiated SMB connection to " | |
| f"{self.staging_host} (share: \\\\{self.staging_host}\\data$)" | |
| ), | |
| ip=self.dc_host, | |
| user="svc_backup", | |
| hostname=self.staging_host, | |
| event_type="network_conn", | |
| ) | |
| ) | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="endpoint", | |
| severity="WARN", | |
| message=( | |
| f"Batch file copy: 847 files (2.3 GB) staged in " | |
| f"C:\\Windows\\Temp\\~cache on {self.staging_host}" | |
| ), | |
| ip=None, | |
| user="svc_backup", | |
| hostname=self.staging_host, | |
| event_type="file_access", | |
| ) | |
| ) | |
| env._state.compromised_hosts.append(self.staging_host) | |
| self._lateral_hops = 2 | |
| env._state.stage = "exfiltration" | |
| self._phase = 3 | |
| self._phase_counter = 0 | |
| return logs | |
| return logs | |
| # ---- Phase 3 : Exfiltration via DNS tunneling ---- | |
| if self._phase == 3: | |
| if not self._exfil_started: | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="dns", | |
| severity="WARN", | |
| message=( | |
| f"Anomalous DNS-over-HTTPS traffic from {self.staging_host} " | |
| f"to {self.c2_ip} — 340 TXT queries in 60 s " | |
| f"(avg payload 220 bytes)" | |
| ), | |
| ip=self.c2_ip, | |
| user=None, | |
| hostname=self.staging_host, | |
| event_type="network_conn", | |
| ) | |
| ) | |
| logs.append( | |
| LogEntry( | |
| timestamp=env._rand_timestamp(), | |
| source="firewall", | |
| severity="CRITICAL", | |
| message=( | |
| f"High-volume outbound: {self.staging_host} → " | |
| f"{self.c2_ip}:443 | 2.1 GB transferred in 180 s" | |
| ), | |
| ip=self.c2_ip, | |
| user=None, | |
| hostname=self.staging_host, | |
| event_type="network_conn", | |
| ) | |
| ) | |
| self._exfil_started = True | |
| if self._phase_counter >= 2: | |
| env._state.data_exfiltrated = True | |
| return logs | |
| return logs | |
| # ------------------------------------------------------------------ | |
| def is_mitigated(self, env: AIGymEnv) -> bool: | |
| """Full mitigation requires BOTH: | |
| 1. The C2 IP is blocked OR the phished host is isolated. | |
| 2. At least one compromised host is isolated. | |
| """ | |
| c2_blocked = self.c2_ip in env._blocked_ips | |
| phished_isolated = self.phished_host in env._isolated_hosts | |
| any_compromised_isolated = bool( | |
| set(env._state.compromised_hosts) & set(env._isolated_hosts) | |
| ) | |
| return (c2_blocked or phished_isolated) and any_compromised_isolated | |
| # ---------------------------------------------------------------------- | |
| # Exported symbols for `import *` | |
| # ---------------------------------------------------------------------- | |
| __all__ = [ | |
| "BaseTask", | |
| "BruteForceSSHTask", | |
| "LateralMovementTask", | |
| "APTMultiStageTask", | |
| ] | |