Framework: Security issue in form validation (redirecting to referrer)

Created on 5 Aug 2016  Â·  33Comments  Â·  Source: laravel/framework

Hello,

today I tried to fix a problem found by our security department.

Abstract

When validating a form based request, laravel will redirect to the form, if the validation failed. To redirect it uses something like redirect::back() - that function will prefer the referrer header if present. By spoofing the referrer you can redirect the user to any domain/URL of the world.

Details

The responsible code is in laravel/framework/src/Illuminate/Routing/UrlGenerator.php - function previous():

// laravel/framework/src/Illuminate/Routing/UrlGenerator.php (line 135)
public function previous()
{
    $referrer = $this->request->headers->get('referer');
    $url = $referrer ? $this->to($referrer) : $this->getPreviousUrlFromSession();
    return $url ?: $this->to('/');
}

Suggestion

There is no validation if the referrer is refering to the same domain. In my case I just extended the UrlGenerator, added a binding in a ServiceProvider and overwrote the function by something like this:

public function previous()
{
    $referrer = $this->validateDomain( $this->request->headers->get('referer') );
    //...
}

protected function validateDomain( $referrer )
{
   $referrerDomain = parse_url( $referrer, PHP_URL_HOST );
   $envDomain      = parse_url( env('APP_URL', 'https://localhost'), PHP_URL_HOST);

   return ( $referrerDomain === $envDomain )? $referrer : null;
}

Epilogue

Please add something like this in the core. I am not sure about a cool laravel-like way to do this, so I just propose my current code as a possibility.

As a side note: I think the use of parse_url() is not secure enough here (due to the strange behaviour if some URL parts are missing).

Tell me, what you think.

Most helpful comment

What is the actual security issue? What can Mr Evil do with this knowledge? Do you have any proof of concept?

Issue comments:

  1. Referrer headers cannot be changed/specified by normal requests/javascript.
  2. The default CSRF setup will ensure that no remote forms are allowed to post data to your controller.

To exploit this the user needs to have a valid CSRF token and the ability to change referrer headers. I see no way this can be done on a normal setup; if possible would break the whole idea about CSRF. The user would need to install some browser addon that could request a CSRF token from your app and send it to the evil form.

Code comments:

  1. How would your code changes work with Laravel applications running on multiple domains?
  2. Only configuration files are allowed to call env(), all other code should should read from the config. (Cached configuration files means that the .env file isn't loaded in 5.2)

All 33 comments

What is the actual security issue? What can Mr Evil do with this knowledge? Do you have any proof of concept?

Issue comments:

  1. Referrer headers cannot be changed/specified by normal requests/javascript.
  2. The default CSRF setup will ensure that no remote forms are allowed to post data to your controller.

To exploit this the user needs to have a valid CSRF token and the ability to change referrer headers. I see no way this can be done on a normal setup; if possible would break the whole idea about CSRF. The user would need to install some browser addon that could request a CSRF token from your app and send it to the evil form.

Code comments:

  1. How would your code changes work with Laravel applications running on multiple domains?
  2. Only configuration files are allowed to call env(), all other code should should read from the config. (Cached configuration files means that the .env file isn't loaded in 5.2)

Even if it's only a security concern in very particular circumstances, it still might be worth doing. What's the use case for redirecting back to a different domain?

Hello sisve, hello GrahamCampbell,

thanks for you responses. The issue is not a big one, nevertheless our security department has noted it.
The referrer manipulation is part of a CSRF attack. See here: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)

But I must confess, that I can not give you a working proof of concept due to my not hackerish nature :-)

I think, that the fix is so easy, that it's worth the effort - more than the risk.

@GrahamCampbell
The security department did a wrong form validation (i.e. a wrong token) and injected a different referrer (to an external domain). Due to the back-redirect of laravel's core the external domain got visited.

I can only think of some simple scenarios:

  • I use this issue to redirect myself/customers/users to an external domain, that is my main object of interest.
  • I use the issue to fake redirection to get different traffic to the destination and/or fake google analytics data. (But I think I don't need the laravel page therefore)

If you don't think, it's worth doing so, I will accept this. I just wanted to pass the security information to this amazing framework.

@thewulf00 Can you or your security guys reproduce this in a browser? Or do you need to manually craft http requests using a separate program/code/curl?

@sisve Indeed you can.
Simply prepare a form with fake data (i.e. a csrf token, that is not correct) and the form destination is my site. The referer will automatically be your URL and the destination will by my URL.

Referer: bad-site.com/hackThewulf00
Form-action: thewulf00.example.com/form
hidden input: _token="1234567890"

The Laravel validation will check the token and redirect you back to bad-site.com.

The Laravel validation will check the token and redirect you back to bad-site.com.

When token validation fails, the default is NOT to redirect. It will give an error 500.

The bug is in your code if you redirect users back for a validation token error.

@thewulf00 As @GrahamCampbell noted; verify that your VerifyCsrfToken isn't doing anything odd. The default would throw a TokenMismatchException when csrf validation fails; and the default exception handler would only redirect users back on _validation_ failures, not csrf failures.

I am sorry if I did not express the issue the right way, so you can understand.
The part of code that I quoted in the first post proofs, that there is an issue. If it is not triggered by a wrong token, my fault. But it is triggered by an invalid form.

If you want to ignore the fact that laravel is vulnerable to a referer manipulation, whatever its severity is, its your choice.
Our security department found it, I fixed it by overriding the class, and I chose to tell you about it.

I did not intend such a discussion. Use the information to fix the issue or don't bother - your choice.

By the way, I never had overridden any VerifyToken codes - I always use the original one.

I'm closing this because your security team have found an issue in your app, not in the framework.

The issue is in the file laravel/framework/src/Illuminate/Routing/UrlGenerator.php - that is not part of my app.

The issue is you calling previous incorrectly.

I don't understand, what you want to say.

All information is up there in the first post. I am just quoting since then. (and answering the questions)

I did try to tell you what the problem is - without any success. Just forget the attempt - I am sorry that I bothered you.

I'm not usually saying this, but I'm with @GrahamCampbell on this one. ;) What you've described _cannot_ be reproduced in a default installation of Laravel with the default implementation of CSRF token validation.

If I've understood this correctly; this can only be reproduced if you disable the CSRF validation functionality. Web browsers does not support changing the referer headers, so those cannot be manipulated by a third party.

Please tell us how we can reproduce this with the default installation of Laravel and the default implementation of CSRF; and we will immediately fix this and issue fixes as fast as we can. But we need to be able to reproduce this to be able to verify that our attempted fix actually worked, and to write working unit/regression tests to ensure that the issue does not pop up again in the future.

The exact same 'vulnerability' popped up in my OWASP ZAP scan and the reason was also a call to Request::back() within the VerifyCsrfToken middleware where some custom logic was added. After reverting everything back to the Laravel 5.5 standard it was all ok.

But you can still reproduce the same behavior by doing a login attempt (with valid csrf token), invalid credentials and an extra header Referer: https://google.com to the request (with a browser plugin for example). You will end up at google.com website because an exception is thrown, that exception is going through the Handler::invalid() method and that method does a call to url()->previous() which has a preference for the 'referer' header.

But as mentioned above, I have no clue if this would even be a vulnerability. But I also agree to the statement of @GrahamCampbell in the beginning of this ticket:

Even if it's only a security concern in very particular circumstances, it still might be worth doing. What's the use case for redirecting back to a different domain?

My websites PCIDSS audit (PT) is pointing this as a security concern where ever there is 302 Redirect.

They are reproducing this as following:

I have a Form to add Product to DB (name, description), name field is required.

I added ProductFormRequest class to add validation rules (name => "required")

On Form submit, with a product name system goes to store and adds the Product to the DB and redirects the page back to Add Product view.

If the Form is submitted with empty name validation error is returned using a 302 redirect.
Before submitting the Form if we update the referrer to a malicious website and submit the form, after the validation error page gets redirected to malicious website.

