Azure / SimuLand

Understand adversary tradecraft and improve detection strategies
MIT License
694 stars 79 forks source link

API Call for oauth2PermissionGrants #13

Closed j-stoner closed 3 years ago

j-stoner commented 3 years ago

In grantDelegatedPermissionsToApplication.md, the last step before the detections is a POST for the permissions grant against the service principals. As I work through this, I get a 409 conflict which upon additional research seems to indicate that the permissions already exist. It isn't clear to me that because we have done the permissions for mail.read in an earlier step if that is why it is throwing this message or what or if this needs to be a patch instead. Can that be clarified a bit? BTW, this is fantastic work.

Cyb3rWard0g commented 3 years ago

Hello @j-stoner ! Thank you very much for the feedback.

Regarding the 409 conflict. The idea is to first add the permissions but then grant it. mmm I wonder if the API to grant is also trying to add the permissions.

Cyb3rWard0g commented 3 years ago

Can you share where exactly you get the error message please? Is it right at this part?

Grant permissions with oauth2PermissionGrants API.

$body = @{
  clientId = $ServicePrincipalId
  consentType = "AllPrincipals"
  principalId = $null
  resourceId = $ResourceSvcPrincipal.value[0].id
  scope = "$permissions"
  startTime = "$((get-date).ToString("yyyy-MM-ddTHH:mm:ss:ffZ"))"
  expiryTime = "$((get-date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ss:ffZ"))"
}

$headers = @{
  “Authorization” = “Bearer $MSGraphAccessToken”
  "Content-Type" = "application/json"
}
$params = @{
  "Method" = "Post"
  "Uri" = “https://graph.microsoft.com/v1.0/oauth2PermissionGrants”
  "Body" = $body | ConvertTo-Json –Compress
  "Headers" = $headers
}
Invoke-RestMethod @params
Cyb3rWard0g commented 3 years ago

I will do some tests today and improve this section too :)

j-stoner commented 3 years ago

Yes that section is causing the issue.

I start with the user.read delegated permission with a status of granted and then run the scripts on this page sequentially, starting with List Azure AD Applications and then going to Update Azure AD Application Required Permission and concluding with Grant Delegated Permissions. I have used one or many additional permissions and they are all added correctly including mail.read and directory.real.all.

