What is Drupal?
Drupal is a free and open-source web content management framework written in PHP. It provides a back-end framework for at least 13% of the top 10,000 websites worldwide – ranging from personal blogs to corporate, political, and government sites according to Wikipedia. For this test we used the latest version of Drupal with the default configuration. Thus, in this internal research pentest project we found a password reset poisoning vulnerability.
We discovered the issue using a black-box approach. However, we also performed a source code review & dynamic analysis in order to determine the root cause of the vulnerability. Moreover, we have reached out to the Drupal team to disclose and allow them time to fix. However, it seems that they know about this issue issue and they created documentation for this, which we will discuss later. The Drupal team was very cooperative and allowed us to publish our findings. This is a host header vulnerability and it allows us to reset an administrator’s password. There is an interesting behavior in the vulnerability which we will discuss in the Source Code analysis section together with the limitations of the current solution.
It is possible to construct the password reset poisoning attack as follows.
On the ‘Reset your password’ page, enter the victim’s email and click submit.
Intercept the request, modify the Host header and send the request:
Right afterwards, the app will be redirect the user to the attacker’s domain as seen below:
In addition, you will receive an email with the poisoned ‘reset password’ link:
If the victim clicks the above link, the ‘attacker.com’ domain will collect the token and thus the attacker can reset victim’s password (which can be an admin).
Source code analysis
Now let’s analyze the source code for this password reset poisoning vulnerability.
The password reset process starts inside the _user_email_notify() function:
The reset token is generated inside the user_mail_tokens():
The url for password reset that gets sent in the notification email is created in the user_pass_reset_url() function, by the Url::fromRoute()->toString() function as can be seen bellow:
The Url->toString() method will call urlGenerator()->generateFromRoute()
And generateFromRoute() does the interesting part where it gets the Host from $this->context->getHost() method.
We tried source code review to find out how they set the Host for the $context(RequestContext class), but we failed. So it’s time to set up a debugger and do some dynamic analysis.
Drupal sets the Host property of the RequestContext class in the RequestContext constructor and also in the “fromRequest()” method of the same class. What is really interesting is that they call the constructor first with a safe value (“localhost” in this case) and then they call fromRequest() which overrides the safe value with the poisoned host header:
After the constructor runs, they override the hostname with a malicious header controlled by the attacker, thus transforming it from a secure link to an insecure one:
Thus, through this host header vulnerability, an attacker can control the host header of the password reset link and capture the token in order to reset an administrator’s password. Once an admin, then the attacker can upload a custom Drupal module which will give him RCE on the server.
We disclosed the host header vulnerability to the Drupal team. However, it seems they know about the issue and they documented it here. What is even more amazing to us is that they know about this issue since 2014 and there’s still no patch.
As demonstrated above this approach is vulnerable and the documented fix has some serious limitations:
- it’s not secure by default
- there were no warnings during the setup process
- it puts the onus on Drupal Admins which might be non technical people. Moreover, they expect you to know how to write secure regexps
- as explained in the Source Code Analysis section the link is is initially secure and then they decide to override this and use the host header
- this can result in RCE on the server, because the Admin can upload custom Drupal modules