Als Microsoft den Midnight Blizzard-Angriff vom Januar zum ersten Mal bekannt gab und ihre darauf folgende tiefere Analyse veröffentlichte, verfolgte ich den resultierenden Inhalt mit großem Interesse – Risiken, die von Enterprise Applikationen ausgehen, sind ein Herzensthema für mich.

Wenn Sie Microsoft 365 auf einem vernünftigen Niveau nutzen, werden Sie unweigerlich Enterprise Applikationen mit einem großen Umfang an Berechtigungen in Ihrem Tenant haben (denken Sie an Backup-Lösungen, Skriptautomatisierungen usw.) – deshalb sollten Sie die Entra ID Rolle "(Cloud) App Administrator" wie einen Globalen Administrator behandeln. Durch das Setzen von Anmeldeinformationen für Applikationen, können sie sich mit den zugehörigen APIs in dessen Namen verbinden und Privilegien nutzen, die Sie den Benutzern vielleicht nie gewährt hätten. Das gilt auch für den Graph API Scope "Application.ReadWrite.All".

Da ich nichts zu den Hauptinteressenspunkten des Breach hinzuzufügen habe, lateral movement zwischen Tenants durch Unternehmensanwendungen und wie man richtig API-Berechtigungen in Exchange Online erteilt, werde ich diese Themen nicht erneut betrachten. Es gibt jedoch einen Bereich, in dem ich gerne mehr Informationen sehen würde – einmal in Ihrem Tenant entdeckt, ist es manchmal nicht einfach zu finden, wie man sich von hochprivilegierten API-Rollen befreit oder sie vermeidet. Ein solches Beispiel ist Application.ReadWrite.All.

Die gesamte Midnight Blizzard Angriffskette könnte direkt mit einem App-Besitzer der Multi-Tenant-App begonnen haben, aber es hätte leicht eine Anwendung im Test-Mandanten mit dieser Berechtigung sein können, durch die der Angreifer zur Multi-Tenant-App wechselte (Microsoft ist hierüber nicht ganz klar).

Schauen wir uns also einige Anwendungsfälle an, bei denen der erste Reflex sein könnte, "Application.ReadWrite.All" zu verwenden, es aber bessere Lösungen gibt:

Ich werde in meinen Demos PowerShell verwenden, aber da Graph eine REST-API ist, können Beispiele auf jede Programmiersprache mit Unterstützung für HTTP-Anfragen übertragen werden
I hold these truths to be self evident: Delegierte Berechtigungen sind vorzuziehen gegenüber Anwendungsberechtigungen, und wenn Sie nur Read benötigen, verwenden Sie keinen ReadWrite Scope


Allgemeine Konfigurationsverwaltung

Im Großteil der Fälle wollen wir nicht alle Anwendungen im Tenant verwalten, sondern nur eine begrenzte Auswahl. Beginnen wir mit einer Enterprise App, die ihre eigene Konfiguration verwalten möchte.

Denken Sie daran: In der Graph API sind App Registrations auf dem application Endpunkt, eine Enterprise App ist ein servicePrincipalKurze Zusammenfassung des Unterschieds
Wenn ich es zu oft schreibe, könnte ich App Registrations als "AppRegs" und Enterprise Apps als "EApps" kürzen.

Erster Versuch

Wenn wir uns die Graph API Dokumentation für die Verwaltung von App Registrations ansehen, können wir bereits eine potenzielle Option sehen (das Gleiche gilt für Enterprise Apps):

DocumentedLeastPrivileges

Also fügen wir den Scope unserer App Registration hinzu, gewähren die Berechtigungen auf der Enterprise App und sind fertig, richtig?

activeScopes

Jetzt, da ich den Scope in meiner Graph Session habe, versuche ich, meine EApp so zu ändern, dass ein Benutzer zugewiesen sein muss, bevor er sich anmelden kann:
Invoke-MgGraphRequest "PATCH" "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$($graphConfig.clientID)')" -Body @{ appRoleAssignmentRequired = $true }

Und werde prompt abgelehnt:

