Dangling Redirect URLs
Summary¶
An Azure Application Redirect URI (Uniform Resource Identifier) is a critical component in the OAuth 2.0 and OpenID Connect authentication flows. It specifies where the identity provider (such as Azure AD) should redirect users after they have authenticated. This URI is registered in the Azure portal as part of your application configuration to ensure security and proper flow of authentication.
If the Redirect URI for an application can be hijacked then it is possible to construct a link that will force a victim through the SSO flow and end in the disclosure of an OAUTH authentication code that (in some circumstances) can be exchanged for refresh/access tokens.
I'll run through an example of finding exploitable redirect URIs from ROADRecon/MS graph data.
Enumerate Redirect URIs¶
Redirect URIs can be extracted from the ROADRecon DB into a CSV:
The domains for the various replyUrls can be extracted and uniqued.
Looping over the domains from the URIs provides an easy way to see which ones don't resolve:
Is the application actually exploitable?¶
Armed with a list of potentially hijackable domains, you get excited and think that you can just start harvesting auth codes and exchanging them for tokens. Not so fast. Redirect URIs are defined in one of a few flavours:
- Public
- Web
- Single Page Application (SPA)
If the URI is classed as Web or SPA, then the application is required to provide a client_secret value when exchanging the authorisation code for tokens. Unless for some reason you have the client secret for that application (or can add one) then you're out of luck.
Public redirect URIs are used where there's no expectation that the client_secret could be kept…..secret. So for example a desktop client that could be decompiled. So if you get an authorisation code for one of those, then you can directly exchange it for a refresh/access token and away you go.
How do you tell whether the Redirect URI is public?¶
Unfortunately, the roadrecon data (nor the graph.windows.net endpoint) seem to give up this information, but querying the msgraph (graph.microsoft.com) endpoint for the application's object ID (NOT the application ID) gives you the info. An example request and response below, where the applications OBJECT ID is 2a0375f9-6a41-4d97-9044-327d36e4d400:
The response shows that the application has a publicClient redirect URI, so we're in luck.
In this case, the redirect URI is an Azure function app, so if we go and register a new function app with the name of idonotexist then we should be able to run a function that listens on the signin-oidc path. A few things to note:
If you make a C# Azure function, you can't have a hyphen in the path name. It doesn't tell you this, it will just silently change signin-oidc to signin_oidc and cause you unrequired pain. You also have to faff around setting up an IDE if you choose C#/Python. If you pick Node/JS then you can edit the code directly in the Azure portal, which makes things a bit easier….so pick node when you create the function.
Azure functions automatically prefix your function name with /api/, so if you just span up a vanilla function called signin-oidc, then your function URL would end up being https://idonotexist.azurewebsites.net/api/signin-oidc
You don't want that, as it needs to match what the Azure application has configured as it's redirect URI. To get around this, you can add a blank custom prefix in the hosts.json file in the function.
It should look like this:

If you're unable to edit the file here because it's in read only mode, then you might have to set up an IDE. I'll try to remember to put a note at the end of this section on how to do that.
So you've got your function up and running at the right URL and now you want to make sure you can log POST/GET requests to steal the token. Pop this in your function code:
If you've enabled Application Insights on the function (which is the default) then when you can nip over to the Logs blade and see the incoming requests to the function:

Now it's time to construct a link that you can send to someone. You will need:
- client_id = The application ID (appID in ROADRecon)
- scope = The graph endpoint you want to get tokens for
- code_challenge = The value in the link below should work but you can generate your own
- redirect_uri = Your newly registered function/domain
- Target tenant - this goes immediately after the login.microsoftonline.com part
The final link should look like this:
So you send this to someone, they click on it whilst logged in to Office/Azure and if they have access to the application in question, you receive a code in your logs, like this:

You can then exchange this token via the OAUTH flow for some nice tokens, making sure all the parameters from the above requests/logs align:
You should get a response similar to the following: