{
  "hunt": {
    "meta": {
      "tlp": "clear",
      "hunt": {
        "handoff": "promote-to-detection",
        "trigger": "intel-report",
        "methodology": "model-assisted",
        "applicability": "campaign-specific",
        "justification": "Hijacked cloud identities provide a path to production environments that bypasses malware detection; monitoring SSPR and discovery activity is vital for supply chain protection."
      },
      "name": "Cloud Identity Takeover and DevOps Enumeration",
      "type": "investigation",
      "labels": [
        "hunt",
        "attack.t1566",
        "attack.t1078",
        "attack.t1087",
        "attack.t1552.001",
        "command and control",
        "credential access",
        "discovery",
        "execution",
        "initial access"
      ],
      "series": {
        "slug": "beyond-source-code-a-path-to-the-keys-to-the-kingdom",
        "index": 1,
        "title": "Beyond source code: A path to the keys to the kingdom",
        "total": 2
      },
      "related": [
        {
          "hunt": "remote-access-tooling-in-dev-pipelines",
          "reason": "The execution of Atera and Chisel within build agents is a separate stage of the campaign focusing on compute telemetry.",
          "relation": "out-of-scope-alternative"
        }
      ],
      "targets": {
        "hunter": {
          "name": "Hunt agent",
          "agent": true
        },
        "analyst": {
          "name": "Tier-2 analyst",
          "role": "analyst"
        },
        "endpoint": {
          "name": "Endpoint telemetry (hb_ surfaces)",
          "category": "endpoint",
          "telemetry": [
            "endpoint"
          ]
        },
        "identity": {
          "name": "Identity / sign-in telemetry",
          "category": "identity",
          "telemetry": [
            "identity"
          ]
        }
      },
      "analysis": "A simple rule detects a password reset, but this hunt connects that reset to automated Azure DevOps resource mapping and Kubernetes credential harvesting\u2014a chain no single-surface rule can see.",
      "coverage": [
        {
          "stage": "identity-compromise-sspr",
          "steps": [
            "sspr-lead",
            "analyze-sspr-lead"
          ],
          "status": "covered"
        },
        {
          "stage": "azure-devops-enumeration",
          "steps": [
            "devops-enumeration-check"
          ],
          "status": "covered"
        },
        {
          "stage": "credential-harvesting-exfiltration",
          "steps": [
            "kubeconfig-access-check"
          ],
          "status": "covered"
        },
        {
          "stage": "malicious-pipeline-execution",
          "reason": "Belongs to another part of the 'Beyond source code: A path to the keys to the kingdom' series.",
          "status": "out_of_scope"
        },
        {
          "stage": "remote-access-tooling",
          "reason": "Belongs to another part of the 'Beyond source code: A path to the keys to the kingdom' series.",
          "status": "out_of_scope"
        }
      ],
      "scenario": {
        "stages": [
          {
            "name": "Identity Takeover via SSPR",
            "slug": "identity-compromise-sspr",
            "tactic": "initial-access",
            "techniques": [
              "T1566",
              "T1078"
            ],
            "observables": [
              "self-service password reset activity",
              "registration of new authentication methods",
              "MFA registration bypass"
            ]
          },
          {
            "name": "Azure DevOps Environment Discovery",
            "slug": "azure-devops-enumeration",
            "tactic": "discovery",
            "techniques": [
              "T1087",
              "T1018"
            ],
            "observables": [
              "enumeration of repositories, projects, pipelines, and deployment environments",
              "automated scripts mapping cloud resources"
            ]
          },
          {
            "name": "Malicious Pipeline Deployment",
            "slug": "malicious-pipeline-execution",
            "tactic": "execution",
            "techniques": [
              "T1059",
              "T1190"
            ],
            "observables": [
              "creation of malicious pipeline",
              "deployment of kube agent",
              "modification of pipeline scripts",
              "execution of pipeline jobs to collect kubeconfig files"
            ]
          },
          {
            "name": "RMM and Tunneling Tooling",
            "slug": "remote-access-tooling",
            "tactic": "command-and-control",
            "techniques": [
              "T1219",
              "T1572"
            ],
            "observables": [
              "Atera remote management agent installation",
              "Chisel tunneling utility download",
              "chisel commands establishing reverse tunnel to external IP"
            ]
          },
          {
            "name": "Kubernetes Credential Harvesting",
            "slug": "credential-harvesting-exfiltration",
            "tactic": "credential-access",
            "techniques": [
              "T1552.001"
            ],
            "observables": [
              "harvesting of kubeconfig files",
              "addition of stolen kubeconfig files to a Git repository",
              "Git version history modification"
            ]
          }
        ],
        "summary": "Storm-3068 compromised a user identity through a self-service password reset and registered their own MFA to gain persistent access. The actor pivoted to Azure DevOps to enumerate repositories and pipelines, then created a malicious pipeline to harvest Kubernetes credentials (kubeconfig) and establish remote access via Atera and Chisel protocol tunneling."
      },
      "severity": "high",
      "rationale": "The hunt uses a software inventory query to focus file activity checks on hosts with developer tools, then uses SSPR activity as a cheap lead to justify deeper DevOps log analysis.",
      "guardrails": {
        "claims": "no_unsupported",
        "evidence": "citation_required",
        "telemetry": "untrusted",
        "missing_data": "not_benign"
      },
      "hypothesis": "An adversary hijacked a cloud identity using self-service password reset to perform automated discovery across Azure DevOps repositories and harvest Kubernetes configuration files.",
      "parameters": {
        "scope_hosts": {
          "type": "list[host]",
          "default": [],
          "description": "Hosts identified as developer workstations or cloud management endpoints; paste results from the scoping query here."
        },
        "lookback_days": {
          "type": "number",
          "default": "14",
          "description": "Days of history to examine."
        },
        "kubeconfig_files": {
          "from": {
            "ref": "msrc-beyond-source-code",
            "kind": "article",
            "observed": "2026-09-29"
          },
          "type": "list[string]",
          "default": [
            "kubeconfig",
            "config",
            "credentials"
          ],
          "description": "Filenames associated with Kubernetes cluster configuration."
        },
        "dev_tool_packages": {
          "type": "list[string]",
          "default": [
            "kubectl",
            "azure-cli",
            "docker",
            "helm"
          ],
          "description": "Package names indicating potential developer or cloud management infrastructure."
        }
      },
      "provenance": {
        "authors": [
          {
            "org": "huntbase.io",
            "name": "Huntbase hunt generation"
          }
        ],
        "generated": {
          "by": "huntbase-hunt-generation",
          "from": "https://www.microsoft.com/en-us/security/blog/2026/09/29/beyond-source-code-a-path-to-the-keys-to-the-kingdom/",
          "gates": [
            "dry-run",
            "lint"
          ],
          "model": "hb_google/gemini-3-flash-preview"
        }
      },
      "references": [
        {
          "url": "https://www.microsoft.com/en-us/security/blog/2026/09/29/beyond-source-code-a-path-to-the-keys-to-the-kingdom/",
          "name": "Beyond source code: A path to the keys to the kingdom"
        }
      ],
      "blind_spots": [
        {
          "id": "mfa-registration-visibility",
          "risk": "Legitimate SSPR followed by MFA registration may be indistinguishable from attacker takeover without method-level auditing.",
          "stage": "identity-compromise-sspr",
          "question": "what specific authentication method was registered by the actor",
          "requires": "hb_account_change with mfa_method column"
        },
        {
          "id": "git-content-visibility",
          "risk": "Audit logs show the commit and filename but not the sensitive content, requiring manual review.",
          "stage": "azure-devops-enumeration",
          "question": "whether the files committed to the repository actually contained valid secrets",
          "requires": "Git version history content auditing"
        }
      ]
    },
    "name": "Cloud Identity Takeover and DevOps Enumeration",
    "description": "Storm-3068 has been observed compromising accounts through self-service password reset (SSPR) to bypass traditional credentials. This hunt identifies the initial takeover in identity logs and then pivots into Azure DevOps audit logs to find high-volume enumeration of repositories and pipelines. Finally, it checks for the access of Kubernetes configuration files on developer workstations. The gated flow ensures that expensive cloud API and file-level queries only run when a suspicious account reset is detected, while the scoping step identifies critical developer infrastructure."
  },
  "nodes": [
    {
      "id": "hypothesis",
      "type": "hypothesis",
      "label": "Hypothesis",
      "config": {
        "tags": [],
        "series": {
          "slug": "beyond-source-code-a-path-to-the-keys-to-the-kingdom",
          "index": 1,
          "title": "Beyond source code: A path to the keys to the kingdom",
          "total": 2
        },
        "coverage": [
          {
            "stage": "identity-compromise-sspr",
            "steps": [
              "sspr-lead",
              "analyze-sspr-lead"
            ],
            "status": "covered"
          },
          {
            "stage": "azure-devops-enumeration",
            "steps": [
              "devops-enumeration-check"
            ],
            "status": "covered"
          },
          {
            "stage": "credential-harvesting-exfiltration",
            "steps": [
              "kubeconfig-access-check"
            ],
            "status": "covered"
          },
          {
            "stage": "malicious-pipeline-execution",
            "reason": "Belongs to another part of the 'Beyond source code: A path to the keys to the kingdom' series.",
            "status": "out_of_scope"
          },
          {
            "stage": "remote-access-tooling",
            "reason": "Belongs to another part of the 'Beyond source code: A path to the keys to the kingdom' series.",
            "status": "out_of_scope"
          }
        ],
        "rationale": "An adversary hijacked a cloud identity using self-service password reset to perform automated discovery across Azure DevOps repositories and harvest Kubernetes configuration files.",
        "blind_spots": [
          {
            "id": "mfa-registration-visibility",
            "risk": "Legitimate SSPR followed by MFA registration may be indistinguishable from attacker takeover without method-level auditing.",
            "stage": "identity-compromise-sspr",
            "question": "what specific authentication method was registered by the actor",
            "requires": "hb_account_change with mfa_method column"
          },
          {
            "id": "git-content-visibility",
            "risk": "Audit logs show the commit and filename but not the sensitive content, requiring manual review.",
            "stage": "azure-devops-enumeration",
            "question": "whether the files committed to the repository actually contained valid secrets",
            "requires": "Git version history content auditing"
          }
        ],
        "scoping_notes": "The hunt uses a software inventory query to focus file activity checks on hosts with developer tools, then uses SSPR activity as a cheap lead to justify deeper DevOps log analysis.",
        "beyond_detection": "A simple rule detects a password reset, but this hunt connects that reset to automated Azure DevOps resource mapping and Kubernetes credential harvesting\u2014a chain no single-surface rule can see."
      }
    },
    {
      "id": "scoping-dev-infrastructure",
      "type": "query",
      "label": "Scope developer infrastructure",
      "config": {
        "dsl": "sqlite",
        "role": "scoping",
        "source": "endpoint",
        "content": "SELECT DISTINCT device_hostname FROM hb_software_inventory WHERE instr(',' || '{{dev_tool_packages}}' || ',', ',' || LOWER(package_name) || ',') > 0",
        "surface": "hb_software_inventory",
        "description": "Identify hosts that run cloud and Kubernetes management tools to focus endpoint file activity checks.",
        "expected_signal": "A list of hostnames belonging to developers or administrators. Silence means no relevant tools are installed."
      },
      "parents": [
        {
          "id": "hypothesis"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "scoping",
        "label": "Scope developer infrastructure",
        "reads": [
          "package_name",
          "device_hostname"
        ],
        "source": "hb_software_inventory",
        "target": "endpoint",
        "content": "SELECT DISTINCT device_hostname FROM hb_software_inventory WHERE instr(',' || '{{dev_tool_packages}}' || ',', ',' || LOWER(package_name) || ',') > 0",
        "silence": "not_evidence_of_absence",
        "expected": "A list of hostnames belonging to developers or administrators. Silence means no relevant tools are installed.",
        "verified": "dry-run",
        "verified_at": "2026-09-30"
      }
    },
    {
      "id": "sspr-lead",
      "type": "query",
      "label": "Identify suspicious password resets",
      "config": {
        "dsl": "sqlite",
        "role": "baseline",
        "source": "endpoint",
        "content": "SELECT user_name, actor_user_name, time, activity_name FROM hb_account_change WHERE activity_id = 4 AND provider = 'm365' AND time >= datetime('now', '-{{lookback_days}} days')",
        "surface": "hb_account_change",
        "description": "Find accounts that performed a self-service password reset as a lead for identity takeover.",
        "expected_signal": "A list of accounts that reset their passwords. Silence proves no SSPR activity occurred in the window."
      },
      "parents": [
        {
          "id": "scoping-dev-infrastructure"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "baseline",
        "label": "Identify suspicious password resets",
        "reads": [
          "user_name",
          "actor_user_name",
          "time",
          "activity_id",
          "provider"
        ],
        "source": "hb_account_change",
        "target": "endpoint",
        "content": "SELECT user_name, actor_user_name, time, activity_name FROM hb_account_change WHERE activity_id = 4 AND provider = 'm365' AND time >= datetime('now', '-{{lookback_days}} days')",
        "silence": "evidence_of_absence",
        "expected": "A list of accounts that reset their passwords. Silence proves no SSPR activity occurred in the window.",
        "verified": "dry-run",
        "verified_at": "2026-09-30"
      }
    },
    {
      "id": "analyze-sspr-lead",
      "type": "analytic",
      "label": "Analyze password reset patterns",
      "config": {
        "cite": "required",
        "tools": [
          "endpoint"
        ],
        "context": [
          "sspr-lead"
        ],
        "objective": "Determine if any account reset was performed by an unusual actor or exhibits patterns of identity hijacking.",
        "description": "Evaluate whether the account reset looks like a potential Storm-3068 takeover.",
        "max_iterations": 3,
        "expected_signal": "A verdict indicating whether the identity change appears suspicious.",
        "success_criteria": "A per-user verdict of suspicious for any identity with irregular reset patterns."
      },
      "parents": [
        {
          "id": "sspr-lead"
        }
      ]
    },
    {
      "id": "gate-on-suspected-takeover",
      "type": "checkpoint",
      "label": "Gate on suspected identity takeover",
      "config": {
        "fuzzy": true,
        "judge": "hunter",
        "question": "the analyze-sspr-lead verdict is suspicious for at least one account",
        "condition": "the analyze-sspr-lead verdict is suspicious for at least one account",
        "blind_spot": "mfa-registration-visibility",
        "confidence": "high",
        "description": "Open the expensive DevOps and file investigation only when the identity lead is real.",
        "checkpoint_type": "mandatory"
      },
      "parents": [
        {
          "id": "analyze-sspr-lead"
        }
      ]
    },
    {
      "id": "devops-enumeration-check",
      "type": "query",
      "label": "Azure DevOps resource enumeration",
      "config": {
        "dsl": "sqlite",
        "role": "detection-candidate",
        "source": "endpoint",
        "content": "SELECT actor_user_name, api_operation, COUNT(DISTINCT resource_name) AS res_count, MIN(time) AS first_seen FROM hb_cloud_api_activity WHERE provider = 'm365' AND (LOWER(api_operation) LIKE '%repository%' OR LOWER(api_operation) LIKE '%pipeline%' OR LOWER(api_operation) LIKE '%project%') AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY actor_user_name, api_operation HAVING res_count > 5",
        "surface": "hb_cloud_api_activity",
        "description": "Identify accounts performing automated discovery across many repositories or pipelines.",
        "expected_signal": "An account mapping more than 5 DevOps objects in the window. Silence means no high-volume enumeration was detected."
      },
      "parents": [
        {
          "id": "gate-on-suspected-takeover",
          "branch": "on_supports"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "detection-candidate",
        "label": "Azure DevOps resource enumeration",
        "reads": [
          "actor_user_name",
          "api_operation",
          "resource_name",
          "time",
          "provider"
        ],
        "source": "hb_cloud_api_activity",
        "target": "endpoint",
        "content": "SELECT actor_user_name, api_operation, COUNT(DISTINCT resource_name) AS res_count, MIN(time) AS first_seen FROM hb_cloud_api_activity WHERE provider = 'm365' AND (LOWER(api_operation) LIKE '%repository%' OR LOWER(api_operation) LIKE '%pipeline%' OR LOWER(api_operation) LIKE '%project%') AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY actor_user_name, api_operation HAVING res_count > 5",
        "silence": "not_evidence_of_absence",
        "baseline": {
          "window": "{{lookback_days}}d",
          "compare": "first_seen"
        },
        "expected": "An account mapping more than 5 DevOps objects in the window. Silence means no high-volume enumeration was detected.",
        "verified": "dry-run",
        "prevalence": {
          "by": "actor_user_name",
          "key": [
            "api_operation"
          ],
          "rare_below": 5
        },
        "verified_at": "2026-09-30"
      }
    },
    {
      "id": "kubeconfig-access-check",
      "type": "query",
      "label": "Kubernetes credential harvesting",
      "config": {
        "dsl": "sqlite",
        "role": "enrichment",
        "source": "endpoint",
        "content": "SELECT device_hostname, actor_user_name, file_path, file_name, time FROM hb_file_activity WHERE instr(',' || '{{kubeconfig_files}}' || ',', ',' || LOWER(file_name) || ',') > 0 AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')",
        "surface": "hb_file_activity",
        "description": "Find file activity involving Kubernetes configuration files on scoped developer hosts.",
        "expected_signal": "Access events on kubeconfig files, particularly by the account identified in the lead. Silence means no such files were touched."
      },
      "parents": [
        {
          "id": "gate-on-suspected-takeover",
          "branch": "on_supports"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "enrichment",
        "label": "Kubernetes credential harvesting",
        "reads": [
          "device_hostname",
          "actor_user_name",
          "file_name",
          "file_path",
          "time"
        ],
        "source": "hb_file_activity",
        "target": "endpoint",
        "content": "SELECT device_hostname, actor_user_name, file_path, file_name, time FROM hb_file_activity WHERE instr(',' || '{{kubeconfig_files}}' || ',', ',' || LOWER(file_name) || ',') > 0 AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')",
        "silence": "not_evidence_of_absence",
        "expected": "Access events on kubeconfig files, particularly by the account identified in the lead. Silence means no such files were touched.",
        "verified": "dry-run",
        "verified_at": "2026-09-30"
      }
    },
    {
      "id": "triage-intrusion-chain",
      "type": "analytic",
      "label": "Weigh the intrusion chain",
      "config": {
        "cite": "required",
        "tools": [
          "endpoint"
        ],
        "context": [
          "analyze-sspr-lead",
          "devops-enumeration-check",
          "kubeconfig-access-check"
        ],
        "objective": "Review the suspected identity reset and determine if it was followed by automated resource discovery and sensitive file harvesting.",
        "description": "Correlate identity takeover, DevOps enumeration, and credential harvesting into a single verdict.",
        "max_iterations": 6,
        "expected_signal": "A final verdict citing rows from identity, DevOps, and file surfaces.",
        "success_criteria": "A per-host and per-user verdict of malicious | suspicious | benign citing specific API operations and file paths."
      },
      "parents": [
        {
          "id": "devops-enumeration-check",
          "kind": "merge"
        },
        {
          "id": "kubeconfig-access-check",
          "kind": "merge"
        }
      ]
    },
    {
      "id": "route-remediation",
      "type": "checkpoint",
      "label": "Route remediation",
      "config": {
        "fuzzy": true,
        "judge": "hunter",
        "question": "the triage-intrusion-chain verdict is malicious for at least one host and user",
        "condition": "the triage-intrusion-chain verdict is malicious for at least one host and user",
        "blind_spot": "git-content-visibility",
        "confidence": "high",
        "description": "Isolate the compromised identity or escalate for manual review.",
        "checkpoint_type": "mandatory"
      },
      "parents": [
        {
          "id": "triage-intrusion-chain"
        }
      ]
    },
    {
      "id": "isolate-identity",
      "type": "action",
      "label": "Isolate compromised identity",
      "config": {
        "target": "identity",
        "description": "Immediately contain the threat by disabling the hijacked account and revoking tokens.",
        "instructions": "Disable the compromised user account in Entra ID, revoke all active Refresh Tokens, and reset the password.",
        "action_approval": "required"
      },
      "parents": [
        {
          "id": "route-remediation",
          "branch": "on_supports"
        }
      ]
    },
    {
      "id": "analyst-review",
      "type": "task",
      "label": "Analyst review",
      "config": {
        "assignee": "analyst",
        "description": "Review the findings for indeterminate or unavailable data cases.",
        "instructions": "Manually review the cited rows and determine if the SSPR activity and subsequent DevOps calls constitute an intrusion. Rotate credentials for any confirmed account compromise."
      },
      "parents": [
        {
          "id": "gate-on-suspected-takeover",
          "branch": "default"
        },
        {
          "id": "gate-on-suspected-takeover",
          "branch": "on_unavailable"
        },
        {
          "id": "route-remediation",
          "branch": "default"
        },
        {
          "id": "route-remediation",
          "branch": "on_unavailable"
        }
      ]
    },
    {
      "id": "secrets-rotation-review",
      "type": "task",
      "label": "Secrets and Git history review",
      "config": {
        "assignee": "analyst",
        "description": "Examine the content of Git commits to confirm if secrets were exfiltrated.",
        "instructions": "Review the Git version history for the repositories identified in the DevOps enumeration. Specifically look for commits containing kubeconfig data and rotate all cluster credentials found within."
      },
      "parents": [
        {
          "id": "isolate-identity"
        },
        {
          "id": "analyst-review"
        }
      ]
    },
    {
      "id": "close-out",
      "type": "task",
      "label": "Close out hunt",
      "config": {
        "assignee": "analyst",
        "description": "Finalize the hunt by documenting findings and tuning notes.",
        "instructions": "Record the findings, document the remediation steps taken, and update the detection tuning notes for SSPR activity."
      },
      "parents": [
        {
          "id": "gate-on-suspected-takeover",
          "branch": "on_refutes"
        },
        {
          "id": "route-remediation",
          "branch": "on_refutes"
        },
        {
          "id": "secrets-rotation-review"
        }
      ]
    }
  ]
}