In Depth: Stealing Password Tokens with Forwarded Host Poisoning
[InDepth#13] The story of why a bugfix I was so confident in was doomed to fail...
Greetings my friends! I hope last week’s Encoding/Serialising Data security tip was a helpful reminder. This week I’m going to tell you the story about an interesting misconfiguration vulnerability in core Laravel framework that I was alerted to. The vuln allows an attacker to steal password reset tokens by spoofing forwarded request headers. After digging into it for a while, I was confident I’d found a universal fix for the vuln, so I coded up a fix on the plane home from Laracon EU… but that fix was always doomed to fail… I’ll explain the vuln in full, and then walk you through my attempt at fixing it and what I overlooked!
This week I’m also excited to announce Early-Access is opening for my Practical Laravel Security course! Early-Access comes with full access to the Cross-Site Scripting (XSS), Escaping Output, and HTML & Markdown modules. The XSS module includes 6 interactive challenges to teach you how XSS works - the last of which is particularly evil. 😈 The presale price is ending on Monday, so now is the time to sign up and grab that discount!
Laravel Security In Depth is a bestselling reader-supported publication. Join over 1,800+ Laravel developers, learning about keeping their apps secure each week.
💡 Worried about the security of your Laravel apps? Book in a Security Audit and I’ll help you secure it! 🕵️
Looking to learn more?
⏩ Security Tip #20: Be Careful of Auth Helpers
▶️ In Depth #8: Policy Objects
Stealing Password Tokens with Forwarded Host Poisoning
Our story starts on the 1st January 2023, when I was pinged on Twitter:
Ankur Kumar linked me to a video by Daniel Coulbourne, covering a vulnerability in the core Laravel framework that was reported to them through their Bug Bounty program.
To save you clicking through, here is the video:
This vulnerability is a Host Header attack technique known as Password Reset Poisoning. The idea is to poison or spoof the values in specific request headers, to trick the server into believing the application is running as a specific host, causing it to generate links for the poisoned hostname rather than the real hostname.
In most cases, overriding the hostnames used for generated URLs won’t have much effect - the links are returned to the browser, where they can be modified anyway. The vulnerability occurs when links are generated and sent to someone else (i.e. the target), such as in an email. This allows the attacker to send a link to a malicious app, rather than the actual app - allowing them to steal any tokens or parameters from the URL. In this case of a password reset link, this token gives the attacker full access to hijack the targeted user’s account!
Before we go on, I want to make it clear that Laravel apps are not vulnerable by default.
However, it is common to run Laravel apps behind a reverse proxy1, and if you fail to properly configure both the
`TrustHosts` middleware, then your app may be vulnerable.
How It Works
Let’s break the attack down to understand how it works…
There are three important factors that make an app vulnerable:
By default Laravel is configured to load the
`TrustProxies`middleware but not the
Reverse proxy IPs need to be configured in
`TrustHosts`middleware is commented out by default, it is either ignored, or worse, configured but left disabled.
The attack works like this:
The attacker loads the Forgotten Password form, as per normal.
The server generates the form and sends it back in the response, as per normal.
(This is needed to obtain the CSRF token.)
The attacker submits the form, and in the process intercepts the request and injects a new header into the request:
POST https://example.com/forgot-password X-Forwarded-Host: evilhacker.dev _token=<csrftoken>&email@example.com
Within the single request, the server:
`X-Forwarded-Host: evilhacker.dev`header and tells itself to generate all links with that hostname.
Generates the reset token, builds the link, and sends it to the victim.
The victim receives the “Password Reset” email, recognises the app it was sent from and clicks the link.
The victim visits the
`evilhacker.dev`app, which records the reset token and immediately:
Visits the real Reset Password form at
`example.com`and submits the form to reset the user’s password.
(This is trivial to fully automate.)
At this point the user has lost complete control over their account.
Why It’s So Serious
It’s easy to underestimate the effectiveness of this attack. It’s sending an email to complete a user-initiated action, so why would the victim click the link?
Keep reading with a 7-day free trial
Subscribe to Laravel Security In Depth to keep reading this post and get 7 days of free access to the full post archives.