Handling obliged SSL in server code - http

I want to force HTTP clients to switch to HTTPS in my application. Users typing www.mysite.com will by default use HTTP, but they need to be redirected to HTTPS. Users using old bookmarks will be redirected to HTTPS version of the bookmarked page.
HSTS (RFC 6797) helps a lot once redirected. My question is actually about HTTP methods.
GET and HEAD are surely supposed to accept a 301/302 redirect, but what about POST/PUT and DELETE?
See the following example:
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
if (context.Request.IsSecureConnection) return;
if (context.Request.HttpMethod == "GET" || context.Request.HttpMethod == "HEAD")
{
string redirectUri = context.Request.Url.ToString().Replace("http://", "https://");
context.Response.RedirectPermanent(redirectUri, true);
}
else
{
throw new HttpException(403, "SSL Required");
}
}
Both GET and HEAD are handled with a redirect. Currently POST, as far as I know, accepts the 301 redirect as a GET request to be done, i.e. doesn't repost to the HTTPS version. So this is why in my code snippet I end up with a 403 code.
The question is to be read from the HTTP protocol's point of view
Apart from checking that all forms in the application point to HTTPS, how should clever HTTP developers force a client to redirect a POST request to the HTTPS version of a page when the browser directs its request to the plain old HTTP version?
Possible solution
Create a landing page filled with all form fields, that automatically (via Javascript and a "click me if you don't get redirected" button) reposts the form to HTTPS version of the target page.
Any other ideas?

The behaviour of POST upon redirection depends on the status code returned by the server. In addition, many browsers implement the Post/Redirect/Get pattern, which may not be entirely compliant with the HTTP specification.
This being said:
Avoid to rely on redirects in general. (See this answer.) It's not necessarily bad to have redirects in production, but that's for users who would type the address directly in the location bar. It relies on the assumption there isn't a MITM when the redirect is made anyway. I'd argue that redirects during the development phase are bad, because they hide potential problems.
Never rely on redirects on a POST (or even on a GET with sensitive information in the URL): the initial request (before the redirect to HTTPS) will be sent in clear anyway, which defeats the purpose of trying to use HTTPS.
If you want HTTPS to be used, make sure the links you give and the target URLs of your forms use https://. Also make sure that your users expect HTTPS to be used, if you can. Only the user can check that.
As Eugene was saying in a comment, it's also good practice to have the landing page where the form is to be served over HTTPS too.
Since you seem to be primarily concerned about users coming to this page using old bookmarks, POST and DELETE don't matter: they'll be using GET from a bookmark anyway. If you can, tell them to update their bookmarks. You can also use HSTS or 301 (permanent redirect), which the browser should remember, so that it will go straight to https:// next time the address is used, unless its cache is cleared.

Originally, the 301 and 302 responses were intended to preserve the request method and request-body, so that POST requests could be properly redirected. In practice, however, pretty much all web browsers implemented them so that the request method was changed to GET, since that's usually what one wants.
It is for that reason that 303 and 307 was introduced in HTTP/1.1. 303 literally means that the request method should be switched to GET, while 307 indicates explicitly that the method be preserved, so you probably want 307. Note, however, that I've never used 307 for anything, so I can't tell how well it works across browsers and other user-agents.
Aren't you making this a larger problem than it needs to be, though? It's not as if anyone can have bookmarked a POST request.

Related

Serving 404 directly