Now I am not sure if Any Program installed on Computer or Browser or App Scripts can update the referrer url. for a request.

A modern browser does not allow you to modify the Referer header. The problem you describe requires you to install browser addons or other tools to modify the outgoing requests. Even if we started validating the Referer header, couldn't such tools just modify the redirect in the response anyway?

What are the security implications of redirecting the user back to a url they claim they arrived from?

I have the same question

What are the security implications of redirecting the user back to a url they claim they arrived from?

The thing is Penetration Testing Agency flagged this as Major vulnerability.

Anyways I did a small fix in my code to validate the redirect url and redirect to only valid url. For this I extended FormRequest and did a override of getRedirectUrl() function.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

abstract class Request extends FormRequest {

    /**
     * get the redirect URL
     *
     * @return string
     */
    protected function getRedirectUrl()
    {
        $url = $this->redirector->getUrlGenerator();

        if ($this->redirect) {
            return $url->to($this->redirect);
        } elseif ($this->redirectRoute) {
            return $url->route($this->redirectRoute);
        } elseif ($this->redirectAction) {
            return $url->action($this->redirectAction);
        } else {
            $previousUrl = $url->previous();
            return $this->validateDomain($previousUrl) ? $url->to($previousUrl) : $url->to('/');
        }
    }

    /**
     * validate the domain of the referrer
     * @param $referrer
     * @return bool|null
     */
    private function validateDomain($referrer)
    {
        $url = $this->redirector->getUrlGenerator();

        if (empty($referrer)) {
            return null;
        }
        $referrerDomain = parse_url($referrer, PHP_URL_HOST);
        $currentDomain = parse_url($url->current(), PHP_URL_HOST);

        return ($referrerDomain === $currentDomain) ? true : false;
    }
}

I can also confirm that I came across the exact same concern from my security group as well. This is an issue with the Laravel Framework and it should be fixed.

I can confirm that an independent pen test flagged up the same issue with our application

i can also confirm a scan flagged this as open redirect.
hey @karmendra, where did you store the FormRequest extension and did you name it FormRequest.php ?

I placed it in namespace App\Http\Requests; and named it Requests
Basically at following path
app/Http/Requests/Request.php

An independent PEN test flagged this in an application i work on.

I can also confirm our pen testers flagged this as a security risk. Following description has been provided (I believe it comes from a standard template):

During Pen testing it was found that an open redirect could be forced upon the user by modifying the ‘Refer’ header in a request to ‘malicious-website.com’. By requesting the server will respond with a 302 Found redirection. This redirection will use the value of the ‘https://malicious-website.com’, rather than the server itself.

Open URL Redirection is a security flaw in an application which causes it to improperly authenticate URLs, resulting in the redirection to a potentially malicious third-party web site. The application used an user-controllable parameter to redirect the user's browser to subsequent content.

there are a few articles talking about it:
https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/understanding-and-discovering-open-redirect-vulnerabilities/

https://blog.detectify.com/2019/05/16/the-real-impact-of-an-open-redirect/

Finally I'd like to quote the conclusion of the 2nd article:

So in conclusion, when you report or create a finding for an open redirect, it is generally not for the impact of the open redirect in itself, but rather for what it can be combined with. Thus, fixing an open redirect prevents the vulnerability from being exploited at an earlier stage.

It's a bit disappointing the laravel framework is not willing to address this issue just because there's no direct harm to the system. Any laravel application that is important enough to get a penetration testing audit will get this as an issue.

@sisve / @GrahamCampbell we should really reopen this thread and address this issue. it was originally raised in 2016, may be that time it was bit less known about this.
@peterw35 pointed to articles that describes the issue in more detailed fashion and also says when and how it can be exploited.

@karmendra - i used your suggestion as a starting point to fix my problem.

However i think the problem can also be prevented by setting the Referrer Policy Header, which i also implemented on recommendation of PEN testers.

This security header can prevent the referrer header from being set. Which in theory (although i have not tested it properly) should remove the referer URL, which should fix the problem.

https://scotthelme.co.uk/a-new-security-header-referrer-policy/

