Today I'm going to talk about the Facebook password reset page and a series of flaws that allowed anyone to brute force a user's primary email or mobile phone number.
TL;DR Iterate over phone numbers/emails, dump valid but anonymous data, use manual/automated enumeration to find specific owners.
TL;DR Iterate over phone numbers/emails, dump valid but anonymous data, use manual/automated enumeration to find specific owners.
Forgot my password...
To perform a password reset on Facebook you first have to find your account. You can conveniently search by either your email, phone, id, username or full name.
If the account has used your ip range before, you will be shown the username, picture of the user, obfuscated email and obfuscated phone number and from here you can choose how you want to reset your password.
To perform a password reset on Facebook you first have to find your account. You can conveniently search by either your email, phone, id, username or full name.
Once you press continue Facebook will send you a one time password reset link/code that can be used to change your password.
Under the hood
When you press that first "Search" button, two requests are sent.
- Request #1 (POST /ajax/login/help/identify.php?ctx=recover) seems to be an initial check to verify if the data you are requesting actually exists. If it does, a cookie is set and you are redirected (Request #2). If it doesn't exist you are given an error message.
- Request #2 (GET /recover/initiate) if you are redirected, the request passes the cookie from step one and the server will give you the password reset info, name/image/email/phone.
So where are the flaws?
Issue #1 Verify information through look-up
Did you notice how we were able to search for an arbitrary user/email/phone number and obtain the username/image/obfuscated data? The lookup feature fundamentally contains and operates using an information disclosure vulnerability. We're able to gain limited information about specific users we're not friends with and even evade privacy settings.
Using Request #1 we can effectively ask the Facebook server does this email/phone number exist in your database? And the server will reply yes or no. It won't however tell us who the owner is. This kind of information disclosure is present in a number of features on Facebook.
Using Request #2 we can ask the server who the specific owner of a particular phone/email is and the server will tell us. Awesome, but there are some server-side checks which mean this information is only disclosed (i think) if you have a specific cookie, user-agent or if the request is coming an ip range the account has used before. So user lookups are still possible with Request #2 but you'll only be able to find users who've used your ip range. Example below.
Invalid IP - Searching by email, our email is echo'ed back but username is not given as the account has never logged in from my network.
Valid IP - After logging in, again searching by email we see the email, but this time we are also given the username and image.
Issue #2 No throttling of requests
For Request #1 no throttling mechanism was used. This meant it was possible to repeatedly query the Facebook server, effectively allowing mass collection of valid users/emails/phone numbers (a spammers dream come true).
If you send Request #2 too often you'll hit a captcha, but not a hard cap. So for a determined attacker they could just repeatedly enter the captcha.
Issue #3 Obfuscation fail
Although it may seem trivial, by giving away two characters plus the domain for email addresses and four out of eleven characters for phone numbers Facebook had made these parameters significantly easier to brute force.
Take my phone number for example. My number is 11 digits long. The first four digits are from the carrier and for most countries there will be a small number of possibilities (50 in my case). Facebook have told us the last four digits. So my 11 digit number (99,999,999,999 possible combinations) has been reduced to 11 - 4 (FB) - 4 (Carrier) = 3 digits (1000 possible combinations per carrier range!).
The same applies to email addresses although they are harder to guess because of the larger search space.
Putting it all together...
Taking advantage of the issues above it is possible to effectively brute force a specific user's phone number or email. For a phone number the attack goes like this:
So lets see some of this stuff in action...
Enumerating everything!
The simplest attack is just to enumerate everyone/everything. You won't know the owners of the emails/phone numbers but you'll still know they are valid and are being used by a Facebook user.
All we need to do for enumeration is send the following POST request containing our guess, which can be a user/email/phone number.
Using Burp Intruder I performed two tests, one where I found valid phone numbers, another where I found valid emails. The first payload I used looked like this <carrier><payload><3362>. Responses of 809 indicate invalid numbers while 1081/1102 indicate valid numbers.
For email testing I submitted the payload johnsmith<number>@gmail.com. Same as before, 809 indicates invalid emails where as 1102/1123 indicates valid emails.
And because there is no captcha or throttling we can just keep going and going until we've found every email/phone number registered with Facebook and spam/spear phish them all :)
Targeted Pwnage
Under the hood
When you press that first "Search" button, two requests are sent.
- Request #1 (POST /ajax/login/help/identify.php?ctx=recover) seems to be an initial check to verify if the data you are requesting actually exists. If it does, a cookie is set and you are redirected (Request #2). If it doesn't exist you are given an error message.
- Request #2 (GET /recover/initiate) if you are redirected, the request passes the cookie from step one and the server will give you the password reset info, name/image/email/phone.
So where are the flaws?
Issue #1 Verify information through look-up
Did you notice how we were able to search for an arbitrary user/email/phone number and obtain the username/image/obfuscated data? The lookup feature fundamentally contains and operates using an information disclosure vulnerability. We're able to gain limited information about specific users we're not friends with and even evade privacy settings.
Using Request #1 we can effectively ask the Facebook server does this email/phone number exist in your database? And the server will reply yes or no. It won't however tell us who the owner is. This kind of information disclosure is present in a number of features on Facebook.
Using Request #2 we can ask the server who the specific owner of a particular phone/email is and the server will tell us. Awesome, but there are some server-side checks which mean this information is only disclosed (i think) if you have a specific cookie, user-agent or if the request is coming an ip range the account has used before. So user lookups are still possible with Request #2 but you'll only be able to find users who've used your ip range. Example below.
Invalid IP - Searching by email, our email is echo'ed back but username is not given as the account has never logged in from my network.
Valid IP - After logging in, again searching by email we see the email, but this time we are also given the username and image.
Issue #2 No throttling of requests
For Request #1 no throttling mechanism was used. This meant it was possible to repeatedly query the Facebook server, effectively allowing mass collection of valid users/emails/phone numbers (a spammers dream come true).
If you send Request #2 too often you'll hit a captcha, but not a hard cap. So for a determined attacker they could just repeatedly enter the captcha.
Issue #3 Obfuscation fail
Although it may seem trivial, by giving away two characters plus the domain for email addresses and four out of eleven characters for phone numbers Facebook had made these parameters significantly easier to brute force.
Take my phone number for example. My number is 11 digits long. The first four digits are from the carrier and for most countries there will be a small number of possibilities (50 in my case). Facebook have told us the last four digits. So my 11 digit number (99,999,999,999 possible combinations) has been reduced to 11 - 4 (FB) - 4 (Carrier) = 3 digits (1000 possible combinations per carrier range!).
The same applies to email addresses although they are harder to guess because of the larger search space.
Putting it all together...
Taking advantage of the issues above it is possible to effectively brute force a specific user's phone number or email. For a phone number the attack goes like this:
- Get on an ip range that your target has recently used
- Search for the user by id, username or email
- Obtain the last four digits of the phone number
- Search the entire number space (with Request #1) and build a list of possible numbers
- Re-submit numbers one by one (Request #1 and Request #2), manually entering the captcha
- Eventually one of the numbers will show you the reset page for your target, which tells us that number is the targets number!
So lets see some of this stuff in action...
Enumerating everything!
The simplest attack is just to enumerate everyone/everything. You won't know the owners of the emails/phone numbers but you'll still know they are valid and are being used by a Facebook user.
All we need to do for enumeration is send the following POST request containing our guess, which can be a user/email/phone number.
Using Burp Intruder I performed two tests, one where I found valid phone numbers, another where I found valid emails. The first payload I used looked like this <carrier><payload><3362>. Responses of 809 indicate invalid numbers while 1081/1102 indicate valid numbers.
Targeted Pwnage
Although a targeted attack is harder, it's still possible with a little preparation. To demonstrate, I'll attempt to find the phone number for my target user mark.zockerberg.54.
First up, to evade the Facebook checks we'll need to attack from an ip in a range that our target has recently used. Next we'll need some way to identify our user - id, name, username and email/phone number are all options. I start off by searching for the username in the request below:
Scanning every carrier range (changing 0926 for 0927, 0928 etc.) we will get a reasonably small list of potential numbers.
Because of the captcha on Request #2 we can't automate number to name enumeration, so the only way to convert all of our numbers to users is by manually searching and completing the captcha. Assuming you have 300 numbers and it takes you 15 seconds to enter the captcha for each number, it will take just over an hour to check them all. Eventually one of the numbers you enter will lead you back to the password reset page for your target, effectively confirming that the number belongs to that user!
If there was no captcha or you had a bypass you'd be able to automate username lookups something like the following:
Here I converted the sfiu cookie direct to user and you can see we get our user Mark Zockerberg (the captcha kicks in right after)
The techniques I used above could also have been used to find a user's email address. Although with emails containing both letters and numbers the search space is a lot larger, e.g. email with 8 characters = 36^8 combinations! With that said, a lot of users will use emails that contain dictionary words or their names, so it may not actually be that hard. Also for an attacker targeting a specific company he could dump emails from their website/google and run those addresses through the password reset to locate and then target their employees, could provide some interesting results.
Final Thoughts
The classic email password reset can and is often implemented completely securely with no information disclosure. Both Google and Facebook however have created multi-step resets that involve information disclosure. For me it just doesn't sit well and I suspect we might see changes in the future.
To mitigate the attacks discussed above Facebook reduced the phone number digits disclosed from four to two and implemented a captcha check for Request #1. The fundamental issue of providing a look-up feature though has not been modified. So if you have a botnet or captcha bypass (watch this space!) you can still dump valid numbers and emails.
I hope you guys have enjoyed this post, questions, comments, are always appreciated.
Pwndizzle over and out