Skip to content

Instantly share code, notes, and snippets.

@smidgedy
Last active November 10, 2023 13:29
Show Gist options
  • Save smidgedy/9b01f0ffdd9b2b94e619be55441c03c6 to your computer and use it in GitHub Desktop.
Save smidgedy/9b01f0ffdd9b2b94e619be55441c03c6 to your computer and use it in GitHub Desktop.
Bellingcat website launch contest notes

Bellingcat website launch contest notes

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.

Pre-contest setup

Upon seeing the tweet announcing the contest I decided to do a bit of pre-game recon:

  1. 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
  2. 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/
  3. I refreshed twitter a lot and waited.

Contest Start

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.

Working it out

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:

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

@smidgedy
Copy link
Author

Lol thanks @jmoz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment