ConnectWise Cloud Backup Breach: What to Check ?

cloudbackup

Your backup app is the thing you hope you never need. It sits quietly in the background, holds a key to your mail, files and chats, and promises to put everything back if disaster strikes. This week, that key became the disaster. The ConnectWise Cloud Backup breach shows how attackers can walk into Microsoft 365 tenants without guessing a single password or tripping a single MFA prompt.

If you manage Microsoft 365 for yourself, your company, or a pile of client tenants, this one is worth fifteen minutes today. Here is what happened, what is still unknown, and the exact checks to run, including two small scripts you can adapt.

⚡ TL;DR

  • Attackers used a stolen credential belonging to ConnectWise Cloud Backup (formerly SkyKick) to sign in to Microsoft 365 tenants as the app itself, so no user password or MFA was involved.
  • Reports put confirmed unauthorized sign-ins at 162 to 173 tenants, with sign-in activity seen in nearly 1,000 tenants. The numbers differ by source and date.
  • ConnectWise disabled seven application identities, paused backups, and says it has no sign that stored backup data was accessed. How the credential was stolen is still unknown.
  • Your move: find the ConnectWise and SkyKick apps in Entra, disable (do not delete) them, review service principal sign-ins and any secrets added since October 13.
  • Nothing here is a reason to panic. It is a reason to look.

What is the ConnectWise Cloud Backup breach?

The ConnectWise Cloud Backup breach is an attack in which unknown actors obtained a Microsoft 365 credential belonging to ConnectWise Cloud Backup, formerly SkyKick, and used it to get Microsoft Graph access tokens in customer tenants. Because the app holds broad permissions, those tokens could reach mail, files, Teams and directory data.

The story broke on October 16, 2026, when security firm Huntress published findings from its own monitoring. By the weekend, BleepingComputer, iTnews and The Hacker News were all covering it, and ConnectWise had switched the product off as a precaution.

Cloud Backup is popular with managed service providers (MSPs), the companies that run IT for other businesses. That matters because one MSP can manage dozens of client tenants, so a single stolen vendor key can fan out very quickly.

timeline
Key moments in the ConnectWise Cloud Backup incident, October 13 to 17 (UTC, as reported).

Timeline: what happened and when

Sources agree on the broad shape of events but differ on a few exact times, so treat the table as a reported timeline, not a forensic one. All times are UTC where the sources give them.

DateWhat was reportedSource
Oct 13–14Password-spraying attempts from one hosting-provider IP address, aimed at law-firm tenants. No successful user sign-ins were seen.Huntress, The Hacker News
Oct 14ConnectWise says its own monitoring identified the issue.ConnectWise advisory (as reported by The Hacker News)
Oct 15, about 06:13–08:34The Cloud Backup app identity signs in from the attacker IP and collects Microsoft Graph tokens across tenants, in quick succession.Huntress
Oct 15Microsoft observes broad successful service principal authentication from the same IP.The Hacker News, BleepingComputer
Oct 16Huntress publishes its first alert, later updated.Huntress
Oct 17, early UTCConnectWise disables seven application identities, cancels the shared credential, and pauses Cloud Backup.ConnectWise advisory (as reported), BleepingComputer
Oct 18–19Service still offline. ConnectWise says backups are not running and archives remain isolated.BleepingComputer, iTnews

How did attackers get in without a password or MFA?

This is the part that surprises people. Most of us picture a breach as someone stealing a login. Here, nobody logged in as a person at all.

User sign-ins versus app sign-ins

Microsoft 365 has two kinds of identities. Users are people, and they get password and MFA checks. Service principals are the identities of apps. When you install a backup tool and approve its permissions, you create a service principal in your tenant that can act with the access you granted, with no human present.

According to Huntress, the stolen item was a client assertion held in the vendor’s own tenant, which works against any tenant where the app has been consented. The sign-ins were app-only, so there was no user password to guess and no MFA prompt to answer. Huntress also says nothing a customer did could have produced this credential.

