9

CakePHP (all versions that I've seen) check against $_SERVER['HTTPS'] to see whether a request has been made over HTTPS instead of plain HTTP.

I'm using nginx as a load balancer, behind which are the Apache application servers. Since the SSL connection terminates at the load balancer, $_SERVER['HTTPS'] is not set as far as CakePHP is concerned.

I'd like to find a secure way to detect HTTPS on the app servers.

So far, I've put this into my CakePHP configuration:

$request_headers = getallheaders();
if ( (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']) || ( isset($request_headers['X-Forwarded-Proto']) && $request_headers['X-Forwarded-Proto'] == 'https' ) ) {

    $ssl = true;

    // overwrite environment vars (ugly) since CakePHP won't honour X-Forwarded-Proto
    $_SERVER['HTTPS'] = 'on';
    $_ENV['HTTPS'] = 'on';

} else {
    $ssl = false;
}

And then in the nginx configuration, I've used proxy_set_header X-Forwarded-Proto https; to add the flag to any requests between the load balancer and the back-end application servers.

This works perfectly fine, but anyone making a direct request to the app servers could fool them into thinking they are browsing over SSL when they're not. I'm not sure whether this is a security risk, but it doesn't seem like a good idea.

Is it a security risk? What's the better solution?

Since using X-Forwarded-Proto seems like something of a standard, the solution may be a good patch to be submitted to the CakePHP core, so I think any answer can legitimately involve editing core files too.

Community
  • 1
  • 1
Aaron Pollock
  • 439
  • 5
  • 13
  • 4
    Check the remote-ip as well (the one of the proxy, not anything signalled via headers). If it ain't your SSL proxy you configured and you know how it behaves, then don't trust the request header. Take care your proxy always sets the request header, regardless it's HTTPS or HTTP. – hakre Jan 13 '13 at 14:41
  • Also if the CakePHP core does not allow to replace the implementation on how to read out the concrete frontend protocol, then this needs more patching as just duck-taping this in if you want to do it right. – hakre Jan 13 '13 at 14:42
  • That's a nice idea. I did see a blog post which suggested having a config flag for "isLoadBalancedEnvironment" on the Apache app servers, but your solution with the IPs is more secure, since it validates the origin of the request header too. – Aaron Pollock Jan 13 '13 at 14:45
  • 3
    You can also already configure your webserver(s) to only accept connections from the load balancer (or from localhost for debugging purposes). – hakre Jan 13 '13 at 14:47

3 Answers3

3

Add a request detector

Do not edit the core. If your intention is to submit a patch, don't rely on your patch until after it's accepted - Otherwise you're on a road to divergence and maintaining your own fork of CakePHP.

Once you determine your exact implementation logic you can use a request detector to honor it.

for example:

//AppController::beforeFilter
public function beforeFilter() {
    $this->request->addDetector('ssl', array(
        'env' => 'HTTP_X_FORWARDED_PROTO',
        'value' => 'https'
    ));
}

After this, your custom header will correctly be identified by cake as an ssl request.

Note that the keys of the $_SERVER global are normalized as all caps and underscore delimited i.e.:

$ curl --header "X-Forwarded-Proto:https" http:://yoursite.com

Will populate $_SERVER['HTTP_X_FORWARDED_PROTO'] - as such this is the key to check for.

Account for (in)security

Yes, it's something you should take care of - either disable direct access to your webservers so that only via the ip of the loadbalancer will it respond at all; or modify your detector so that it does not return true for direct-access requests irrespective of the value of the X-Forwarded-Proto header - as shown in the documentation you can use a callback to perform whatever logic required rather than simply testing the values of some environment variable.

Community
  • 1
  • 1
AD7six
  • 63,116
  • 12
  • 91
  • 123
  • I hadn't heard of request detectors yet - thanks for that pointer. I've got a quick test running on a vanilla CakePHP install, with your `beforeFilter` and I'm echoing the result of `$this->request->is('ssl')` in the PagesController->display() method for now, but I'm getting `false`, even when I send the `X-Forwarded-Proto` header from my HTTP client. Since `X-Forwarded-Proto` is an HTTP header, and not an environment variable, will it still get picked up by a call to the `env()` function in Cake? I don't see that in the `env()` method in /lib/Cake/basics.php – Aaron Pollock Jan 13 '13 at 18:09
  • Thanks for the edit @AD7six - I'll give it a try later but I think you have it nailed! – Aaron Pollock Jan 14 '13 at 14:01
  • This does indeed work, but calls to `$this->redirect()` still generate 302s to HTTP URLs instead of HTTPS URLs. I guess I need to go lower in the stack than the `$this->request->is('ssl')` call to get that working seamlessly. The hunt goes on! – Aaron Pollock Jan 14 '13 at 20:23
  • you can simply override redirect in your app controller and force https. i.e. `function redirect redirect($url) { return parent::redirect(str_replace('http', 'https', Router::url($url, true)));}` – AD7six Jan 14 '13 at 21:52
  • Yeah, that's what I've been doing for some time. Be nice if it was handled lower down. – Aaron Pollock Jan 14 '13 at 21:57
2

mod_rpaf will let you do this.

This sets the HTTPS value in Apache to "on" based on the headers sent by nginx so Cake will work out of the box (as well as any other apps run in Apache).

It also corrects the values for REMOTE_ADDR, SERVER_PORT and HTTP_HOST.

Here is my example config:

<IfModule mod_rpaf.c>
    RPAF_Enable       On
    RPAF_ProxyIPs     127.0.0.1 10.0.0.0/24
    RPAF_SetHostName  On
    RPAF_SetHTTPS     On
    RPAF_SetPort      On
</IfModule>

# If mod_rewrite redirects then we lose the HTTPS status to REDIRECT_HTTPS.
# This resets it back. This happens with Cake's front controller
<IfModule setenvif_module>
    SetEnvIf REDIRECT_HTTPS on HTTPS=on
</IfModule>
BigToach
  • 560
  • 5
  • 8
1

I found the comment of AD7six - override the redirect() function - extremely helpful in my SSL-only setup since my app had some redirects going to http.

I just added the following to AppController.php so the redirect function always uses https:

function redirect($url, $status = NULL, $exit = true) {
    return parent::redirect(str_replace('http://', 'https://', Router::url($url, true)));
}
lorem monkey
  • 3,942
  • 3
  • 35
  • 49
  • There's actually a cleaner way ([assuming v2.4 or later](http://book.cakephp.org/2.0/en/development/routing.html#Router::fullBaseUrl), in earlier versions [it's a constant](https://github.com/cakephp/cakephp/blob/2.3.0/lib/Cake/bootstrap.php#L139)) - just define `Router::fullBaseUrl('https://exmaple.com');` and that's what absolute urls will then use - feel free to test and update your answer (+1). – AD7six Aug 27 '14 at 13:50