SOC Prime Bias: Critical

20 Oct 2026 12:39 UTC

ConnectWise SkyKick Vendor Credentials Abused in Microsoft 365 Attacks

Author Photo
SOC Prime Team linkedin icon Follow
ConnectWise SkyKick Vendor Credentials Abused in Microsoft 365 Attacks
shield icon

Detection stack

  • AIDR
  • Alert
  • ETL
  • Query

Summary

A compromised Microsoft 365 API secret associated with ConnectWise Cloud Backup, formerly SkyKick, was abused by threat actors to gain unauthorized access to customer M365 environments. The attackers leveraged the application’s extensive permissions to access Exchange Online, Microsoft Teams, and SharePoint Online. The activity included both automated token requests and hands-on post-exploitation operations.

Investigation

Huntress identified abuse of ConnectWise service principals through Microsoft 365 sign-in logs, including unauthorized access from hosting provider IP address 144.172.64.128 and private Azure IPv6 space. The investigation found that attackers used compromised credentials to assign directory roles such as Global Administrator and add new client secrets to other trusted applications for persistence. Activity also showed scripted movement through tenants in GUID order and distinct User-Agent patterns.

Mitigation

Organizations should immediately disable the affected ConnectWise Cloud Backup and ConnectWise Cloud Management application IDs within their Microsoft 365 tenants. Disabling rather than deleting the applications is important to preserve consent management. Security teams should also audit all service principal sign-ins and review application credentials for unexpected additions created since October 13.

Response

When suspicious activity is identified, organizations should review Entra audit logs for Add member to role, Update application, and Add service principal credentials events. Security teams should validate associated User-Agent strings and distinguish legitimate vendor activity, such as kiota-dotnet, from attacker tooling including Python-urllib and curl. Finally, review all client secrets and certificates across enterprise applications and remove any unauthorized credentials.

Attack Flow

We are still updating this part.

Detections

Add a User to an Admin Role (via office365)

SOC Prime Team
19 Oct 2026

Service Principal Account Credentials Update (via azuread)

Microsoft, SOC Prime Team
19 Oct 2026

High Privilege Role was Assigned (via azuread)

SOC Prime Team
19 Oct 2026

IOCs (SourceIP) to detect: ConnectWise (SkyKick) Vendor Credentials Abused in M365 Attacks

SOC Prime AI Rules
19 Oct 2026

IOCs (DestinationIP) to detect: ConnectWise (SkyKick) Vendor Credentials Abused in M365 Attacks

SOC Prime AI Rules
19 Oct 2026

Detect Unauthorized Modifications in M365 via Python-urllib [Azure Activity Logs]

SOC Prime AI Rules
19 Oct 2026

Unauthorized Use of Microsoft Graph Tokens in M365 via ConnectWise Applications [Azure Auditlogs]

SOC Prime AI Rules
19 Oct 2026

Simulation Execution

  • Attack Narrative & Commands: The adversary has successfully gained access to a service principal with limited permissions. To achieve persistence and full environment control, the attacker executes a Python script. The script targets the Microsoft Graph API to: 1) Add the compromised service principal to a highly privileged directory role (e.g., Global Administrator or Privileged Role Administrator), and 2) Add a new client secret to the service principal. The attacker uses the urllib library in Python 3.12, which defaults to the Python-urllib/3.12 User-Agent, providing the specific telemetry hook required by the detection rule.

  • Regression Test Script:

    import urllib.request
    import json
    
    # NOTE: This script is a simulation of the API call structure. 
    # In a real environment, a valid OAuth2 Bearer token would be required.
    # This script is designed to trigger the 'UserAgent' and 'OperationName' detection logic.
    
    target_url = "https://graph.microsoft.com/v1.0/directoryRoles"
    access_token = "SIMULATED_TOKEN"
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    # This specific User-Agent is the trigger for the detection rule
    user_agent = "Python-urllib/3.12"
    
    # Simulate "Add member to role"
    payload = {
        "roleId": "target-role-guid",
        "principalId": "attacker-service-principal-guid"
    }
    
    print(f"[*] Simulating unauthorized role modification using {user_agent}...")
    
    try:
        req = urllib.request.Request(
            target_url, 
            data=json.dumps(payload).encode('utf-8'), 
            headers=headers, 
            method='POST'
        )
        req.add_header('User-Agent', user_agent)
    
        # In a real test, this would call the actual API. 
        # For simulation purposes, we assume the API call is successful to generate the log.
        print("[+] Request sent. Check Azure Audit Logs for 'Add member to role' with User-Agent: Python-urllib/3.12")
    except Exception as e:
        print(f"[-] Simulation failed: {e}")
  • Cleanup Commands:

    # Manual cleanup is required in M365 as these are identity-level changes.
    # 1. Remove the added role assignment:
    # Remove-MgDirectoryRoleMemberByRef -DirectoryRoleId <ID> -DirectoryRoleMemberId <ID>
    
    # 2. Remove the added service principal credentials:
    # Remove-MgServicePrincipalPassword -ServicePrincipalId <ID> -PasswordId <ID>