Think of it as a master key to a building you rent. Your door has a great lock, and you may have added a camera. But if the property manager’s master key walks out the door, the lock on your own unit does not enter into it. That is why this kind of incident is called a supply-chain attack: the weak link is upstream of you.

signins
User sign-ins face password and MFA checks. App-only sign-ins use the app’s own credential.

Why a backup app is such a juicy target

Backup tools need to read nearly everything so they can copy it. Huntress describes the app as holding broad read and write permissions over mailboxes, OneDrive, SharePoint, Teams and directory data in each tenant it is installed in. An attacker holding its tokens inherits that reach, limited only by what each tenant granted.

Microsoft, as reported by BleepingComputer, characterised the activity as an active supply-chain attack that likely abused existing permissions such as Directory.ReadWrite.All. Put simply: the permissions were already there. The attacker only needed to borrow them. (Yes, the backup plan itself needed a backup plan.)

How many tenants were affected? The numbers do not match

If you read three articles, you may see three different numbers. That is normal in a live incident, but it is worth untangling so you do not repeat the wrong one.

FigureWhat it meansWhere it appears
162 tenantsConfirmed unauthorized sign-ins in Huntress’s snapshot as of about 19:00 UTC on October 16. This covers Huntress’s own environment.Huntress, BleepingComputer, iTnews
173 tenantsConfirmed unauthorized sign-ins, attributed to Huntress, in a later report. The article does not explain the difference from 162.The Hacker News (Oct 17)
Almost 1,000 tenantsTenants that showed sign-in activity from the attacker’s IP. Not all of it succeeded.Huntress update, iTnews
300+ sign-ins succeededSuccessful sign-ins among that wider group, per a later Huntress update.Huntress
16 tenantsA separate October 15 wave aimed at organizations in policy, national security, trade and critical minerals. It failed where the ConnectWise service principal was not present.Huntress, iTnews

What did the attackers do once they were in?

Here the reports separate what was possible from what was observed, and it pays to keep them apart.

What was observed

  • In one partner tenant, a separate ConnectWise Cloud Management app added itself to the Global Administrator role, then added new client secrets to an MSP management app and to Huntress’s own app, according to Huntress. A certificate attempt failed.
  • iTnews reports the rogue secrets were set to last until 2028. Huntress says it found and removed the one it knew about; the article does not say whether others exist.
  • In roughly 50 monitored tenants, directory roles (mostly Exchange Administrator) were assigned to Cloud Management service principals. Huntress is unsure whether that was vendor automation or the attacker.
  • Microsoft, via BleepingComputer, reports Graph reconnaissance and attempts to add credentials that could act as a backdoor.

What is not confirmed

Actual data theft. In the one tenant Huntress reviewed in detail, the audit log showed no mail or file access. Security firm Todyl analysed nine intrusions and found only sign-in activity, with no account creation, permission changes or persistence through the backup app. But Huntress adds an important caveat: directory reads through Graph leave no audit trail, so data access cannot be ruled out. Absence of evidence, in other words, is not a clean bill of health.

There are also unverified external tips of hands-on activity, which Huntress itself labels as unverified. I am treating those as rumor and leaving them out of the conclusions.

What have ConnectWise and Microsoft said?

From ConnectWise’s advisory, as relayed by The Hacker News coverage of ConnectWise’s advisory and BleepingComputer:

  • Unknown actors gained unauthorized access to application identities and used them to compromise Microsoft 365 tenants. The investigation is ongoing.
  • Seven application identities were disabled and the shared Cloud Backup credential is no longer valid.
  • Scheduled backups stopped, and archive access and restores are unavailable. Existing archives are described as encrypted, isolated and preserved, with no evidence they were accessed.
  • SaaS Security and Cloud Management were taken offline as a precaution, which ConnectWise says is not evidence that any environment was compromised.
  • Disabling the apps blocks new sign-ins but does not undo changes already made inside tenants, and tokens issued earlier may keep working until they expire.