So I have an Nginx server set up which is supposed to redirect all http to https (and non-www to www) using 4 server blocks.
The issue is that any 404 or non existent http URL first get a 301 redirect to what could have been an https version if it hypothetically existed (hence creating an extra URL and redirect).
See example:
1) http://example.com/thisurldoesntexit
301 Redirect
2) https://example.com/thisurldoesntexit
404
3) https://example.com/notfound
Is there a way to redirect user directly to a https 404 (URL 3)?
First of all, as already been pointed out, doing a 301 redirect from a non-existent page to a single /notfound moniker, is a really bad practice, and is likely against the RFCs.
What if the user simply mistyped a single character of a long URL? Modern browsers make it non-trivial to go back to what has been typed in order to correct it. The user would have to decide whether your site is worth a retyping from scratch, or whether your competitor might have thought of a better experience.
What if the user simply followed a broken link, which is broken in a very obvious way, and could be easily fixed? E.g., http://www.example.org/www.example.com/page, where an absolute URL was mistyped by the creator to be a relative one, or maybe a URI like /page.html., with an extra dot in the end. Likewise, you'll be totally confusing the user with what's going on, and offering a terrible user experience, where if left alone, the URL could easily have been corrected promptly.
But, more importantly, what real problem are you actually trying to solve?!
For better or worse, it's a pretty common practice to indiscriminately redirect from http to https scheme, without an account of whether a given page may or may not exist. In fact, if you employ HSTS, then content served over http effectively becomes meaningless; the browser with a policy would never even be requesting anything over http from there on out.
Undoubtedly, in order to know whether or not a given page exists, you must consult with the backend. As such, you might as well do the redirect from http to https from within your backend; but it'll likely tie up your valuable server resources for little to no extra benefit.
Moreover, the presence or absence of the page may be dictated by the contents of the cookies. As such, if you require that your backend must discern whether a page does or does not exist for an http request, then you'll effectively be leaking private information that was meant to be protected by https in the first place. (In turn, if your site has no such private information, then maybe you shouldn't be using https in the first place.)
So, overall, the whole approach is just a REALLY, REALLY bad idea!
Consider instead:
Do NOT do a 301 redirect from all non-existent pages to a single /notfound page. Very bad practice, very bad UX.
It is totally OK to do an indiscriminate redirect from http to https, without accounting for whether or not the page exists. In fact, it's not only okay, but it's the way God intended, because an adversary should not be capable of discerning whether or not a given page exists for an https-based site, so, if you do find and implement a solution for your "problem", then you'll effectively create a security vulnerability and a data leak.
Use https://www.drupal.org/project/fast_404 module for serving 404 pages directly without much overload.
I'd suggest redirecting to a 404 page is a poor choice, and you should instead serve the 404 on the incorrect URL.
My reasons for stating this are:
By redirecting away from the page, you are issuing headers that implicitly say "The content does not exist on this URL, but it does over here". I'm not sure how the various search engines would react to being redirected to a 404
I can speak from my own experience as a user when I say that having the URL change on me when I've mis-typed by a single character can be very frustrating. I then need to spend the time to type out the entire URL again.
You can avoid having logic in your .htaccess file or whatever to judge a page as a 404. This will greatly simplify your initial logic (which by-the-by gets computed on every single page load) - and will remove far more redirects than just the odd one of http://badurl to https://badurl to https://404

What happens if a 302 URI can't be found?

If I make an HTTP request to get index.html on http://www.example.com but that URL has a 302 re-direct in place that points to http://www.foo.com/index.html, what happens if the redirect target (http://www.foo.com/index.html) isn't available? Will the user agent try the original URL (http://www.example.com/index.html) or just return an error?
Background to the question: I manage a legacy site that supports a few existing customers but doesn't allow new signs ups. Pretty much all the pages are redirected (using 302s rather than 301s for some unknown reason...) to a newer site. This includes the sign up page. On one of the pages that isn't redirected there is still a link to the sign up page which itself links through to a third party payment page (i.e. on another domain). Last week our current site went down for a couple of hours and in that period someone successfully signed up through the old site. The only way I can imagine this happened is that if a 302 doesn't find its intended URL some (all?) user agents bypass the redirect and then go to originally requested URL.
By the way, I'm aware there are many better ways to handle the particular situation we're in with the two sites. We're on it! This is just one of those weird situations I want to get to the bottom of.
You should receive a 404 Not Found status code.
Since HTTP is a stateless protocol, there is no real connection between two requests of a user agent. The redirection status codes are just a way for servers to politely tell their clients that the resource they were looking for is somewhere else now. The clients, however, are in no way obliged to actually request the resource from that other URL.
Oh, the signup page is at that URL now? Well then I don't want it anymore... I'll go and look at some kittens instead.
Moreover, even if the client decides to do request the new URL (which it usually does ^^), this can be considered as a completely new communication between server and client. Neither server nor client should remember that there was a previous request which resulted in a redirection status code. Instead, the current request should be treated as if it was the first (and only) request. And what happens when you request a URL that cannot be found? You get a 404 Not Found status code.

How to work around POST being changed to GET on 302 redirect?

