Desktop Applications, OAuth and CSRF

Table of Contents

Taking advantage of the OAuth 2 protocol is fairly trivial for mobile and web applications, as there are a number of libraries and integrations readily available. The advantage of this is that any security critical code has already been tried and tested, and is often being maintained by large organisations or open-source communities.

However, implementing third-party authorisation via OAuth2 on a desktop application can prove quite tricky. Last year I discovered a CSRF vulnerability in a popular desktop application which would allow the attacker full access to the GitHub and Bitbucket account of the victim under the following circumstances:

  • The victim has already used the application and granted it access to their account.
  • The victim clicks a link sent by the attacker in a browser session where they are logged into the third party.

The vulnerability was fixed immediately and the company behind the product was nice enough to send me a bag of goodies, but since then I have noticed this vulnerability to be quite a common design pattern amongst desktop applications. In this article I’ll describe how the attack is carried out, and how it can easily be fixed

Note: I’m definitely not claiming to have discovered this vulnerability, I’m sure you’ll be able to find plenty more well informed articles about it.

The State Parameter

The state parameter is (ironically in this case) designed to protect against Cross-Site Request Forgery attacks on the OAuth2 protocol. The value is typically generated at the start of the authorisation pipeline, passed as a query parameter to the third-party application and is then finally passed back to the application to verify the response.

The common design pattern which exhibits this vulnerability has the following steps:

  1. The desktop application generates a unique state parameter value, such as a GUID.
  2. The desktop application opens the OAuth2 authorisation link with the generated state parameter and a redirect back to a HTTP endpoint owned by the application e.g. https://facebook.com/oauth2/authorize?state=123&redirect=https://my.application/oauth2/callback (Most standard parameters have been omitted for simplicity)
  3. The user clicks through the authorisation code grant, explicitly grants the application access, and then is eventually redirected back to the application with an authorisation code.
  4. The application saves this authorisation code against the state parameter passed as a query parameter.

Throughout this entire process the desktop application is polling an endpoint on the application web server providing the state parameter as a query string. When the user has finished authorising the application, the endpoint will respond with some token providing the application access to this user’s resources (This could be the third-party application refresh token or some other authentication token for the application itself, depending on the application).

So, what’s the problem here?

The Attack

If a user has already authorised the application access to this third party application (i.e. they have already used the application, clicked the authorisation link and explicitly granted the application access), they could be vulnerable to a CSRF attack.

As the state parameter is generated by the desktop application, an attacker could just do the following:

  1. Generate their own state parameter and construct the authorisation URL.
  2. Disguise the URL and use social engineering to convince a vulnerable user to open it.
  3. Begin polling the confirmation endpoint mentioned above with the generated state parameter.

If the victim does click the link and has already logged into the third-party application, they will be automatically forwarded through the grant and will arrive at the application call-back endpoint with an authorisation code for the third party. The attacker would then be able to retrieve the victim’s credentials using the state parameter.

I’m not entirely sure if all third-party applications exhibit this ‘automatic re-authorisation’ behaviour, but certainly most of the major applications such as GitHub, Bitbucket, Facebook, Google do!

Prevention

Good news, preventing the attack is simple! Instead of providing the redirect URL as a live endpoint on the application’s web server, the desktop application should instead open a HTTP endpoint on the local machine and handle the callback itself. With this solution, the following steps would be taken:

  1. The desktop application generates a unique state parameter value, such as a GUID.
  2. The desktop application opens the OAuth2 authorisation link with the generated state parameter and a redirect back to a HTTP endpoint on the local machine e.g. https://facebook.com/oauth2/authorize?state=123&redirect=http://localhost/oauth2/callback
  3. The user clicks through the authorisation code grant, explicitly grants the application access, and then is eventually redirected back to the local callback endpoint.
  4. The desktop application receives this callback and uses the authorisation code to complete the flow.

Running a HTTP server might seem like a lot of effort (especially with the additional privileges required), but it’s much better than putting your users at risk!