This has been driving me mad for the last few hours. It seems like PHP/Apache/Pound/Haproxy (can't really tell) insists on redirecting a page to the same scheme/domain I was on before, even though I clearly state it should be a different one.
Background: we usually have an http:\\www.example.com domain coupled to an https:\\secure.example.com secure domain (note the different subdomains). This makes our development easy, since we don't have SSL certificates for all our developers/projects and we can just check if the URL starts with 'secure.'. For this project, the client wants https:\\www.example.com as a secure domain, so I thought: no problem, I'll just change the logic to detect that for production. That part is working fine.
However, in some cases the user needs to be redirected from secure to non-secure, e.g. when having logged in and being redirected to the homepage. I've narrowed it down to this simple test-case (note: secure and non-secure public directories are simply symlinks to one another):
<?php
// this is our server-specific way of detecting secure/non-secure in production
if (isset($_SERVER['HTTP_X_SSL_CIPHER'])) {
header("HTTP/1.1 Moved permanently", true, 301);
header("Location: http://www.example.com/test.php");
}
echo 'bubbles!';
You'd expect it to redirect once after calling https:\\www.example.com/test.php, and then echo "bubbles!". However, my browser insists on redirecting back to httpS:\\www.example.com/test.php, thus causing an endless loop.
If I replace the header call with an HTTP meta refresh and/or javascript redirect all works fine, but I really want it to work with the location header. Apache/PHP/Pound/Haproxy seem to be deciding for themselves that I should just stick to the secure domain, even though I'm explicitly telling them not to.
One other interesting thing to note is that the reverse (redirecting from non-secure to secure) seems to be working fine. Also, on other sites (on the same server) that use the https:\\secure.example.com scheme, everything is working fine too, presumably because the subdomain is different.
Does anyone have any idea why this could be happening, and what is causing it? Our server setup is: Pound to distribute https requests to hidden webservers on port 8080, then haproxy to do the same for non-secure on port 80 (and some other stuff like IMAP), and behind that just some plain vanilla Apache webservers.
The only thing I can think of is that somehow the load balancers are messing things up, but that doesn't make sense since the replies from the webservers are sent directly back to the client AFAICT... any ideas, anyone?
N.B. replaced the slashes in the examples with backslashes, or SO thought I would be posting "too many links". They're not links, they're example domains. If I wanted to post a link, I'd have added an anchor.
Found it; Pound defaults to rewriting any location header to "what was originally requested" if the domain names match (which they did in this case). See "RewriteLocation 0|1|2" in the ListenHTTPS section; should be set to 0 ("don't do this") in my case.
Related
I have a situation that an Apache web server running a site on Joomla/php is managing to get itself into a redirect loop.
When visiting http://www.example.com/ it is issuing a 302 redirect to http://www.example.com/ (yes, the same url).
After some investigation we narrowed it down to the fact that there is a cookie with a value that it doesn't like...
The cookie contents is:
%7B%22distinct_id%22%3A%20%22vp37vv%22%2C%22%24initial_referrer%22%3A%20%22%24direct%22%2C%22%24initial_referring_domain%22%3A%20%22%24direct%22%7D
To save you the trouble that is urlencoded and decodes to:
{"distinct_id": "vp37vv","$initial_referrer":
"$direct","$initial_referring_domain": "$direct"}
My guess was that there was some kind of "security" check going on, that it was looking at the cookie, not liking what it saw there and doing the redirect in misguided attempt to get rid of the bad value.
I noticed that if I put that cookie value in the url then it did a similar redirect. That is going to:
http://www.example.com/?c=%7B%22distinct_id%22%3A%20%22vp37vv%22%2C%22%24initial_referrer%22%3A%20%22%24direct%22%2C%22%24initial_referring_domain%22%3A%20%22%24direct%22%7D
resulted in it redirecting to http://www.example.com/ (ie it successfully got rid of the value it didn't like). Testing with a "safe" querystring value did not cause the redirect.
So to the question: What could be causing this and what can I do to stop it from happening?
Changing the cookie in any way is not an option since it is part of third party software - I need to make the server accept this cookie's existence.
My guess is that this is a php safety feature but if that is the case then I'm having trouble finding any documentation about it.
There are a couple of things that may be causing this problem:
A Joomla security plugin (check to see if you have non-core Joomla security plugins installed on the server)
A ModSecurity Rule (or another firewall rule) being triggered. If that's the case, then you will need to whitelist that rule (check this post on how to whitelist a ModSecurity rule).
I suspect the latter.
Does anyone know if there's a way to send PHP a different $_SERVER['SERVER_NAME'] value to the one that's actually being used to call Apache? I.E: if the user types dev.mydomain.com in the URL bar of the browser, it arrives at the correct IP address, but tells PHP that the server name is live.mydomain.com? I've Googled around for this, but not found anything.
"Why do I want to do this?" I hear you ask
I've inherited a large PHP system that makes a lot of decisions based on the domain name being used to call the system. The domain name is sometimes hardcoded, and sometimes stored in the database.
I now need to make a development environment separate from the live environment. I could search and replace all those domain names, but I then have different code for the two environments, and problems with the code versioning.
Please note
I don't need the user to hack or fake anything, so there's no security issues. I want, as the site administrator, Apache and PHP to use different domain names.
The following 'solutions' are not what I'm asking
ServerAlias : No. Apache still sends the wrong domain name to the code.
Redirecting : Is only going to send the request to the wrong server.
Hacking the client's host file : Sure I can do that on my own machine, but a number of 'technically naive' people without administrator rights on their machines also need to test the development site. And they would need to remember to change it back when done tesing. It would become unworkable.
Any ideas please?
After another 3 hours searching on the internet, I eventually found an answer. I'll leave it here for the next person searching how to do this.
First things first. ModRewrite was not the answer! By the time Apache is processing its ModRewrite and SetEnv settings, the SERVER_NAME and 'HTTP_HOST' variables are cast in stone. You can try resetting them, but your PHP won't ever see the changed values.
What does work is PHP's auto_prepend option. How you set this depends on your PHP setup, but the flag is as follows:
auto_prepend_file=prepend.php
Then create the prepend.php file in your document root:
<?php
if($_SERVER['HTTP_HOST'] == 'dev.mydomain.com'){
$_SERVER['HTTP_HOST']= 'live.mydomain.com';
$_SERVER['SERVER_NAME']= 'live.mydomain.com';
}
?>
It even works in Symfony. All you have to do is ensure the prepend.php file stays out of your master repository.
And then, literally 30 seconds after posting the question, I thought to myself: "Hang on... You haven't tried ModRewrite yet. I'll leave the question here for now. I could get other useful feedback!
I have found many posts regarding checking the SNI browser support part, but not combining with the system time.
The default landing for my site is http. Now I have set up SSL. But compatibility is most important to me. Security is least important. I only want to auto-redirect users to https if I'm damn sure they won't see an error (and then falsely think my site's broken).
Since landing page is HTTP, we can use php or .htacess to detect. I had seen discussion elsewhere on using php redirections which may cause some "Back" button issue. (Force SSL and handle browsers without SNI support)
Besides SNI support, I also know that if user has a wrongly configured system time, he may encounter error on https site.
So what's the best approach to achieve both SNI + system time check? Any other possible scenario where user get redirected to https may encounter errors?
=====================================================================
Update:
Loading the home page doesn't really require "security". I'm thinking whether I can do a quick check to see if user could successfully load an object e.g. https://example.com/test.ico , if yes then show a "Secure Login" option. (Kudos to Steffen). The post action will then be done in https to prevent credentials being submitted unsafely.
If the test for https fails, then no choice the user has to login without https.
Will this work?
The additional test would definitely be a drag on site load speed isnt it
There is no way to detect this on the server side with PHP or .htaccess because if a connection fails because of missing SNI or wrong system time then it fails already during the SSL handshake. But the PHP part or .htaccess gets only used for the HTTP part, i.e. only if the SSL handshake completed successfully.
What you might try to do is to include a https resource into your landing page and see if this gets loaded successfully. This might be done with image, css , XHR or similar. For example you could do this
<img src="https://test-ssl.example.com"
onload="redirect_to_https();"
onerror="notify_user_about_problem();" />
If the SSL connection to your site got established successfully the onload handler gets executed and the user gets redirected to the https site. If the handshake fails instead the user gets notified about the problem.
Note that you cannot distinguish between different kinds of errors this way, i.e. you don't know if the problem is caused by the wrong system time or missing SNI support. If you want to do this you need to have to include a non-SNI resource the same way and see if it gets successfully loaded.
But compatibility is most important to me. Security is least important.
I consider this a bad but unfortunately very common approach. I would recommend you instead use this mechanism not to allow the user to access all functionality of your site with http only, but the sensitive parts should be restricted to https. But you could use this approach to inform user about the problem in detail and showing alternatives instead of just causing some strange connection error because of the failed SSL handshake.
This is not the answer you are looking for and was going to leave as a comment for that reason, but got to long, so decided to answer instead:
While it's admirable to try to handle all your users no matter what antiquated tech or bad setup they have, at some point you've got to ask yourself about the effort's you're putting in versus the reward of a few extra hits?
Managing dual http and https is an administrative nightmare that just puts your https-capable users (the vast majority on nearly all sites) at needless risk due to downgrade attacks, inability to make cookies secure, accidentally including insecure content... etc. From an SEO perspective as well you basically have two sites which is difficult to manage.
IMHO, if you feel this strongly about SNI users then pay for a dedicated IP, if not move on to HTTPS only.
compatibility is most important to me. Security is least important
Then stick to HTTP and don't even consider HTTPS.
Finally you've got to remember that browsers will force you move on soon enough. Forcing SHA-2 certs for example (which are not supported by very old browsers - similar to SNI) means that you will eventually have to call it a day on older browsers. So any answer you come up with here will only be short lived. And that's assuming you can come up with a solution that will work across all browsers (no small ask in itself!).
I believe the recommended way these days is to serve static files from a domain using //domain.com rather then http://domain.com and https://domain.com as needed. In fact https://developers.google.com/speed/libraries/devguide lists snippets in this format.
My question is, does this or any method allow caching across http/https? I thought it did (can't remember why) but my testing doesn't seem to allow me to do it. The problem is I recently changed a number of things about my setup (server, PHP framework) and so can't be sure why I cant cache across http/https (unless of course it isn't possible).
I'm wondering which of the methods is best to include static files:
(1) Serve exactly http or https depending on the page requested
(2) use //domain.com
(3) Always use https to serve static content, even on http pages as that way it will only donwnload once, but of course first visit to a site uses https for static content which can be slow, but at least it won't download files twice.
I know there's an issue in IE7 and 8 with stylesheets by using the //domain.com method though.
Any help, and particularly is it possible to cache across protocol as when a user first uses a https page for the first time it's really slow (until everything's cached) and I want to stop that.
On a server that supports both http and https you should probably reference assets via https if possible. This will maximize the caching and avoid any mixed content errors. Another motivation is that tomorrow this is going to start happening with the release of FF 23. Basically in an ssl context the browser will not download non-ssl assets.
This is a variant of a common php problem that seems to defy solution (and common sense): when a user switches between http and https on my site, php dumps the contents of the session. This would be bad enough, except for the fact that the site works fine when I run it under the domain test.mysite.com. The problem only shows up when I run it under www.mydomain.com, and only on our new server. The code worked just fine on my old machine!
Both servers are running CentOS, with the troublesome one on Rackspace CloudServer.
Any suggestions?
Edit
Just to make something clearer: the session actually gets cleared when going to a secure page. I can't go back to an unsecured page to view the original contents of the session, even though the session id's haven't changed.
Sounds like you've fallen victim to the curse of the dreaded php.ini file. Some cookie parameters are getting set differently.
I would ensure that the php.ini files in development and production are EXACTLY the same, you are running the same version of PHP, ideally the same build.
EDIT: ok, so it's not necessarily a difference in php.ini.
Have a look at the domain you're using for your cookies. If you set a cookie without explicitly setting the domain, it is the current domain only.
If this is www.example.com, users who visit http://example.com/ then are redirected to https://www.example.com/ WILL LOSE THEIR COOKIES.
Why? Because the cookie is being set for the exact domain, and won't be sent by the browser to a different host name.
The same is true if you run the site on multiple names. Be sure that you only run the site on exactly one name. If a user arrives on any other name, redirect them with a permanent redirect to the One True Name, before setting any cookies.
Can you try setting the secure flag to false using this function?
<?php
session_set_cookie_params(0,"/",".mysite.com",false,true);
?>
More info here: http://us2.php.net/manual/en/function.session-get-cookie-params.php