Saturday, 6 December 2014

Reading local files from Facebook's server (fixed)


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 link
3. upload 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.


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
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
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 '

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:


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:


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


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
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{{schoolGroupName}}/_join_/
for example, in my test case:
victim with jxfXXXX@***.edu verified visits site with <img src="***/_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ć

Tuesday, 30 July 2013

SQL injections in Nokia sites.


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.


SQL injection in

Found this site using Google. The actual vulnerable link was:

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);
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:

->  "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. 

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.

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. subdomain SQL injection

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




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.


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....

After some sub-domain bruteforcing, I have found there is a Ganglia here:
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 "".
On the second page:
Visit the IP address, and you get Instagram's Ganglia. 
There was also a reflected XSS on<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:[[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. 


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:[[some profile]]/friends
Now, I go to [[some profile]]'s profile and get list of people who liked that
profile picture:[[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!

Thursday, 3 January 2013 cross site scripting and privilege escalation in Consumer Surveys


I have recently found a persistent cross site scripting and privilege escalation in Google Consumer Surveys. Here are proofs of concept for both vulnerabilities:
Cross site scripting (XSS)

You can create a new Google Consumer Survey here. I have entered  "</script><script>alert(document.cookie)</script> as name of my survey and clicked Continue. The JavaScript was executed. Now the problem was, how do I exploit this on other users?
When creating a survey, there are four steps. Step 1,3 and 4 links could be used to exploit it on other users, while Step 2 (still) gives a 500 Internal server error if viewing other people's surveys (I do not know why, maybe you can find something there :)). Here are the 3 links (the survey is deleted).

Visiting any of those three links would execute the JavaScript in your browser.

Privilege escalation
In the same service, you could delete anyone's Consumer Survey with a single POST request. Keep in mind that this is a paid Google service.

A POST request to this URL with following parameters:

POST parameters:

You could change survey parameter to any valid survey, and it would get deleted. 
When trying to visit a deleted survey, 500 Internal Server Error would pop out, and you wouldn't be able to view it. 

Google has rewarded me with $1337 for each bug (high severity rewards), along with second entry to Hall of Fame. Thank you Google Security team for quick response and fix!