PascalCTF 2026
JSHit
so we have been given a url. by visiting the url we found a page with
we get a static page with
i goofed around and found nothing but when i looked into the source code i found its obsfucated in jsfuck so the flag is hidden in Likely client-side obfuscation
then by decoding we get the flag
() => {const pageElement = document.getElementById('page'); const flag = document.cookie.split('; ').find(row => row.startsWith('flag=')); const pageContent = <div class="container"><h1 class="mt-5">Welcome to JSHit</h1><p class="lead">${flag && flag.split('=')[1] === '******************************************' ? 'You got the flag gg' : 'You got no flag yet lol'}</p></div>; pageElement.innerHTML = pageContent; console.log("where's the page gone?"); document.getElementById('code').remove();}ZaZastore
by visiting the url we get a login page
before trying anything lets look into the challenge.zip which is provided
Archive: challenge.zip
creating: src/
inflating: src/.gitignore
inflating: src/Dockerfile
creating: src/app/
inflating: src/app/package-lock.json
inflating: src/app/package.json
creating: src/app/public/
creating: src/app/public/images/
extracting: src/app/public/images/CartoonZa.png
inflating: src/app/public/images/ElectricZa.jpeg
inflating: src/app/public/images/FakeZa.jpeg
inflating: src/app/public/images/RealZa.jpeg
inflating: src/app/public/images/ZazaIsBad.png
creating: src/app/public/style/
inflating: src/app/public/style/style.css
inflating: src/app/server.js
creating: src/app/views/
inflating: src/app/views/index.ejs
inflating: src/app/views/inventory.ejs
inflating: src/app/views/login.ejs
inflating: src/app/views/cart.ejs
inflating: src/docker-compose.ymland by looking into the src/app/server.js. i found the following
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (username && password) {
req.session.user = true;
req.session.balance = 100;
req.session.inventory = {};
req.session.cart = {};
return res.json({ success: true });
} else {
res.json({ success: false });
}
});this piece of code reveals that as long as we submit any username or password we will be able to login into the page
so we have a balance of 100$ and we can add items to the card and checkout.
and on server.js
const content = {
"RealZa": process.env.FLAG,
"FakeZa": "pascalCTF{this_is_a_fake_flag_like_the_fake_za}",
...
};- The real flag is stored in
process.env.FLAGand tied to the product"RealZa". - The price of
"RealZa"is set to1000, while your starting balance is only100.
So the challenge is: find a way to get the RealZa into your inventory despite the balance restriction.
by intercepting with burp we can see that it make a post req when we add it to cart
Checkout → normally blocked because balance < 1000.
so i goofed around by changing the quantity values(negative values,decimal,null,0) around but found nothing and hence i gotta find a way to bypass it to get the realza inorder to get the flag
so lets try Try changing the product name slightly so it doesn’t match the prices object in server.js. For example:
{“product”:”RealZa”,”quantity”:1}
to
{“product”:”RealZa “,”quantity”:1}
note the spaces, this work because of
- The server adds whatever you send into your cart.
- At checkout, it calculates
total += prices[product] * cart[product]. - If your product key doesn’t exist in
prices, thenprices[product]isundefined. undefined * quantitybecomesNaN, and the total calculation breaks.- When it checks
if (total > balance),NaN > balanceis false, so checkout succeeds. - The product still ends up in your inventory, and since
inventory.ejsuses thecontentobject,"RealZa"will display the flag.
after that if we look into the inventory we could see 2 realza
after checking out if we check the inventory. we could see the flag
Tea Guardian(AI)
so on here we have given a task to make the ai reveal the flag. so lets try to make the ai reveal the flag.
requesting the flag directly wont work here. so lets ask the TeaGuardian what it can do
and now we can make it reveal the flag
Auratester(gyat)
while connecting via netcat the program asks for your name if you leave it blank and enter it insults you but with any username it welcomes you.
so there are 4 questions.we gain or lose aura by answering the questions
Check current aura.
Take the final AuraTest.
Exit.by downloading the gyat file and looking into it this is what we see
glaze random ahh sonopazzo
glaze os ahh palle
words = ["tungtung","trallalero","filippo boschi","zaza","lakaka","gubbio","cucinato"]
phrase = " ".join(sonopazzo.sample(words,k=sonopazzo.randint(3, 5)))
steps = sonopazzo.randint(2, 5)
flag = palle.getenv("FLAG", "pascalCTF{REDACTED}")
bop encoder(phrase, steps):
encoded_phrase = ""
mewing i in huzz(0,len(phrase)):
chat is this real phrase[i] twin " ":
encoded_phrase rizz= phrase[i]
yo chat i% steps twin 0:
encoded_phrase rizz= str(ord(phrase[i]))
only in ohio:
encoded_phrase rizz= phrase[i]
its giving encoded_phrase
bop questions(name):
gained_aura = 0
questions = [
"Do you believe in the power of aura? (yes/no)",
"Do you a JerkMate account? (yes/no)",
"Are you willing to embrace your inner alpha? (yes/no)",
"Do you really like SHYNE from Travis Scott? (yes/no)",
]
aura_values = [(150,-50), (-1000,50),(450,-80),(-100,50)]
mewing i in huzz(len(questions)):
print(f"{name}, {questions[i]}")
answer = input("> ").strip().lower()
chat is this real answer twin "yes":
gained_aura rizz= aura_values[i][0]
yo chat answer twin "no":
gained_aura rizz= aura_values[i][1]
its giving gained_aura
bop aura_test(name):
yap(f"{name}, you have reached the final AuraTest!")
yap("If you want to win your prize you need to decode this secret phrase:",encoder(phrase, steps))
guess = input("Type the decoded phrase to prove your worth:\n> ")
chat is this real guess twin phrase:
yap(f"Congratulations {name}! You have proven your worth and gained the ultimate aura!\nHere's your price:\n{flag}")
exit()
only in ohio:
yap(f"Dont waste my time {name}, you failed the AuraTest. Try again but this time use all your aura!")
yap("Welcome to the AuraTester2000!\nHere, we will make sure you have enough aura to join our alpha gang.")
let him cook(Aura):
name = input("First of all, we need to know your name.\n> ")
chat is this real(name.strip() twin ""):
yap("You didn't start very well, I asked your named stupid npc.")
only in ohio:
yap(f"Welcome {name} to the AuraTester2000!")
yap("""⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣦⣀⠀⠀⢀⣴⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⣿⡿⠟⠛⠛⠻⢿⣿⡄⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠈⢷⣦⣤⣤⡾⠋⠀⣴⣾⣷⣦⠀⠙⢿⣦⣤⣤⣾⠃⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠘⣿⣿⠟⠀⠀⢸⣿⣿⣿⣿⡇⠀⠀⠹⣿⣿⡏⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣀⣴⣿⡏⠀⠀⠀⠘⢿⣿⣿⡿⠃⠀⠀⠀⠹⣿⣷⣀⠀⠀⠀⠀⠀
⠀⠀⠲⣾⣿⣿⣿⣿⠀⠀⠀⢀⣤⣾⣿⣿⣷⣦⡀⠀⠀⠀⢿⣿⣿⣿⣿⠖⠂⠀
⠀⠀⠀⠈⠙⢿⣿⡇⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⢸⣿⣿⠟⠁⠀⠀⠀
⠀⠀⠀⠀⠀⢨⣿⡇⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠘⣿⣏⠀⠀⠀⠀⠀
⠀⠀⠀⢀⣠⣾⣿⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢰⣿⣿⣦⡀⠀⠀⠀
⠀⠀⠺⠿⢿⣿⣿⣇⠀⠀⠘⠛⣿⣿⣿⣿⣿⣿⠛⠃⠀⠀⢸⣿⣿⡿⠿⠗⠂⠀
⠀⠀⠀⠀⠀⠈⠻⣿⡀⠀⠀⠀⢹⣿⣿⣿⣿⣿⠀⠀⠀⠀⣿⡿⠉⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢠⣿⣧⠀⠀⠀⢸⣿⣿⣿⣿⡏⠀⠀⠀⣼⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣠⣾⣿⣿⣧⡀⠀⢸⣿⣿⣿⣿⡇⠀⠀⣼⣿⣿⣿⣄⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠓⠀⠘⠛⠛⠛⠛⠃⠀⠚⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀""")
just put the fries in the bag bro
aura = 0
let him cook(Aura):
yap("\n\n1. Answer questions to gain or lose aura.\n\n2. Check your current aura.\n\n3. Take the final AuraTest to prove your worth.\n\n4. Exit the AuraTester2000.")
choice = input("What do you want to do little Beta?\n> ")
chat is this real (choice twin "1"):
yap("You choose to answer questions. Let's see how much aura you can gain!")
gained_aura = questions(name)
chat is this real(aura sigma 0):
yap(f"Congratulations {name}! You gained {gained_aura} aura points.")
only in ohio:
yap(f"Sorry {name}, you lost {gained_aura} aura points. Learn how to be a real Sigma!")
aura += gained_aura
yo chat(choice twin "2"):
yap(f"Your current aura is {aura}.")
yo chat(choice twin "3"):
chat is this real(aura beta 500):
yap("You need more aura to even try the final AuraTest.")
only in ohio:
aura_test(name)
yo chat(choice twin "4"):
yap("Exiting the AuraTester2000. Goodbye!")
exit()
only in ohio:
yap("Invalid option. Please try again.")to attempt the final test we need more than 500 aura points.four yes/no questions, each with aura gain/loss values:
Believe in aura? → yes = +150, no = –50
JerkMate account? → yes = –1000, no = +50
Embrace inner alpha? → yes = +450, no = –80
Like SHYNE (Travis Scott)? → yes = –100, no = +50
Your aura total is updated accordingly.Final AuraTest:
It generates a random phrase from the words list (3–5 words).and it encodes the phrase with custom encoder function:
For each character:
If it’s a space → keep space.
If index % steps == 0 → replace with ASCII code (ord()).
Else → keep character.
You must decode this back to the original phrase.
If correct, you get the flag (process.env.FLAG).so this is the word i must decode
103ubbi111 zaz97 tun103tung laka107a cu99inat111decoding the value:
103 → g
111 → o
97 → a
103 → g
107 → k
99 → c
111 → oso the words become
103ubbi111 → gubbi o
zaz97 → zaza
tun103tung → tungtung
laka107a → lakaka
cu99inat111 → cucinatoNotice the first word: 103ubbi111.That decodes to gubbi + o → gubbio (not “gubbi o” with a space).The encoder replaced single characters with ASCII codes, but it didn’t insert spaces — so the decoded phrase is continuous letters
you have reached the final AuraTest!
If you want to win your prize you need to decode this secret phrase: 103ubbi111 zaz97 tun103tung laka107a cu99inat111
Type the decoded phrase to prove your worth:
gubbio zaza tungtung lakaka cucinato
Congratulations admin!
You have proven your worth and gained the ultimate aura!
Here's your price: pascalCTF{***_***_****_****_*****_****}