HackTM challeneges were pretty unique and challenging. I didn’t spend much time on the ctf and only was able to solve the first web challenge which exploited unicode and objects in javascript to get the flag.

Draw with us #

Come draw with us!  
Author: stackola

File: stripped.js

We are given a URL which links to a frontend that allows us to paint a canvas after logging in. We are also provided with a javascript file that we assume is the nodejs code running behind the api.

When looking at stripped.js we see there there is a route /flag with the following code

app.get("/flag", (req, res) => {
	// Get the flag
	// Only for root  
	if (req.user.id == 0) {
		res.send(ok({ flag: flag }));
	} else { res.send(err("Unauthorized")); } });

It is clear that our goal is to become the user with id 0 which is initialized into the users array. In the route /init we see that if we can get the values of p and q then we can get a token which validates us as the admin user.

let target = md5(config.n.toString()); 
let pwHash = md5( bigInt(String(p)).multiply(String(q)).toString());
if (pwHash == target && clearPIN === _clearPIN) {
	// Clear the board
	board = new  Array(config.height).fill(0).map(() =>  new Array(config.width).fill(config.backgroundColor));
	boardString = boardToStrings(); io.emit("board", { board: boardString });

We see that the global config object is defined as the following

const config = { port: process.env.PORT || 8081, width: 120, height: 80, usersOnline: 0, message: "Hello there!", p: p, n: n, adminUsername: "hacktm", whitelist: ["/", "/login", "/init"], backgroundColor: 0x888888, version: Number.MIN_VALUE };

It is clear our goal is to get the values p and n from the config object, then solve for q using the equation q = n / p. How can we obtain these values? There is a route called /serverInfo which lets us view items in the config object. All we have to do is add the items p and n to our users rights property. There is also conveniently a route named /updateUser which lets us change our user’s rights and the color we paint.

app.post("/updateUser", (req, res) => { 
	// Update user color and rights
	// Only for admin
	// POST
	// {  // color: 0xDEDBEE,
	// rights: ["height", "width", "usersOnline"]
	// }
	let uid = req.user.id;
	let user = users[uid];
	if (!user || !isAdmin(user)) { res.json(err("You're not an admin!")); return; }
	let color = parseInt(req.body.color); users[uid].color = (color || 0x0) & 0xffffff;
	let rights = req.body.rights || [];
	if (rights.length > 0 && checkRights(rights)) {
		users[uid].rights = user.rights.concat(rights).filter(onlyUnique);
	res.json(ok({ user: users[uid] }));

This function poses two challenges. The first is the function isAdmin and the second is the function checkRights

function isAdmin(u) {
	return u.username.toLowerCase() == config.adminUsername.toLowerCase();
function checkRights(arr) {
	let blacklist = ["p", "n", "port"];
	for (let i = 0; i < arr.length; i++) {
		const element = arr[i];
		if (blacklist.includes(element)) { return  false; }
	return  true;

In order to bypass isAdmin, it seems like we need to login as hacktm however it uses an uppercase check in order to make sure we are not admin which is done with the function isValidUser.

function isValidUser(u) {
	return ( u.username.length >= 3 && u.username.toUpperCase() !== config.adminUsername.toUpperCase() );

My first observation is that one is a toUpperCase check and one is a toLowerCase check. This leads me to think that by using unicode characters I can bypass the login check. Sure enough, there was a unicode character that when .toLowerCase() is used on it, returns k. Thus, if I signed in with the user hacKtm, I would have an account which was admin. The next step was to add p and n to my account’s rights. However, this was disallowed by the checkRights function I showed above. After testing, I found that if I send an objeect instead of a string. It would bypass the check. Thus, if I send {rights: [['p']]} and then {rights: [['n']]}, I will be able to add these to my own rights.

Now, all that was left was the call /serverInfo with my token and it would give me the p and n stored in the config object. After finding q using the formula listed above, all I had to do was send this to the route /init, granting me the token of the user with id 0. The final step was to call /flag with this new token and we successfully get the flag.