← Back to practice

YesWeHack Dojo #54 · Report #2123-5752 · JSON injection / query bypass · Resolved · Medium (5.3)

Highscore: character counting to query bypass

The filter counted JavaScript string characters but inspected a UTF-8 byte buffer. I used that disagreement to hide JSON syntax from the filter and replace the object passed into the database query.

View report #2123-5752 on YesWeHack

The mismatch

The parser created a UTF-8 buffer, then used the original string length as its loop limit:

const bytes = Buffer.from(session, "utf8");

for (let i = 0; i < session.length; i++) {
  // validate bytes[i]
}

For ASCII, one character is one byte. A character such as Ç occupies one JavaScript string position but two UTF-8 bytes. Enough multi-byte characters make the loop end before it reaches later ASCII bytes in the buffer.

Why the skipped bytes mattered

The application built JSON by placing the session value directly inside a template string:

JSON.parse(
  `{"id":${id}, "session":{"session":"${session}"}}`
)

Once quotation marks and braces reached this expression, they could end the original string value and add another session key. JavaScript’s JSON parser keeps the last value when an object contains duplicate keys.

The query bypass

The parsed value was then used directly as a Sequelize filter:

Users.findOne({ where: cookie["session"] })

I padded the session with multi-byte characters and followed it with JSON that replaced the expected session lookup with an ID lookup. The injected bytes sat beyond the part of the UTF-8 buffer examined by the filter, but they were still present when the JSON was parsed.

1:[multi-byte padding]"}, "session": {"id": "1

The resulting query returned the privileged challenge record and revealed the flag.

Result

Highscore challenge screen showing the filter bypassed and the flag captured
The completed Highscore challenge.
YesWeHack confirmation for completing Dojo 54 Highscore
YesWeHack’s completion confirmation.

What should change?

  • When validating bytes, iterate over bytes.length.
  • Do not construct JSON by concatenating untrusted strings.
  • Build the object directly and pass an explicit allowlist of fields to the ORM.