Some parts of my website are only accessible via HTTPS (not whole website - security vs performance compromise) and that HTTPS is enforced with a 302 redirect on requests to the secure part if they are sent over plain HTTP.
The problem is for all major browsers if you do a 302 redirect on POST it will be automatically switched to GET (afaik this should only happen on 303, but nobody seems to care). Additional issue is that all POST data is lost.
So what are my options here other than accepting POSTs to secure site over HTTP and redirecting afterwards or changing loads of code to make sure all posts to secure part of website go over HTTPS from the beginning?
You are right, this is the only reliable way. The POST request should go over https connection from the very beginning. Moreover, It is recommended that the form, that leads to such POST is also loaded over https. Usually the first form after that you have the https connection is a login form. All browsers applying different security restrictions to the pages loaded over http and over https. So, this lowers the risk to execute some malicious script in context that own some sensible data.
I think that's what 307 is for. RFC2616 does say:
If the 307 status code is received in response to a request other
than GET or HEAD, the user agent MUST NOT automatically redirect the
request unless it can be confirmed by the user, since this might
change the conditions under which the request was issued.
but it says the same thing about 302 and we know what happens there.
Unfortunately, you have a bigger problem than browsers not dealing with response codes the way the RFC's say, and that has to do with how HTTP works. Simplified, the process looks like this:
The browser sends the request
The browser indicates it has sent the entire request
The server sends the response
Presumably your users are sending some sensitive information in their post and this is why you want them to use encryption. However, if you send a redirect response (step 3) to the user's unencrypted POST (step 1), the user has already sent all of the sensitive information out unencrypted.
It could be that you don't consider the information the user sends that sensitive, and only consider the response that you send to be sensitive. However, this turns out not to make sense. Sensitive information should be available only to certain individuals, and the information used to authenticate the user is necessarily part of the request, which means your response is now available to anyone. So, if the response is sensitive, the request is sensitive as well.
It seems that you are going to want to change lots of code to make sure all secure posts use HTTPS (you probably should have written them that way in the first place). You might also want to reconsider your decision to only host some of your website on HTTPS. Are you sure your infrastructure can't handle using all HTTPS connections? I suspect that it can. If not, it's probably time for an upgrade.

Is there such thing as a HTTP URL re-write without 301 or 302 redirect?

Is there such a thing?
A way it might be used:
Many locations have forms that post to http://www.example.com/wally/app/receiver.aspx
Managements decides they want a cleaner URL and there is no reason to pretend you are using aspx (you didn't really think I was using aspx for that did you?)
They say it should be http://example.com/receiver
Easy enough! Just put a 301 redirect. No need to update all those forms that exist all over..,, but wait.. You can't do that for POST.
Perhaps you can receive and handle the request and then re-write the URL without causing a subsequent request? Perhaps this will not strip the www (cross domain), but can it shorten the pathname like that without a separate request?
Even in GET requests, this would indeed be a performance boost if one could re-write the URL and send the response body at the same. Can this be done?
You cannot send content to user and do 301/302 etc redirect at the same time -- browser interprets the HTTP Response code and acts accordingly to the code received. If 301/302 -- it will do redirect, if 200 -- will display it to the customer.
Is there such thing as a HTTP URL re-write without 301 or 302 redirect?
Yes -- it's called rewrite (internal redirect). For example -- customer requests http://example.com/receiver. You rewrite URL to point to /wally/app/receiver.aspx (e.g. RewriteRule ^receiver$ /wally/app/receiver.aspx [L] -- that's if you have an Apache, which you most likely not (considering receiver.aspx)). This will do internal redirect when URL remains unchanged in browser address bar (works fine with POST and GET methods).
Well, I guess rewriting url suggested by LazyOne is not the answer to the question as he himself states that
This will do internal redirect when URL remains unchanged in browser
address bar
(http://www.example.com/wally/app/receiver.aspx). Still, the question asks for
(...) it should be http://example.com/receiver
I think the solution is to redirect old url to the new one using 307 status code introduced in RFC 2616. User agents which handle version 1.1 of HTTP protocol (I guess all popular browsers for some time now) should make the new request using the same http method (POST in this case) as in the original request.

Resolve http form post as https form post

Is there a way to force a non-secure form post to be secure? I understand there are ways to automatically resolve an http URL as an https URL but with form posts, is this type of redirection too late? Will the posted data have already gone through the wire as plain text?
Is there a way to force a non-secure form post to be secure?
Technically, no. You can however make the form's action attribute a fully-qualified HTTPS site, regardless if the protocol that rendered it is secured or not.
Will the posted data have already gone through the wire as plain text?
Yes since a redirect happens on the server by issuing a 301/302 status code in the response.
Generally speaking, the page that serves up the form should be https as well. If it is not, then users have no indication before they submit the form that it will be secure (that is, there'll be no 'lock' icon until after they submit). Also, an attacker could hijack the initial page and still collect responses without any warning to the users.
By setting the 'action' to a full-qualified https URL then the data will be encrypted when it goes to the server, but it is still less secure than doing the whole thing in https from the start.
Here are a few things that come to mind:
As has already been noted, you can just set the URL of the form's action to an HTTPS address.
Change the protocol from HTTP to HTTPS before the page is posted. This would seem to be the most ideal. It also gives your visitors greater sense of security by seeing the secured padlock before entering any info (if this even matters). Three ways to do this come to mind:
Change all links to your form page to be in HTTPS.
Detect when the page is browsed non-securely, and redirect (using client-side scripting)
Do the same thing but on the server, by sending a Location header (a.k.a. Response.Redirect).
An example of this in javascript is simple enough:
if ('https:' != window.location.protocol) {
// This assumes your secured domain is the same as the currently-browsed one...
window.location = String(window.location).replace(/^http:\/\//, 'https://');
}
Good luck!

Resources