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
- Logged in as a low-privileged user.
- Navigated to the password change functionality.
- Entered the valid current password of the logined user along with a new password and confirmation.
- Intercepted the password change workflow using any interception proxy.
- The application first sent a request validating the current password(logined user); this request was forwarded without modification and returned a success response.
- A subsequent request was automatically issued to change the password, containing a
userIdparameter and new password. - Modified the
userIdvalue in this second request from the logined user's ID to another user's ID (e.g., from29to1). - Forwarded the modified request to the server.
- 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
userIdparameter. - The server did not verify whether the
userIdmatched 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:
- Never trust client-supplied identifiers for sensitive operations.
- Derive the user identity from the authenticated session or token.
- Ignore
userIdparameters for account-specific operations. - Enforce server-side authorization checks before performing updates.
Correct implementation should resemble:
authenticatedUserId = session.userId
Update password where userId = authenticatedUserIdAuthorization decisions must always be enforced on the server side.