What we still do not know

  • How the credential was stolen. Huntress, BleepingComputer and iTnews all say the root cause is not established, and ConnectWise has not explained it.
  • Whether a product flaw played a part. The exposure mechanism is still under analysis.
  • The full victim count and any data impact. ConnectWise cannot see inside customer tenants.
  • Whether other rogue credentials exist. Huntress found one set of added secrets; nobody has certified there are no more.

How to check if your Microsoft 365 tenant was hit

To check your tenant, find the ConnectWise and SkyKick enterprise apps in Entra, disable them without deleting them, then review service principal sign-ins and any app secrets added since October 13, 2026. Keep the apps disabled until ConnectWise confirms every credential has been reset.

The steps below follow the guidance from Huntress and ConnectWise as reported. If you run an MSP, repeat them for every managed tenant, including dormant SkyKick connections.

  1. Preserve evidence first. Export your sign-in and audit logs before you change anything. Turn on Microsoft Graph activity logging if it is not already on.
  2. Find the apps. In the Microsoft Entra admin center, open Enterprise applications and search for the seven application IDs in the script below, or for the names ConnectWise Cloud Backup, ConnectWise Cloud Management and SkyKick. Older tenants may show “SkyKick Cloud Manager”.
  3. Disable, do not delete. Set “Enabled for users to sign-in” to No. Deleting can remove evidence and complicate re-enabling later. Disabling pauses backups, but email, files and Teams keep working and existing backups stay in place.
  4. Open the right sign-in tab. Use the Service principal sign-ins tab, not the default user sign-ins view. Filter by the app ID and look for source IPs outside Microsoft and Azure ranges. Huntress reports the app normally connects from Azure private IPv6 addresses and highlights 144.172.64.128 as the attacker address. Review October 13 onward.
  5. Check the audit log for three events: Add member to role, Update application, and Add service principal credentials. A service principal adding itself to a role, or editing other apps, is the red flag.
  6. Hunt for new secrets. Review client secrets and certificates on all enterprise apps and app registrations, especially management and security tools, for anything added since October 13 that you did not create.
  7. Remove roles you did not grant, and look at Conditional Access exclusions that mention these apps.
  8. Look for mailbox tricks. Check for unexpected forwarding, inbox or transport rules, and delegated mailbox access.
entra enterprise
Illustrative schematic, not a Microsoft screenshot: disable the app via the Enabled for users to sign-in setting. Do not delete it.
entra service
Illustrative schematic with example data, not a Microsoft screenshot: the Service principal sign-ins tab, filtered by application ID.

One more caution from Huntress: a clean result does not rule out compromise. User agents, it saw (kiota-dotnet, Python-urllib/3.12 and curl/7.88.1) are low-fidelity clues on their own. The combination, not any single string, is what matters. Also note that Huntress can only check sign-in logs through the API for tenants with Entra ID P1 or higher, so smaller tenants may need to review logs by hand.

Check it with code: two small read-only scripts

Clicking through the portal works for one tenant. For many, a script saves time. Both scripts below are read-only. They change nothing in your tenant, and they use no secrets in the file itself.

Python: list vendor apps, broad permissions and recent secrets

This script (Python 3.12+, pip install requests) walks your service principals and app registrations, flags the seven ConnectWise application IDs, shows apps holding broad application permissions, and lists password credentials that started on or after October 13. Set GRAPH_TOKEN to a read-only Microsoft Graph token. Check Microsoft Learn for the current least-privileged read permissions before you request one.

"""Read-only audit of app identities in a Microsoft 365 / Entra tenant.

Needs: Python 3.12+, `pip install requests`, and a Microsoft Graph access token
with read-only directory/application permissions in the GRAPH_TOKEN env var.
It never changes anything in your tenant.
"""
import os
import sys
from datetime import datetime, timezone

import requests

GRAPH = "https://graph.microsoft.com/v1.0"