@edd-smith this is interesting, I wasn't aware of this. Thanks for sharing.

So in context of Laravel, setting Referrer-Policy: same-origin on my web-server would fix this issue?

How will Laravel handle redirecting back to Form when there is no URL in referrer, in my code I redirect user to root ('/') if referrer origin and current origin do not match.

We got around the issue by setting the Content Security Policy Referrer and Referrer-Policy headers to "no-referrer"

Laravel then gets the previous URL from session data, rather than from the referrer header.

People keep saying this is appearing in "scans" but not providing an actual way to exploit it in a real, default Laravel application 👀

@peterw35 @karmendra Both of the linked articles mention the urls as querystrings, not as a Referer header. We have not yet seen a proof-of-concept on how this can be exploited.

The application used an user-controllable parameter to redirect the user's browser to subsequent content.

The Referer header isn't user-controllable in a browser. The browser generates it automatically, and should not allow it to be manually specified. If anyone can show me an example where this isn't the case, where the browser allows he website or javascript to specify the Referer header to use, then there's a proof-of-concept and something we need to fix.

@edd-smith @karmendra @1stwebdesigns Setting the Referrer-Policy will not change anything. (However, it's still a good idea for other reasons.) Keep in mind that the Referrer-Policy header tells the browser what Referer header should be sent out when navigating from _your_ page. An evil third party site will not set this header, and request from the evil third party site will have an evil third party site Referer header set. Laravel will then use this header.

We really need a proof-of-concept to show how this is actually a problem. Do you know of any articles or other references that talk about the Referer header explicitly?

@sisve If I can change referrer url in my browser than it is user-controllable, is what I understood.

I in my own browser (firefox) I can intercept a form post using Inspect-element, network tab, Edit and Resend option and change the referrer url to evil.example.org url before it is posted to server and on error when it redirects to referrer url I land in evil website.
image

Another way to intercept onBeforeRequest is using this https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Intercept_HTTP_requests or using an addon like https://addons.mozilla.org/en-US/firefox/addon/xhr-interceptor/

I really do not know how any external attacker can change the referrer header of my request.
Can it be that a browser extension that has access to my request (e.g. barryvdh/debugbar) gets infected with malicious code that modifies the URL? Or something else like this, I am not sure.

Well, I see there are many articles about open redirect vulnerability when referrer url is used for automatic redirects.

https://blog.qualys.com/securitylabs/2016/01/07/open-redirection-a-simple-vulnerability-threatens-your-web-applications

https://blog.qualys.com/news/2019/12/11/cve-2019-11016-open-redirect-vulnerability

These article have more details about how this "CAN" be exploited.

Anyways without fixing this we were not getting our PCI compliance certificate for sure.

I'll admit that the phrase "user-controllable" is wrong; you could just have told me that you can control it when sending requests in Postman. ;) I think that requiring the user to install addons or do manual things is out of the picture. You would need some way to exploit this functionality just by having the user browse to, and interacting with, your site.

Any post from evil-site.com would hit the csrf validation and result in a HTTP 419.
Any post from your own domain, with a proper csrf token, would redirect the user back to your own domain.

If we ignore the fact that we have no clear exploit (or perhaps that I just don't understand the problem); how would you want to fix it? Do you want to modify Handler::invalid or UrlGenerator::previous? Is it enough to make sure that we only redirect to same host as we're already on?

Well, I do not have any problem right now, as I mentioned in my earlier post I did override getRedirectUrl() function to compare the current domain and referrer domain, if they are not same then redirect to homepage instead of the referrer url.

Yes I think, modifying UrlGenerator::previous to do this origin domain validation and falling back to previous url stored in session would be a solution for this case. Or may be just throwing an exception that redirecting to unexpected domain.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gabriellimo picture gabriellimo  Â·  3Comments

felixsanz picture felixsanz  Â·  3Comments

shopblocks picture shopblocks  Â·  3Comments

progmars picture progmars  Â·  3Comments

Fuzzyma picture Fuzzyma  Â·  3Comments