So Bellingcat held a contest as part of their new website launch where you had to find a hidden symbol on the site in order to find the new website, and the first 25 people to do so could claim a prize (a limited edition Bellingcat hoodie).
I was curious to participate, and lucky/quick enough to be one of the 25 (after a false start I got there in a bit under 40 minutes), however I wasn't having a bar of doing it properly so I used ahem alternative methods. The rationale is that I wanted to see if I could attack it in a methodical way, reduce reliance on chance and my dumb brain, while still being competitve in terms of speed.
Upon seeing the tweet announcing the contest I decided to do a bit of pre-game recon:
-
I used OWASP Amass to look for subdomains in case they fucked up and I could find something early. This didn't really turn up anything up, but was low effort and worth a shot.
$ amass enum -d bellingcat.com Querying Pastebin for bellingcat.com subdomains Querying AlienVault for bellingcat.com subdomains Querying Bing for bellingcat.com subdomains Querying BufferOver for bellingcat.com subdomains Querying Entrust for bellingcat.com subdomains Querying CertSpotter for bellingcat.com subdomains Querying HackerOne for bellingcat.com subdomains Querying Ask for bellingcat.com subdomains Querying HackerTarget for bellingcat.com subdomains Querying Dogpile for bellingcat.com subdomains Querying Netcraft for bellingcat.com subdomains Querying CommonCrawl for bellingcat.com subdomains Querying Censys for bellingcat.com subdomains Querying Exalead for bellingcat.com subdomains Querying GoogleCT for bellingcat.com subdomains Querying Baidu for bellingcat.com subdomains Querying IPv4Info for bellingcat.com subdomains Querying Google for bellingcat.com subdomains Querying DNSTable for bellingcat.com subdomains Querying Mnemonic for bellingcat.com subdomains Querying DNSDumpster for bellingcat.com subdomains Querying Crtsh for bellingcat.com subdomains Querying Sublist3rAPI for bellingcat.com subdomains Querying Yahoo for bellingcat.com subdomains Querying Spyse for bellingcat.com subdomains Querying ViewDNS for bellingcat.com subdomains Querying URLScan for bellingcat.com subdomains Querying ThreatCrowd for bellingcat.com subdomains Querying VirusTotal for bellingcat.com subdomains Querying SiteDossier for bellingcat.com subdomains Querying Riddler for bellingcat.com subdomains Querying Robtex for bellingcat.com subdomains bellingcat.com fr.bellingcat.com es.bellingcat.com www.bellingcat.com ru.bellingcat.com ftp.bellingcat.com cn.bellingcat.com ar.bellingcat.com mobilemail.bellingcat.com calendar.bellingcat.com smtp.bellingcat.com imap.bellingcat.com fax.bellingcat.com email.bellingcat.com files.bellingcat.com mail.bellingcat.com yemen.bellingcat.com Average DNS queries performed: 198/sec, Average retries required: 6.06% OWASP Amass v3.6.0 https://github.com/OWASP/Amass -------------------------------------------------------------------------------- 17 names discovered - cert: 8, scrape: 8, api: 1 -------------------------------------------------------------------------------- ASN: 14618 - AMAZON-AES 50.17.0.0/16 1 Subdomain Name(s) 54.156.0.0/14 1 Subdomain Name(s) 35.153.0.0/16 1 Subdomain Name(s) ASN: 20857 - TRANSIP-AS Amsterdam 136.144.128.0/17 6 Subdomain Name(s) ASN: 21155 - ASN-PROSERVE Amsterdam 83.96.128.0/18 2 Subdomain Name(s) ASN: 26496 - AS-26496-GO-DADDY-COM-LLC 72.167.236.0/22 2 Subdomain Name(s) 68.178.212.0/22 2 Subdomain Name(s) 68.178.252.0/22 6 Subdomain Name(s) 97.74.132.0/22 8 Subdomain Name(s) 72.167.216.0/22 6 Subdomain Name(s) 45.40.140.0/22 1 Subdomain Name(s) 173.201.192.0/22 17 Subdomain Name(s) 45.40.128.0/18 2 Subdomain Name(s) 184.168.128.0/22 2 Subdomain Name(s) The enumeration has finished Discoveries are being migrated into the Cayley Graph database
-
I used SiteDiff to crawl the current Bellingcat website in case they hadn't hidden the symbols yet. If I'd read the manual properly I would have used the config options to make more concurrent requests and speed this up.
$sitediff init https://bellingcat.com/
-
I refreshed twitter a lot and waited.
When the contest kicked off I did another Amass run in the background just-in-case, and ran the diff check in SiteDiff. I actually had everything I needed almost immediately, but I decided what was actually an important detail was a false positive and wasted a bunch of time (approx 20 minutes) trying to filter results to eliminate it.
Here's roughly what the SiteDiff command looked like (yes it's a Kali VM, I use this one for doing HTB):
$sitediff diff
Reading config file: /home/kali/sitediff/sitediff/sitediff.yaml
Read 44 paths from: /home/kali/sitediff/sitediff/paths.txt
Using sites from cache: before
[CHANGED] /
[CHANGED] /about
[CHANGED] /author/aaronstein
[CHANGED] /author/adamrawnsley
The thing that I'd spotted looking in the diff results was that there was a <script> tag added to import
https://www.bellingcat.com/gtapi/js/gtapi.js
. The file's gone now, but thanks to the magic of screenshots and OCR I still
have a copy.
Here's the script (apologies for typos, it's OCR that I've tried to clean up quickly). Comments are added by me, explaining what I think is going on here.
// This function is immediately interesting. It makes a POST request with no
// parameters to https://www.bellingcat.com/gtapi/ticket-request which returns
// a token.
function getTicketRequestToken(callback) {
var xhr = new XMLHttpRequest();
xhr.open( 'POST', '/gtapi/ticket-request');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var jsonResponse = JSON.parse(xhr.responseText);
callback(jsonResponse.token);
}
}
};
xhr.send(null);
}
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
// cool so this is setting up an event listener for when the DOM is loaded. This
// finds a single random question mark on the page and turns it into the magic symbol.
document.addEventListener && document.addEventListener('DOMContentLoaded', function() {
// The page has to have an element with a matching ID in order for any of this to trigger
if (null !== document.getElementById('jetpack_css-css')) {
// Makes the call to get the token from that funciton above, and the rest of the logic happens in
// this callback funciton
getTicketRequestToken(function (requestToken) {
// Bellingcat trying to get me to care about XPath WELL NOT TODAY SATAN
// This appears to be searching the document for any question marks, returning
// an array of elements to choose from
var nodes = [];
var xpathResults = document.evaluate("/html/body//*[contains(text(), '?')]", document, null, XPathResult.ANY_TYPE, null);
var result = xpathResults.iteratellext();
while (result) {
nodes.push(result);
result = xpathResults.iterateNext();
}
// Okay so if we found any elements with question marks then we go on
if (0 < nodes.length) {
// Pick a random element with a question mark
var selected = getRandomInt(nodes.length);
// Generate a random CSS class name
var className = Math.random().toString(36).substring(7);
// Find all question marks within the randomly selected element
var occurences = nodes[selected].innerHTHL.match(/\?/g).length;
// Pick one of the question marks at random
var occurence = getRandomInt(occurences) + 1;
// set up the magic symbol, create the link
nodes[selected].innerHTML = nodes[selected].innerHTML.replace(new Regexp('\\?{' + occurence + '}'), '<span class="' + className + '">¿</span>');
var linkNodes = document.getElementsByClassName(className) ;
if (0 < linkNodes.length) {
linkNodes[0].addeventListener('click', function (event) {
event.preventDefault();
window.location.href = '/gtportal/' + requestToken; // THIS IS THE GOOD BIT, THIS IS WHAT I WANTED
})
}
}
});
}
});
So looking at that code I can safely ignore all that XPath shit and concentrate on the 2 good bits:
- Make a POST request with no parameters to https://www.bellingcat.com/gtapi/ticket-request and save the token
$ curl -X POST https://www.bellingcat.com/gtapi/ticket-request
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpcCI6IjEyMy4yMDguMTg5LjQ3IiwiaWF0IjoxNTk0NzM3NzQ2LCJleHAiOjE1OTQ3Mzg2NDYsImF1ZCI6Imh0dHBzOi8vd3d3LmJlbGxpbmdjYXQuY29tIiwiaXNzIjoiaHR0cHM6Ly93d3cuYmVsbGluZ2NhdC5jb20ifQ.gHzABCoczuy0mIXg7L2h2YzTS-c-nJoVv_r9Vr2LLNg"}
- Go to the URL https://www.bellingcat.com/gtportal/ and receive cake / glory / fortune etc.
https://www.bellingcat.com/gtportal/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpcCI6IjEyMy4yMDguMTg5LjQ3IiwiaWF0IjoxNTk0NzM3NzQ2LCJleHAiOjE1OTQ3Mzg2NDYsImF1ZCI6Imh0dHBzOi8vd3d3LmJlbGxpbmdjYXQuY29tIiwiaXNzIjoiaHR0cHM6Ly93d3cuYmVsbGluZ2NhdC5jb20ifQ.gHzABCoczuy0mIXg7L2h2YzTS-c-nJoVv_r9Vr2LLNg
Big thanks to Bellingcat, It was a simple contest but I enjoyed overengineering my response and racing against your readers, many of whom are probably OSINT tragics like myself.
HACKING MACHINE