Drupal Exploit

Posted Sun May 19 @ 07:46:17 PM PDT 2013

In Django 1.5, a new ALLOWED_HOSTS setting was introduced. The documentation for it reads:

This is a security measure to prevent an attacker from poisoning caches and password reset emails with links to malicious hosts by submitting requests with a fake HTTP Host header, which is possible even under many seemingly-safe web server configurations.

It wasn't at all obvious to me how the attacks worked, so I did some searching and found a fantastic article explaining it: Practical HTTP Host header attacks. It was a real eye opener for me, especially the part about RFC-2616.


As an academic exercise, I wanted to try this attack in the wild. Drupal, the CMS I love to hate, was an easy target.

Drupal, by default, naively uses the $_SERVER['HTTP_HOST'] variable to construct absolute URLs. Since this value is supplied by the client in an HTTP request, it cannot be trusted.

You can easily tell if a Drupal site is vulnerable by sending it a carefully constructed HTTP request with nc:

echo -ne "GET http://demo.opensourcecms.com/drupal/ HTTP/1.1 Host: foobar.example.com\n\n" | nc -q 1 demo.opensourcecms.com 80

The HTTP request is sent to the server with an absolute RequestURI (which is unusual, but part of RFC-2616). The second line of the request contains a spoofed HTTP host header. If the HTTP response contains "foobar.example.com", the site can be attacked.

Intercepting the one-time login link email

When a user requests to reset their password, Drupal sends them an email containing a link back to the Drupal site, with a special token. The token allows the user to login once without a password. If an attacker can get the token, the attacker can login as the user. Drupal uses the $_SERVER['HTTP_HOST'] variable to construct the one-time login link. So it will happily construct, and send an email with a one-time reset link that looks like: evil.example.com/?q=user/reset/1/some-secure-token-abc123. If the unsuspecting user clicks the link, her account can be compromised immediately.

This attack requires the attacker to know a Drupal user's username or email address. Not hard stuff to get or guess.

The one-time login request form exists at /?q=user/password on every Drupal site I've seen (e.g. http://demo.opensourcecms.com/drupal/?q=user/password). All the attacker needs to do is send a valid HTTP POST request to the page, with a forged host header and voila, the user will be emailed a bad reset link.

To satisfy Drupal, the HTTP POST request needs a few special values, which you can get simply by viewing the source code on the reset form: form_id, form_build_id, and op. Here is example request:

POST http://demo.opensourcecms.com/drupal?q=user/password HTTP/1.1 Host: hacked.example.com Content-Length: 114 Content-Type:application/x-www-form-urlencoded name=admin&form_build_id=form-kgeKZxP9RjzU3YC_ZyCsKHlDYBF6zkXL6CVnz9eof6o&form_id=user_pass&op=E-mail+new+password

If you send that request over port 80 to the server, it sends the admin user a one-time login link, that looks something like: http://hacked.example.com/drupal/?q=user/reset/1/1369016774/LOVL9KIJ4WSmRfHrmTWCkgT96qIJ0tKfIZjn1HGle_Y. If the user doesn't notice the incorrect URL, and clicks the link, the one-time login token will be intercepted at the attacker's website. The attacker can now login as the user.

Drupal's Response

Once I successfully used this attack on the latest version of Drupal, I emailed security@drupal.org. They responded (six days later) by creating this page: http://drupal.org/node/1992030. Since Drupal doesn't think it's a big enough problem to warrant a change to their default configuration, most Drupal sites will remain vulnerable.

<< Home