# The seven application IDs ConnectWise said it disabled (Oct 17, 2026).
VENDOR_APP_IDS = {
    "506e08f9-6e9a-4da4-942b-a099a03644ae",  # Cloud Backup
    "2399d300-6fca-4e6a-a686-399fefe75b83",  # Cloud Management
    "24205314-2b89-4e07-a2a4-579e898a5640",  # Cloud Management, MS Teams
    "b78f6a77-d4e5-46fb-87e5-d1648bd02519",  # Cloud Management, Exchange Online
    "0de14c77-b9b0-4137-9360-34878a929827",  # Cloud Management, MS Graph
    "412dcf96-176d-4b3a-a1b0-4981ad66971e",  # Cloud Management, Partner Center
    "b2b55f5b-7758-4719-8d8f-88ae172e6bdb",  # Cloud Management, SharePoint Online
}
NAME_HINTS = ("connectwise", "skykick")

# Broad "application permissions" worth a human look when an app holds them.
RISKY_ROLES = {
    "Directory.ReadWrite.All", "Mail.ReadWrite", "Mail.Read", "Files.ReadWrite.All",
    "Sites.FullControl.All", "Sites.ReadWrite.All", "Application.ReadWrite.All",
    "RoleManagement.ReadWrite.Directory", "User.ReadWrite.All", "Chat.ReadWrite.All",
}
# Microsoft's own first-party apps live in this tenant; skip them to cut noise.
MICROSOFT_TENANT = "f8cdef31-a31e-4b4a-93e4-5f571e91255a"
SINCE = datetime(2026, 10, 13, tzinfo=timezone.utc)  # start of the review window


def pages(get_json, url, params=None):
    """Yield every item from a paged Graph collection."""
    while url:
        data = get_json(url, params)
        yield from data.get("value", [])
        url, params = data.get("@odata.nextLink"), None


def new_secrets(obj):
    """Return password credentials that started on or after SINCE."""
    found = []
    for cred in obj.get("passwordCredentials") or []:
        start = cred.get("startDateTime")
        if start and datetime.fromisoformat(start.replace("Z", "+00:00")) >= SINCE:
            found.append(cred)
    return found


def audit(get_json):
    findings, role_names = [], {}

    def role_name(resource_id, role_id):
        if resource_id not in role_names:
            sp = get_json(f"{GRAPH}/servicePrincipals/{resource_id}", {"$select": "appRoles"})
            role_names[resource_id] = {r["id"]: r["value"] for r in sp.get("appRoles", [])}
        return role_names[resource_id].get(role_id, role_id)

    sp_params = {"$select": "id,appId,displayName,accountEnabled,appOwnerOrganizationId,"
                            "servicePrincipalType,passwordCredentials"}
    for sp in pages(get_json, f"{GRAPH}/servicePrincipals", sp_params):
        name = sp.get("displayName") or ""
        if sp["appId"] in VENDOR_APP_IDS or any(h in name.lower() for h in NAME_HINTS):
            state = "ENABLED" if sp.get("accountEnabled") else "disabled"
            findings.append(f"[vendor app] {name} ({sp['appId']}) is {state}")
        for cred in new_secrets(sp):
            findings.append(f"[new secret] service principal {name}: started {cred['startDateTime']}")
        if sp.get("appOwnerOrganizationId") == MICROSOFT_TENANT:
            continue
        for a in pages(get_json, f"{GRAPH}/servicePrincipals/{sp['id']}/appRoleAssignments"):
            role = role_name(a["resourceId"], a["appRoleId"])
            if role in RISKY_ROLES:
                findings.append(f"[broad permission] {name} holds {role} on {a.get('resourceDisplayName')}")

    app_params = {"$select": "id,appId,displayName,passwordCredentials"}
    for app in pages(get_json, f"{GRAPH}/applications", app_params):
        for cred in new_secrets(app):
            findings.append(f"[new secret] app registration {app['displayName']}: started {cred['startDateTime']}")
    return findings


def main():
    token = os.environ.get("GRAPH_TOKEN")
    if not token:
        sys.exit("Set GRAPH_TOKEN to a read-only Microsoft Graph access token.")
    session = requests.Session()
    session.headers["Authorization"] = f"Bearer {token}"

    def get_json(url, params=None):
        resp = session.get(url, params=params, timeout=30)
        resp.raise_for_status()
        return resp.json()

    results = audit(get_json)
    print("\n".join(results) if results else "Nothing flagged. Logs still matter: check them too.")


