Vimeo account takeover

Apr 3, 2015

A while back I was playing around with the OAuth2 spec and discovered a flaw in how Vimeo associates Facebook accounts. Their Facebook connect callback URL was vulnerable to a Cross Site Request Forgery, allowing an attacker to connect their Facebook account with a victim’s Vimeo account.


If you try to connect a Facebook account to your Vimeo account, Vimeo sends you to the following URL:,public_profile,publish_actions,user_friends&state=f599e2d1b07d64214116415646a6a653

Once you accept the authorization prompt, Facebook returns an HTTP 302, redirecting you back to Vimeo’s redirect_uri along with a code that Vimeo uses to access your Facebook info and associate the accounts.

Along with the code, Facebook sends back the state value from the URL above so that Vimeo can verify that the callback request is authentic and originated from the same browser that started the flow.

Vimeo correctly set a state parameter, but used code similar to the following to verify the state:

def callback
  if params[:state] == session[:state]
    # associate accounts
    # error csrf detected!

But what is session[:state] when the request is not authentic?

Both params[:state] and session[:state] will be null.

Attack Scenario

  1. The attacker logs into their own Vimeo account and beings the flow to connect their Facebook account.

  2. The attacker accepts all permissions, but uses a tool like NoRedirect to block their browser from making the final redirect back to Vimeo:<CODE>&state=<state>#_=_
  1. The attacker uses this URL, omitting the state parameter as part of a CSRF attack on a page that the victim (who is currently logged into Vimeo) is likely to view.
<img src='<CODE>#_=_' />
  1. The victim’s Vimeo account is now associated with the attacker’s Facebook account. The attacker can now use their Facebook to login to the victim’s Vimeo account.


The callback must ensure that the state query parameter matches the persisted state token AND that both values are non-nil.