Social Media Login Vulnerability

This article covers things I missed during an implementation of social media login feature for a web application with Facebook and Google+. Both vulnerabilities are reported by a bounty hunter under the alias Sphinx, so the credit (and bounty) goes to him.

Facebook Login API

The vulnerability existed on an API endpoint to let the user login via Facebook in the mobile apps. The backend server then accepts the token issued by Facebook and checks the token’s validity. If the token is valid, the backend API will authenticate the user.

The code we had was something like this.

fb_uid    = params[:uid]
fb_token  = params[:token]
user      = User.find_by(fb_uid: fb_uid)
graph     = Koala::Facebook::API.new(fb_token)
user_info = graph.get_object('me')

Given a valid Facebook token, the server will treat the authentication request as a valid request regardless of which application the token is issued to. Therefore, an attacker with a registered Facebook app can have the victim to use their malicious Facebook app. The data they acquired from the malicious app can be stored in a database, and they can use the retrieved Facebook uid and token to authenticate as the victim.

To patch the vulnerability, we need to check whether the token is issued for a valid Facebook app before letting the system authenticate the user.

fb_uid    = params[:uid]
fb_token  = params[:token]
user      = User.find_by(fb_uid: fb_uid)
graph     = Koala::Facebook::API.new(fb_token)
app_info  = graph.get_object('app')
user_info = graph.get_object('me')
raise 'Invalid Token' unless app_info['id'] == 'Your Facebook App ID'

While this vulnerability is incapable of causing monetary loss to the victim, it allows the attacker to access their information via the REST API.

Google+ Login

The problem with Google+ login is not as severe with the Facebook login API in the sense that the vulnerability doesn’t give the attacker access over the victim’s account. Yet, it might still be used for nasty things.

To use the Google+ login, we need to request token from Google+ by redirecting the user to this URL.

https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.profile.emails.read&state=generate_a_unique_state_value&redirect_uri=callback_uri&response_type=code&client_id=client_id&access_type=offline

We need to send the redirect_uri parameter with the callback address and client_id parameter with the client ID assigned to our app by Google. The Google+ login will work with both parameters correctly set.

What we missed was the state parameter. Just like a CSRF token, the state parameter serve as a token to prevent the victim from being attacked with CSRF.

Without the state parameter, it’s possible to trick the victim to log in with the attacker’s account by providing a link to the Google+ login function in our app with his access token in it. We didn’t validate the state parameter when we implemented the function, and it allows the CSRF vulnerability to exist in the Google+ login function.

We need to set the state parameter with a pseudorandom value and keep the value in the app to validate the Google+ login request. We’ll need something like this in the code.

state = 'Some Pseudorandom Value'

if params[:state] != state
  # deny the request for Google+ login
else
  # do the actual login sequence
end

Conclusion

Social media login might be convenient for the users. But without a proper implementation, it might leave open vulnerabilities in the application that put the users at risk. We should be really careful on this.