When I add a trap to the end of this search, the error is {"error":{"code":"Request_MultipleObjectsWithSameKeyValue","message":"Permission entry already exists.

This makes me think that something about the permissions being added is causing it to think it should be an update and not a create event.

I decided to scramble the section order and ran the scripts in this order; List Azure AD Applications, Grant Delegated Permission, Update Azure AD Application Required Permission. I created a new app, which again only had user.read permission. I did NOT grant admin consent and ran a script that Defined the initial variables of the app and permissions as well as the script under the heading Get Microsoft Graph Service Principal object to retrieve permissions references from it. I then ran script above that was flagged as being a problem.

I got the same error message, {"error":{"code":"Request_BadRequest","message":"Permission entry already exists but when I refreshed the Azure UI, the lower section of API Permissions had a heading of Other Permissions Granted For.... and it was populated with those 6 permissions in my above script. So that was progress.

I then ran the permission scripts that were under Update Azure AD Application Required Permissions, ie the first 4 script blocks to assign perms. After a refresh of the UI, things were all good as illustrated in your visuals. Screen Shot 2021-07-08 at 1 50 17 PM

So to make a long story even longer, it looks like while you get a 409 error either way, running the Grant Delegated Permissions script blocks first then the others seem to get you to the desired end state. Not sure if this will vary in other testing but I was trying to solve this for a bit and wanted to see if I was missing something else here.

Hope this is helpful.

Cyb3rWard0g commented 3 years ago

I start with the user.read delegated permission with a status of granted and then run the scripts on this page sequentially, starting with List Azure AD Applications and then going to Update Azure AD Application Required Permission and concluding with Grant Delegated Permissions. I have used one or many additional permissions and they are all added correctly including mail.read and directory.real.all.

Does it mean that it worked the first time? You say that it worked with multiple permissions?

Cyb3rWard0g commented 3 years ago

I then ran the permission scripts that were under Update Azure AD Application Required Permissions, ie the first 4 script blocks to assign perms. After a refresh of the UI, things were all good as illustrated in your visuals.

which script did you use? this one? https://github.com/Azure/SimuLand/blob/main/2_deploy/aadHybridIdentityADFS/scripts/Update-AzureADAppPermissions.ps1

Cyb3rWard0g commented 3 years ago

So to make a long story even longer, it looks like while you get a 409 error either way, running the Grant Delegated Permissions script blocks first then the others seem to get you to the desired end state. Not sure if this will vary in other testing but I was trying to solve this for a bit and wanted to see if I was missing something else here

Would you mind sharing the scripts and code used to get it to work? Maybe if you are using sections from the docs and then the script, can you just point to the script lines in GitHub or sections in the docs? Please

j-stoner commented 3 years ago

It didn't provide the check with Granted for... when the service principals where run when running the scripts from top to bottom on the page. This is the result... Screen Shot 2021-07-08 at 2 50 34 PM

j-stoner commented 3 years ago

Not a problem, just not sure how best to do it.

List Azure AD Applications

$headers = @{“Authorization” = “Bearer $MSGraphAccessToken”} $params = @{ “Method” = “Get” “Uri” = “https://graph.microsoft.com/v1.0/applications” “Headers” = $headers } $AzADApps = Invoke-RestMethod @params

Define initial variables

$ServicePrincipalId = $AzADAppSp.value[0].id $resourceSpDisplayName = ‘Microsoft Graph’ $PropertyType = 'oauth2PermissionScopes' $permissions = @(‘Mail.ReadWrite’)

Get Microsoft Graph Service Principal object

$headers = @{“Authorization” = “Bearer $MSGraphAccessToken”} $params = @{ "Method" = "Get" "Uri" = "https://graph.microsoft.com/v1.0/servicePrincipals?$filter=displayName eq '$resourceSpDisplayName'" "Headers" = $headers } $ResourceSvcPrincipal = Invoke-RestMethod @params if ($ResourceSvcPrincipal.value.Count -ne 1) { Write-Error "Found $($ResourceSvcPrincipal.value.Count) service principals with displayName '$($resourceSpDisplayName)'" } `

Grant permissions with oauth2PermissionGrants API.

`$body = @{ clientId = $ServicePrincipalId consentType = "AllPrincipals" principalId = $null resourceId = $ResourceSvcPrincipal.value[0].id scope = "$permissions" startTime = "$((get-date).ToString("yyyy-MM-ddTHH:mm:ss:ffZ"))" expiryTime = "$((get-date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ss:ffZ"))" }

$headers = @{ “Authorization” = “Bearer $MSGraphAccessToken” "Content-Type" = "application/json" } $params = @{ "Method" = "Post" "Uri" = “https://graph.microsoft.com/v1.0/oauth2PermissionGrants” "Body" = $body | ConvertTo-Json –Compress "Headers" = $headers } Invoke-RestMethod @params `

Update Azure AD Application Required Permissions (OAuth2Permissions)

`$Application = $AzADApps.value[0] $resourceSpDisplayName = ‘Microsoft Graph’ $PropertyType = 'oauth2PermissionScopes' $ResourceAccessType = 'Scope' $permissions = @(‘Mail.ReadWrite’)

Get Microsoft Graph Service Principal object to retrieve permissions from it.

$headers = @{“Authorization” = “Bearer $MSGraphAccessToken”} $params = @{ "Method" = "Get" "Uri" = "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=displayName eq '$resourceSpDisplayName'" "Headers" = $headers } $ResourceResults = Invoke-RestMethod @params $ResourceSvcPrincipal = $ResourceResults.value[0] if ($ResourceResults.value.Count -ne 1) { Write-Error "Found $($ResourceResults.value.Count) service principals with displayName '$($resourceSpDisplayName)'" } `

$ResourceAccessItems = @() Foreach ($AppPermission in $permissions) { $RoleAssignment = $ResourceSvcPrincipal.$PropertyType | Where-Object { $_.Value -eq $AppPermission } $ResourceAccessItem = [PSCustomObject]@{ "id" = $RoleAssignment.id "type" = $ResourceAccessType } $ResourceAccessItems += $ResourceAccessItem }

`if ($resourceAccess = ($Application.requiredResourceAccess | Where-Object -FilterScript { $.resourceAppId -eq $ResourceSvcPrincipal.appId })) { Foreach ($item in $ResourceAccessItems) { if ($null -eq ($resourceAccess.resourceAccess | Where-Object -FilterScript { $.type -eq "$ResourceAccessType" -and $_.id -eq $item.id })) { $Application.requiredResourceAccess[$Application.requiredResourceAccess.resourceAppId.IndexOf($ResourceSvcPrincipal.appId)].resourceAccess += $item } } } else { $RequiredResourceAccess = [PSCustomObject]@{ "resourceAppId" = $ResourceSvcPrincipal.appId "resourceAccess" = $ResourceAccessItems }

Update/Assign application permissions

$Application.requiredResourceAccess += $RequiredResourceAccess

} `

$AppBody = $Application | Select-Object -Property "id", "appId", "displayName", "identifierUris", "requiredResourceAccess" $headers = @{ “Authorization” = “Bearer $MSGraphAccessToken” "Content-Type" = "application/json" } $params = @{ "Method" = "Patch" "Uri" = "https://graph.microsoft.com/v1.0/applications/$($AppBody.id)" "Body" = $AppBody | ConvertTo-Json -Compress -Depth 99 "Headers" = $headers } $updatedApplication = Invoke-WebRequest @params -usebasicparsing if ($updatedApplication.StatusCode -eq 204) { return "Required permissions were assigned successfully" }

Cyb3rWard0g commented 3 years ago

Not a problem, just not sure how best to do it.

List Azure AD Applications

$headers = @{“Authorization” = “Bearer $MSGraphAccessToken”} $params = @{ “Method” = “Get” “Uri” = “https://graph.microsoft.com/v1.0/applications” “Headers” = $headers } $AzADApps = Invoke-RestMethod @params

Define initial variables

$ServicePrincipalId = $AzADAppSp.value[0].id $resourceSpDisplayName = ‘Microsoft Graph’ $PropertyType = 'oauth2PermissionScopes' $permissions = @(‘Mail.ReadWrite’)

Get Microsoft Graph Service Principal object

$headers = @{“Authorization” = “Bearer $MSGraphAccessToken”} $params = @{ "Method" = "Get" "Uri" = "https://graph.microsoft.com/v1.0/servicePrincipals?$filter=displayName eq '$resourceSpDisplayName'" "Headers" = $headers } $ResourceSvcPrincipal = Invoke-RestMethod @params if ($ResourceSvcPrincipal.value.Count -ne 1) { Write-Error "Found $($ResourceSvcPrincipal.value.Count) service principals with displayName '$($resourceSpDisplayName)'" } `

Grant permissions with oauth2PermissionGrants API.

`$body = @{ clientId = $ServicePrincipalId consentType = "AllPrincipals" principalId = $null resourceId = $ResourceSvcPrincipal.value[0].id scope = "$permissions" startTime = "$((get-date).ToString("yyyy-MM-ddTHH:mm:ss:ffZ"))" expiryTime = "$((get-date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ss:ffZ"))" }

$headers = @{ “Authorization” = “Bearer $MSGraphAccessToken” "Content-Type" = "application/json" } $params = @{ "Method" = "Post" "Uri" = “https://graph.microsoft.com/v1.0/oauth2PermissionGrants” "Body" = $body | ConvertTo-Json –Compress "Headers" = $headers } Invoke-RestMethod @params `

Update Azure AD Application Required Permissions (OAuth2Permissions)

`$Application = $AzADApps.value[0] $resourceSpDisplayName = ‘Microsoft Graph’ $PropertyType = 'oauth2PermissionScopes' $ResourceAccessType = 'Scope' $permissions = @(‘Mail.ReadWrite’)

Get Microsoft Graph Service Principal object to retrieve permissions from it.

$headers = @{“Authorization” = “Bearer $MSGraphAccessToken”} $params = @{ "Method" = "Get" "Uri" = "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=displayName eq '$resourceSpDisplayName'" "Headers" = $headers } $ResourceResults = Invoke-RestMethod @params $ResourceSvcPrincipal = $ResourceResults.value[0] if ($ResourceResults.value.Count -ne 1) { Write-Error "Found $($ResourceResults.value.Count) service principals with displayName '$($resourceSpDisplayName)'" } `

$ResourceAccessItems = @() Foreach ($AppPermission in $permissions) { $RoleAssignment = $ResourceSvcPrincipal.$PropertyType | Where-Object { $_.Value -eq $AppPermission } $ResourceAccessItem = [PSCustomObject]@{ "id" = $RoleAssignment.id "type" = $ResourceAccessType } $ResourceAccessItems += $ResourceAccessItem }

if ($resourceAccess = ($Application.requiredResourceAccess | Where-Object -FilterScript { $_.resourceAppId -eq $ResourceSvcPrincipal.appId })) { Foreach ($item in $ResourceAccessItems) { if ($null -eq ($resourceAccess.resourceAccess | Where-Object -FilterScript { $_.type -eq "$ResourceAccessType" -and $_.id -eq $item.id })) { $Application.requiredResourceAccess[$Application.requiredResourceAccess.resourceAppId.IndexOf($ResourceSvcPrincipal.appId)].resourceAccess += $item } } } else { $RequiredResourceAccess = [PSCustomObject]@{ "resourceAppId" = $ResourceSvcPrincipal.appId "resourceAccess" = $ResourceAccessItems } # Update/Assign application permissions $Application.requiredResourceAccess += $RequiredResourceAccess }

$AppBody = $Application | Select-Object -Property "id", "appId", "displayName", "identifierUris", "requiredResourceAccess" $headers = @{ “Authorization” = “Bearer $MSGraphAccessToken” "Content-Type" = "application/json" } $params = @{ "Method" = "Patch" "Uri" = "https://graph.microsoft.com/v1.0/applications/$($AppBody.id)" "Body" = $AppBody | ConvertTo-Json -Compress -Depth 99 "Headers" = $headers } $updatedApplication = Invoke-WebRequest @params -usebasicparsing if ($updatedApplication.StatusCode -eq 204) { return "Required permissions were assigned successfully" }

Niceee!!! So all that code worked to get the Granted check mark right? @j-stoner

j-stoner commented 3 years ago

Yeah, it was doing the service principal first then permissions that seemed to work...both threw 409 which is odd....

Cyb3rWard0g commented 3 years ago

Ohh so even when getting it work, it stills returns a 409 response code .Interesting. I am testing it today.

Cyb3rWard0g commented 3 years ago

Ok I figured it our after some testing. The issue that I had was the when I register an AD application for this lab, I was adding the User.Read delegated permissions and granting admin consent to it. Somehow, the Grant API was not liking me to grant another permissions when there was already another one granted.

I updated the instructions that after deploying the Hybrid environment for the first lab, an Azure AD app needs to be registered and added a delegated permissions User.Read, but it does not need to be granted admin consent.

After that, I ran through all the steps to update the application permissions and grant Mail.ReadWrite permissions and it worked fine. Thank you very much for all the details. It was very helpful to figure it out :)

I appreciate you taking the time @j-stoner to open the issue and provide details. Thank you!