Skip to main content

Password reset poisoning

Need

Secure password reset functionality

Context

  • Usage of TypeScript for statically typed JavaScript development
  • Usage of Express for building web applications and APIs

Description

Non compliant code

import express from 'express';

const app = express();

app.get('/reset-password', (req, res) => {
const token = req.query.token;
const email = req.query.email;

const resetUrl = `https://example.com/reset-password?token=${token}&email=${email}`;

res.send(`
<h1>Reset Password</h1>
<form action="${resetUrl}" method="POST">
<input type="password" name="newPassword" placeholder="New Password" required>
<input type="password" name="confirmPassword" placeholder="Confirm Password" required>
<button type="submit">Reset</button>
</form>
`);
});

app.post('/reset-password', (req, res) => {
const newPassword = req.body.newPassword;
const confirmPassword = req.body.confirmPassword;

// Reset password logic
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

The vulnerability in this code is known as "Password reset poisoning." It occurs because the application generates password reset links improperly, allowing an attacker to manipulate the request by changing the domain to one under their control.

In the vulnerable code, when a user requests a password reset by accessing the /reset-password endpoint, the server generates a password reset URL using the token and email query parameters provided in the request. The generated URL is then embedded in an HTML form as the action attribute.

However, the issue lies in the fact that the server blindly uses the resetUrl variable, which includes the token and email parameters provided by the user, without validating or sanitizing them. This allows an attacker to manipulate the resetUrl and change the domain to their own controlled domain.

For example, an attacker could craft a malicious URL like https://attacker.com/reset-password?token=abc123&[email protected]. When a user clicks on this link, they will be directed to the attacker's domain instead of the legitimate domain.

This vulnerability can lead to the compromise of user accounts because the attacker can intercept the password reset requests and gain unauthorized access to the user's account.

To mitigate this vulnerability, it is recommended to ensure that password change URLs are not disposed in client-side requests. This can be achieved by generating the password reset URL on the server-side and sending it directly to the user via a secure channel such as email. Additionally, the server should validate and sanitize the input parameters to prevent any malicious manipulation.

Steps

  • Remove the password reset URL from the client-side code.
  • Instead, generate a unique token for each password reset request on the server-side.
  • Store the token in a secure database along with the associated user's email.
  • When generating the password reset URL, use a server-side route that includes the token as a parameter.
  • Update the client-side code to send a POST request to the server-side route for password reset.
  • On the server-side route, verify the token and email combination before allowing the password reset.
  • Implement the password reset logic securely, ensuring proper validation and encryption of the new password.
  • Consider implementing additional security measures such as rate limiting or CAPTCHA to prevent abuse of the password reset functionality.

Compliant code

import express from 'express';
import crypto from 'crypto';

const app = express();

// Step 1: Remove the password reset URL from the client-side code

app.get('/reset-password/:token', (req, res) => {
const token = req.params.token;

// Step 6: Verify the token before allowing the password reset

// Step 7: Implement the password reset logic securely

res.send(`
<h1>Reset Password</h1>
<form action="/reset-password/${token}" method="POST">
<input type="password" name="newPassword" placeholder="New Password" required>
<input type="password" name="confirmPassword" placeholder="Confirm Password" required>
<button type="submit">Reset</button>
</form>
`);
});

app.post('/reset-password/:token', (req, res) => {
const token = req.params.token;
const newPassword = req.body.newPassword;
const confirmPassword = req.body.confirmPassword;

// Step 6: Verify the token before allowing the password reset

// Step 7: Implement the password reset logic securely

res.send('Password reset successful');
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

The fixed code addresses the vulnerability by ensuring that the password reset URL is not exposed in the client-side code. Here is an explanation of the changes made:

  1. The client-side code no longer includes the password reset URL. This prevents an attacker from manipulating the request by changing the domain to one under their control.

  2. The server-side code includes two routes: one for rendering the password reset form and another for handling the password reset submission.

  3. In the GET route for '/reset-password/:token', the token parameter is extracted from the request parameters.

  4. The server-side code should include additional steps to verify the token before allowing the password reset. This step is marked as "Step 6" in the code comments. Implementing token verification is crucial to ensure that only valid password reset requests are processed.

  5. The server-side code renders a password reset form using a template string. The form includes two password input fields for the new password and confirm password.

  6. In the POST route for '/reset-password/:token', the token parameter is extracted from the request parameters. The new password and confirm password are extracted from the request body.

  7. Similar to the GET route, the server-side code should include additional steps to verify the token before allowing the password reset. This step is marked as "Step 6" in the code comments. Implementing token verification is crucial to ensure that only valid password reset requests are processed.

  8. The server-side code should implement the password reset logic securely. This step is marked as "Step 7" in the code comments. The actual implementation of the password reset logic is not shown in the code snippet, but it should include proper validation, encryption, and storage of the new password.

  9. Finally, the server sends a response indicating that the password reset was successful.

By removing the password reset URL from the client-side code and implementing proper token verification and password reset logic on the server-side, the fixed code mitigates the vulnerability and ensures the security of the password reset functionality.

References