ErrorMessageSelfPatch

Owner Rechte hinzufügen

Das ist logisch, wenn wir darüber nachdenken. Wir haben der Enterprise App die Berechtigung gegeben, Anwendungen zu verwalten, die sie besitzt, aber wir besitzen nichts (Hier klicken für Microsofts Übersicht über Anwendungsbesitz). Wie können wir das also beheben? Zum Zeitpunkt des Verfassens (Feb. 2024) bietet das Entra Portal keine EApps an, wenn wir versuchen, einen Owner zu einer Enterprise App oder einer App Registration hinzuzufügen.

Wenn wir uns jedoch mit dem delegierten Scope "Application.ReadWrite.All" als Benutzer mit mindestens Application Administrator (oder Owner) gegen die Graph API authentifizieren, können wir den notwendigen Besitz hinzufügen – ein Objekt kann sogar sich selbst besitzen.

Denken Sie daran – Auch wenn Anmeldeinformationen auf der App Registration konfiguriert sind, hält die Enterprise App / der service Principal alle Berechtigungen im Tenant

Siehe auch die Microsoft Dokumentation zum Hinzufügen von Ownern zu Enterprise Apps

# Objekt-ID unserer EApp über App / Client ID abrufen
$enterpriseAppID    = ( Invoke-MgGraphRequest "GET" "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$($graphConfig.clientID)')?select=id" ).id

# Die EApp als ihren eigenen Owner hinzufügen
$params = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$enterpriseAppID" }
Invoke-MgGraphRequest "POST" "https://graph.microsoft.com/v1.0/servicePrincipals/$enterpriseAppID/owners/`$ref" -Body $params


Versuchen wir es also erneut:
Invoke-MgGraphRequest "PATCH" "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$($graphConfig.clientID)')" -Body @{ appRoleAssignmentRequired = $true }

alt

Erfolg! Und wir können den gesamten Tenant von hier aus nicht übernehmen, da wir nur eine Anwendung besitzen 🥳

Interessanterweise, obwohl wir Enterprise Apps nicht über die UI als Owner hinzufügen können, sobald wir sie hinzugefügt haben, sind sie sichtbar:

PortalOwnerView


Wenn wir die Eigenschaften unserer App Registration ändern müssen, können wir auch dort Owner wie folgt hinzufügen:

In diesem Beispiel verwende ich immer noch die App / Client ID der EApp, als die ich angemeldet bin, ersetzen Sie sie bei Bedarf

Siehe auch die Microsoft Dokumentation zum Hinzufügen von Ownern zu App Registrations

# Die ID der EApp abrufen, die wir für das Management verwenden möchten
$enterpriseAppID    = (Invoke-MgGraphRequest "GET" "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$($graphConfig.clientID)')?select=id").id
$appRegistrationID  = (Invoke-MgGraphRequest "GET" "https://graph.microsoft.com/v1.0/applications(appId='$($graphConfig.clientID)')?select=id").id

# Wir fügen unsere EApp als den Owner der AppReg hinzu
$params = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$enterpriseAppID" }
Invoke-MgGraphRequest "POST" "https://graph.microsoft.com/v1.0/applications/$appRegistrationID/owners/`$ref" -Body $params


Und wieder sind wir erfolgreich:

appRegEditSuccess


App Credentials verwalten

Einer der häufigsten Anfragen im Anwendungsmanagement ist wahrscheinlich, selbstverwaltung von Anmeldeinformationen. Das konsistente Ablaufen von Geheimnissen und Zertifikaten erfordert entweder eine genaue Überwachung oder, vorzugsweise, Automatisierung.

Mit "Application.ReadWrite.OwnedBy" und unserer Enterprise App, die bereits im letzten Kapitel Owner an der App Registration erhalten hat, können wir Anmeldeinformationen aktualisieren – es handelt sich schließlich nur um eine weitere Eigenschaft.

Secrets

Siehe auch die Microsoft Dokumentation zum Aktualisieren von App Registrations