if __name__ == "__main__":
    main()

What to expect: one line per finding, such as a vendor app marked ENABLED or disabled, a broad permission like Mail.ReadWrite, or a new secret with its start time. I tested the logic against mock Graph responses, including paging and the date filter. I have not run it against a real tenant, so try it on a test tenant first.

JavaScript: scan an exported sign-in log

If you have exported service principal sign-ins to JSON, this Node 22 script flags vendor-app sign-ins from the reported attacker IP, and any IPv4 source as a prompt for a closer look.

// Scan an exported Entra service-principal sign-in log (JSON array) for the ConnectWise apps.
// Node 22 LTS, no dependencies. Usage: node scan-signins.mjs signins.json
// ASSUMPTION: each record has appId, ipAddress, createdDateTime, userAgent. Rename to match your export.
import { readFileSync } from "node:fs";

const VENDOR_APP_IDS = new Set([
  "506e08f9-6e9a-4da4-942b-a099a03644ae", // Cloud Backup
  "2399d300-6fca-4e6a-a686-399fefe75b83", // Cloud Management
  "24205314-2b89-4e07-a2a4-579e898a5640", // Cloud Management, MS Teams
  "b78f6a77-d4e5-46fb-87e5-d1648bd02519", // Cloud Management, Exchange Online
  "0de14c77-b9b0-4137-9360-34878a929827", // Cloud Management, MS Graph
  "412dcf96-176d-4b3a-a1b0-4981ad66971e", // Cloud Management, Partner Center
  "b2b55f5b-7758-4719-8d8f-88ae172e6bdb", // Cloud Management, SharePoint Online
]);
const REPORTED_BAD_IP = "144.172.64.128"; // reported by Huntress; treat as a clue, not proof
const WINDOW_START = Date.parse("2026-10-13T00:00:00Z");

export function scan(records) {
  const hits = [];
  for (const r of records) {
    if (!VENDOR_APP_IDS.has(r.appId)) continue;
    if (Date.parse(r.createdDateTime) < WINDOW_START) continue;
    // Huntress says the app normally connects from Azure private IPv6 addresses,
    // so an IPv4 address is only a heuristic reason to look closer.
    const reasons = [];
    if (r.ipAddress === REPORTED_BAD_IP) reasons.push("reported attacker IP");
    else if (r.ipAddress && !r.ipAddress.includes(":")) reasons.push("IPv4 source (unusual for this app)");
    if (reasons.length) hits.push({ time: r.createdDateTime, app: r.appId, ip: r.ipAddress, agent: r.userAgent, reasons });
  }
  return hits;
}

// Only run the CLI when this file is executed directly.
if (import.meta.url === `file://${process.argv[1]}`) {
  const file = process.argv[2];
  if (!file) {
    console.error("Usage: node scan-signins.mjs signins.json");
    process.exit(1);
  }
  const hits = scan(JSON.parse(readFileSync(file, "utf8")));
  console.log(hits.length ? JSON.stringify(hits, null, 2) : "No suspicious vendor-app sign-ins found.");
}

Run node scan-signins.mjs signins.json. You will get a JSON list of suspicious rows or a message that none were found. I tested it with sample records. The field names (appId, ipAddress, createdDateTime, userAgent) are my assumption, so rename them to match your export. An IPv4 hit is only a heuristic, because it rests on Huntress’s note about the app’s normal IPv6 egress.

Hardening: make the next vendor-key theft hurt less

You cannot stop a vendor from losing a key. You can limit what a lost key opens. These recommendations come from Huntress’s guidance, plus Microsoft’s documentation on reviewing app permissions.

  • Treat app permissions like admin accounts. Review them on a schedule. Microsoft’s guidance separates delegated permissions (an app acting for a signed-in user) from application permissions (an app acting as itself), and a complete review must check both.
  • Restrict client secrets. Use Entra application management policies to cap how long secrets can live and to limit who can add them.
  • Limit powerful roles. Fewer people holding Application Administrator and Cloud Application Administrator means fewer ways to quietly add credentials.
  • Use workload identity controls. Consider Conditional Access for workload identities and Entra ID Protection, as Huntress suggests.
  • Turn on logging early. Graph activity logs and preserved audit logs are what let you answer questions after the fact.
  • Know your retention. While Cloud Backup is offline, you cannot restore from it. Confirm that your backups exist and how long they are kept.
