In this article, I will explain a logic flaw discovered during an authorized security assessment, where a password change workflow led to full account takeover due to improper authorization enforcement.

Intended Password Change Flow

Typically, a secure password change process follows this sequence:

1. User logs into their account.

2. User enters: Current password New password Confirm new password

3. Application validates the current password.

4. If valid, the password is updated for that user.

At a high level, this flow appears secure. However, implementation details matter.

Observed Application Workflow

During testing, the password change functionality was intercepted using a Burp Suite. The workflow consisted of two separate HTTP requests.

Step 1 β€” Current Password Validation

The first request verifies whether the entered current password is correct for the user whose password you want to change(logined user). The backend is validating here using only the userId.

{
  "userId": 29,
  "currentPassword": "OldPass@123"
}

This request was forwarded without modification and returned a success response. (If I try to change the Id here, it returns false because the current password does not match the other user.)

At this stage, authentication was verified.

Step 2 β€” Password Change Request

After successful validation, a second request was automatically triggered by the application.

Example structure:

{
  "userId": 29,
  "newPassword": "NewPass@123"
}

The userId parameter was supplied by the client in both requests.

Exploitation Steps

  1. Logged in as a low-privileged user.
  2. Navigated to the password change functionality.
  3. Entered the valid current password of the logined user along with a new password and confirmation.
  4. Intercepted the password change workflow using any interception proxy.
  5. The application first sent a request validating the current password(logined user); this request was forwarded without modification and returned a success response.
  6. A subsequent request was automatically issued to change the password, containing a userId parameter and new password.
  7. Modified the userId value in this second request from the logined user's ID to another user's ID (e.g., from 29 to 1).
  8. Forwarded the modified request to the server.
  9. The server responded with success and updated the password of the targeted account.

Root Cause Analysis

The vulnerability existed because the application separated validation from authorization.

  • The first request validated the current password.
  • The second request performed the password change.
  • However, the current and change password requests relied on a client-controlled userId parameter.
  • The server did not verify whether the userId matched the authenticated session.

Although the first request validated the current password for the logged-in user, the application failed to enforce that the subsequent password change request must apply only to that same user. The lack of binding between validation and change operations allowed manipulation of the target account Id.

Secure Design Recommendation

To prevent such vulnerabilities, the application must:

  1. Never trust client-supplied identifiers for sensitive operations.
  2. Derive the user identity from the authenticated session or token.
  3. Ignore userId parameters for account-specific operations.
  4. Enforce server-side authorization checks before performing updates.

Correct implementation should resemble:

authenticatedUserId = session.userId
Update password where userId = authenticatedUserId

Authorization decisions must always be enforced on the server side.