$appRegistrationID  = (Invoke-MgGraphRequest "GET" "https://graph.microsoft.com/v1.0/applications(appId='$($graphConfig.clientID)')?select=id").id
$params = @{
    passwordCredential = @{
        displayName = "Secret$(get-date -f "ddMMyy")"
    }
}
# Je nachdem, wo Sie das Geheimnis benötigen, ist es wahrscheinlich eine gute Idee, es so schnell wie möglich dort abzulegen ;)
$response = Invoke-MgGraphRequest POST "https://graph.microsoft.com/v1.0/applications/$appRegistrationID/addPassword" -Body $params

Wenn ich tatsächlich Secrets nutzen muss, bevorzuge ich, sie nur in SecureStrings zu speichern, da sie im Speicher besser geschützt sind:
$secret = ConvertTo-SecureString $(Invoke-MgGraphRequest POST "https://graph.microsoft.com/v1.0/applications/$appRegistrationID/addPassword" -Body $params).secretText -AsPlainText -Force

In unserer Antwort erhalten wir das secret als secretText, zusammen mit einigen Metadaten (denken Sie daran, es irgendwo zu speichern, sonst ist es für immer verloren):

secretMetadata

Wir können das Geheimnis auch in der UI sehen (nur nicht den Wert):

secretUI


Keys / Zertifikate

Ich bin mir jedoch sicher, niemand benötigt noch Secrets. Sie sind einfach so viel schlechter als asymmetrische Authentifizierungsmethoden, also haben offensichtlich alle Anwendungen auf Zertifikate für die Authentifizierung umgestellt, nicht wahr? RICHTIG? Ein Mann darf träumen…

Theoretisch bräuchten wir für Zertifikatrotation nicht mal "Application.ReadWrite.OwnedBy" Berechtigungen, aber es ist mir nicht gelungen, dies in PowerShell umzusetzen – ich werde zu dem Thema vermutlich später zurück kehren oder würde mich sehr über einen Hinweis auf eine funktionierende Implementierung in der offenen Q&A-Frage freuen…

Da ich die wirklich coole Lösung (vorerst) nicht demonstrieren kann, habe ich nicht viel zur Microsoft Dokumentation hinzuzufügen. Ich verwende jedoch das Zertifikat aus dem Zertifikatsspeicher:

Beachten Sie, dass dieser Befehl auf der App Registration ALLE DERZEIT KONFIGURIERTEN ZERTIFIKATE ERSETZT.

$Certificate = Get-Item "Cert:\CurrentUser\My\$($graphConfig.newThumb)" -ErrorAction Stop
$params = @{
    keyCredentials = @(
        @{
            type = "AsymmetricX509Cert"
            usage = "Verify"
            key = [convert]::ToBase64String($Certificate.GetRawCertData())
            displayName = "CN=20240228"
        }
    )
}
Invoke-MgGraphRequest PATCH "https://graph.microsoft.com/v1.0/applications(appId='$($graphConfig.clientID)')" -Body $params


Skalieren

Wir haben nun ein gutes Verständnis davon, wie wir einzelne Anwendungen verwalten können. Aber wie können wir unseren Scope erweitern? Es gibt Anwendungsfälle, bei denen wir die Kontrolle über viele Enterprise Apps / App Registrations behalten wollen (ich werde ab jetzt "Apps" als Kurzform für beide verwenden).

Denken Sie an Infrastructure as Code-Lösungen wie Terraform oder vielleicht haben wir ein Backup-System, das Graph API-Ratenlimits umgehen möchte, indem mehr EApps erstellt werden.

Im Wesentlichen benötigen wir mindestens eine "Manager"-App und eine Reihe von "Worker"-Apps. Die lästige Lösung wäre, im Voraus eine Reihe von Apps zu erstellen – und danach die Basis-Zuweisung der Owner durchzuführen. Nicht ideal, aber glücklicherweise ist es viel einfacher – wenn wir "Application.ReadWrite.OwnedBy" auf unserer "Head"-App verwenden, können wir sie dann nutzen, um bei Bedarf Apps zu erstellen.