permission hardening
A one-glance hardening checklist for app identities in Microsoft 365.

Should you care? A quick verdict

If you are…How much to worryDo this
An MSP or reseller using ConnectWise Cloud Backup, Cloud Management or SaaS SecurityHigh: act todayRun the full check for your tenant and every client tenant, and tell clients what you find.
A business whose MSP uses these toolsMedium: ask questionsAsk your provider whether the apps are installed and disabled, and whether sign-in logs were reviewed.
A Microsoft 365 admin with no ConnectWise or SkyKick appsLow for this incidentConfirm by searching Enterprise applications, then use this as a nudge to audit your other app identities.

The honest summary is mixed. Pros: ConnectWise acted, Huntress and Microsoft publish detection details, and most reports see sign-ins rather than confirmed data theft. Cons: the root cause is unknown, the full count is unclear, and the credential reached every tenant where the app lived. Nothing here proves your data was touched. Everything here says it is worth ten minutes to be sure.

🔮 What’s next (analysis, not fact)

A few things are likely, and I want to be clear that these are my predictions. First, expect more detail from ConnectWise on how the credential was exposed, since that is the question everyone is asking. Second, expect Microsoft and security vendors to push harder on workload identity protections, because app-only sign-ins sidestep the user-focused defenses most organizations have invested in. Third, expect customers and MSPs to ask backup vendors tougher questions about how they store and rotate credentials.

Watch for three signals: a ConnectWise root-cause statement, an updated tenant count from Huntress, and word on when Cloud Backup returns and what changed before it does. I will update this post if any of them land.

❓ FAQ

What is the ConnectWise Cloud Backup breach?

Attackers obtained a credential belonging to ConnectWise Cloud Backup, formerly SkyKick, and used it to sign in to Microsoft 365 tenants as the app itself. The app holds broad permissions over mail, files, Teams and directory data, so the tokens could reach all of them. How the credential was stolen is not yet known.

Did MFA stop the attack?

No. The sign-ins were app-only, authenticated as the application rather than as a user, so there was no user password and no MFA prompt involved. That is why Huntress stresses that customer-side actions could not have produced the credential, and why app and workload identity controls matter.

Should I delete the ConnectWise apps from my tenant?

Huntress advises disabling them rather than deleting. Deleting can remove evidence and complicate safe re-enabling. Set the app to not be enabled for sign-in, keep it disabled until ConnectWise confirms credentials are reset, and know that doing so pauses backups but leaves email, files and existing backups in place.

Were backups stolen?

ConnectWise says it has no indication that stored backup data was accessed and describes existing archives as encrypted and isolated. That is the company’s own assessment, and the reports I reviewed do not independently confirm it. Data theft from tenants is also unconfirmed, though it cannot be ruled out.

Is Microsoft 365 itself compromised?

There is no sign of that in the reports. The problem is a stolen credential belonging to a third-party app, abused through the permissions customers granted it. Microsoft’s own platform did what it is designed to do, which is why reviewing which apps hold which permissions is the lesson.

✅ Key takeaways

  • The ConnectWise Cloud Backup breach is a supply-chain attack: a vendor credential, not your password, was the weak point.
  • App-only sign-ins skip user MFA, so review service principals, their permissions and their secrets.
  • Disable, do not delete, the ConnectWise and SkyKick apps, preserve logs, and check for secrets added since October 13.
  • Counts of 162, 173 and almost 1,000 describe different things. None is a final number.
  • Root cause and data impact are still unknown. Treat claims beyond that as rumor until confirmed.

🗳️ Quick poll

How do you review app permissions in your tenant? Monthly, after incidents only, or never? Tell us in the comments.

Leave a Reply