Stolen Passwords Are Boring. Stolen Consent Just Owned 173 Tenants at Once.

The ConnectWise breach didn't steal a password—it weaponized 173 consent grants that customer tenants had already approved. One stolen app credential became a master key to every M365 tenant that had clicked 'yes.'

The headline on the ConnectWise cascade — one stolen app credential, 173-plus Microsoft 365 tenants exposed — is technically true and quietly misleading. A credential on its own unlocks nothing. What it unlocks is whatever a tenant already decided to trust. The breach wasn’t the secret. It was the 173 standing agreements that secret could cash in.

Entra News #171 laid out the chain this week: attackers extracted an application credential from a compromised ConnectWise ScreenConnect estate, then used it against the vendor’s multi-tenant app registration to reach customer tenants that had consented to it. One identity, many front doors — and every door had been propped open in advance by a consent grant.

So what actually did the unlocking?

Here’s the mechanism people skip. A multi-tenant app registration lives in the vendor’s home tenant. When your organisation “connects” that SaaS product, a service principal is provisioned in your tenant and granted permissions — Mail.ReadWrite, Files.ReadWrite.All, Sites.ReadWrite.All, the usual suspects. That grant persists. It doesn’t expire when someone leaves, and it survives every password reset you’ll ever run.

The credential the attacker stole just proves the app is the app. The access comes from the grants sitting in each customer directory. Steal the identity once, and you inherit every tenant that ever said yes. Revoking the compromised secret in the vendor tenant doesn’t touch those grants — they’re yours to clean up, in all 173 directories, one at a time.

Worth a pedantic note the headline glosses over: if a single app-only credential reached mailboxes with no signed-in user, that behaves like application permissions, not delegated ones. Delegated scopes need a user token. The brief flags delegated access, and both patterns were plausibly in play — but the distinction decides your query. Audit both.

Is this a new attack, or just a bigger one?

New? No. Consented-app abuse and illicit-consent phishing have been documented for years. The industry has now relearned “OAuth grants are durable, unmonitored credentials” roughly once per rebrand. What’s new is the scale of a single blast radius: a managed-services vendor sits upstream of hundreds of tenants by design, so compromising one registration compromises its entire customer book. Supply-chain math, applied to consent.

Where to look, right now

In the Entra admin center: Identity → Applications → Enterprise applications, then each app’s Permissions blade. Pay attention to grants marked Granted for all users — that’s org-wide admin consent, your widest exposure. The tenant-level knobs live under Enterprise applications → Consent and permissions.

Don’t click through 400 apps by hand. Pull the consent grants and filter for broad scopes on unverified multi-tenant publishers:

audit.ps1PowerShell
Connect-MgGraph -Scopes 'Application.Read.All','Directory.Read.All'
​
$riskyScopes = @('Mail.ReadWrite','Mail.Read','Mail.Send',
                 'Files.ReadWrite.All','Sites.ReadWrite.All','offline_access')
​
try {
    $grants = Get-MgOauth2PermissionGrant -All -ErrorAction Stop
}
catch {
    Write-Error "Could not read consent grants: $($_.Exception.Message)"
    return
}
​
$report = foreach ($g in $grants) {
    $hit = ($g.Scope -split ' ').Where({ $_ -in $riskyScopes })
    if (-not $hit) { continue }
​
    $sp = Get-MgServicePrincipal -ServicePrincipalId $g.ClientId -ErrorAction SilentlyContinue
    if (-not $sp) { continue }
​
    [pscustomobject]@{
        App               = $sp.DisplayName
        AppId             = $sp.AppId
        VerifiedPublisher = $sp.VerifiedPublisher.DisplayName
        SignInAudience    = $sp.SignInAudience      # AzureADMultipleOrgs = multi-tenant
        ConsentType       = $g.ConsentType          # AllPrincipals = org-wide
        RiskyScopes       = ($hit -join ', ')
    }
}
​
# Multi-tenant apps from unverified publishers holding broad delegated scopes
$report |
    Where-Object { -not $_.VerifiedPublisher -and $_.SignInAudience -eq 'AzureADMultipleOrgs' } |
    Sort-Object App | Format-Table -AutoSize

Export that, review it with a human, and only then revoke. Do not pipe a live query straight into Remove-MgOauth2PermissionGrant — you will nuke something load-bearing at 4pm on a Friday.

Stop the next 173

Turn off end-user consent to anything but low-risk, verified-publisher apps, and route the rest through admin consent workflow so a human reviews the grant. Stage it with -WhatIf before committing:

audit.ps1PowerShell
$params = @{
    IsEnabled             = $true
    NotifyReviewers       = $true
    RemindersEnabled      = $true
    RequestDurationInDays = 7
    Reviewers = @(
        @{ query = "/users/<reviewer-object-id>"; queryType = "MicrosoftGraph" }
    )
}
​
# Dry-run first; drop -WhatIf only after reviewing the change
Update-MgPolicyAdminConsentRequestPolicy -BodyParameter $params -WhatIf

The password-reset era trained everyone to think a compromised secret is a Tuesday. Consent grants aren’t. They’re silent, durable, and they don’t show up in your sign-in logs as a problem until someone reads 173 mailboxes with them. Rotate the credential if you must — but the grant is the breach, and the grant is still sitting in your tenant until you go pull it out.