Wir können auch den servicePrincipal-Endpoint aufrufen, um zugehörige Enterprise Apps zu erstellen, aber das sieht im Grunde gleich aus

Invoke-MgGraphRequest POST "https://graph.microsoft.com/v1.0/applications" -body @{ displayName = "CreatedByApp" }

Die ausführende Enterprise App wird automatisch als Owner der neuen App hinzugefügt:

newAppReg


Allerdings wird es eine Herausforderung sein, den Überblick über all unsere Owner Verhältnisse zu behalten. Die Lösungen, die mir derzeit bekannt sind, sind den Aufbau einer Automatisierung basierend auf Custom Security Attributes oder die Erstellung eines Tracking-Systems in directory extensions. In jedem Fall sind dies keine großartigen Optionen – sind wir also dazu verdammt, entweder viel zu breite Berechtigungen zu vergeben oder zu versuchen, ein potenziell komplexes Netz von Besitzverhältnissen im Auge zu behalten?


Die "Beste" Option

Wenn wir wirklich den Ansatz von "least privilege" verfolgen wollen, sind Graph API Scopes nicht der Weg. Im Kern sind Apps Entra ID Objekte, was custom roles zu einer deutlich granulareren Option für das Berechtigungsmanagement macht. "Application.ReadWrite.OwnedBy" ist zufällig fast ein direkter Ersatz für das häufig verwendete "Application.ReadWrite.All".

OAuth Scopes haben den Vorteil, dass sie leicht zwischen Tenants übertragbar sind – wenn eine Anwendung nur für Ihren Tenant erstellt wird, ist das kein stichhaltiges Argument, und wenn sie einer Multi-Tenant-App diese Rechte geben sollen müssen sie dem Entwickler wirklich WIRKLICH vertrauen…

Sobald wir uns die in Entra ID verfügbaren Optionen anschauen, gibt es eine schmerzlich offensichtliche Lösung: Für Benutzer, Gruppen und Geräte können wir bereits Administrative Units erstellen. Diese ermöglichen das Einschränken von integrierten und benutzerdefinierten Rollen auf Teilbereiche eines Tenants, einschließlich der Erstellung neuer Objekte innerhalb des Geltungsbereichs.
Somit hätten wir eine leicht skalierbare und nachvollziehbare Sammlung von Apps.
Leider wird die Verwendung von AUs zur Verwaltung von Apps derzeit (März 2024) von Microsoft nicht unterstützt


Abschließende Worte

Bei Ihrer nächsten Überprüfung der App-Berechtigungen fragen Sie Entwickler, ob es möglich wäre, weniger Berechtigungen zu verwenden. Denken Sie daran, dass Sicherheit oft sekundär im Entwicklungsprozess ist. Fordern Sie die Behauptung ‘wir brauchen das einfach‘ heraus, indem Sie nach den spezifischen Anwendungsfällen suchen, die abgedeckt werden müssen – vielleicht gibt es Lösungen, die noch nicht in Betracht gezogen wurden.

Andererseits, auch wenn ich erläutere, wie wir Application.ReadWrite.All ersetzen können und sollten, ist dies nicht immer eine gute Idee. Wenn es ein System gibt, das 90% der Apps besitzt, rechtfertigt der Verwaltungsaufwand normalerweise nicht die geringfügig erhöhte Sicherheit.
Außerdem, wenn Sie die Option von Entra ID benutzerdefinierten Rollen interessant finden, ist es nicht einfach zu verstehen, welche Entra ID Berechtigungen für eine bestimmte Graph-Operation notwendig sind – Sie müssen bereit sein, ein wenig Trial and Error in Kauf zu nehmen.


Habe ich einen gängigen Anwendungsfall übersehen oder möchten Sie diskutieren, warum man Application.ReadWrite.All benötigen könnte? Ich möchte Ihre E-Mail-Adresse nicht, also bitte diskutieren Sie in meinem zugehörigen LinkedIn-Post.

Wenn Sie an den Dingen interessiert sind, die ich mache, folgen Sie mir auf LinkedIn.

Zuletzt bearbeitet: 4. März 2024