- Is a hijacked session the same as an exposed password? a hijacked session compromises a single session on a single system, while a stolen password constitutes a cross-session and (because passwords are re-used) often a cross-system breach. At the very least, probably better to keep the damage temporary and in-house rather than be the site that compromises everyone else?
- Many major sites implement case-1 security- Facebook for example. There's got to be some reason for that?
- The login-only vs all-the-time SSL options aren't my idea, I took it from WordPress (http://codex.wordpress.org/Administration_Over_SSL). Again, assuming there must be a reason.
FORCE_SSL_LOGIN is for when you want to secure logins so that passwords are not sent in the clear, but you still want to allow non-SSL admin sessions (since SSL can be slow).
FORCE_SSL_ADMIN is for when you want to secure logins and the admin area so that both passwords and cookies are never sent in the clear. This is the most secure option.
This probably has to do with Wordpress being installed on every shared hosting facility in the world..... speed vs security just to not leak password (but leak cookie) is not a dealbreaker in 2012 for us (at least until python shared hosting improves the coverage). An addendum to the 37signals link you posted before: is dated 2008! Even IE8 now caches correctly by default if cache headers are set.As for your PS, I don't see how the scenario you describe would be any different from a site that is all SSL all the time? The user has to start at an HTTPS login screen somehow- are you saying the whole HTTPS concept is BS?
Feel free to keep this going- I'm no security expert, just trying to get a handle on in it all--
if request.env.http_x_forwarded_for and request.env.http_x_forwarded_proto in ['https', 'HTTPS'] and auth.settings.is_proxied:Wrong- this is a matter of protecting the user. I may have a site that doesn't deal with anything important. Let's say it allows users to see their friends' baby pictures. They need to login so that we know what babies to show. Session gets hijacked? Big deal, you can see someone else's babies. But half the users are gonna use the same password they use for their paypal account. Should they have? no. But you don't want to be the vector by which that stuff happens.
2) It's a cpu issueI can't follow your conclusion here. You say it's not important in 2012, and yet Facebook still defaults to it and WordPress continues to offer it. It may not be a good trade off in most cases, but it's certainly common practice. I think we're still missing something here...
4) Mixed content issueMixed content is not always a choice. If you pull images hosted on other HTTP sites, boom, you're stuck with mixed content, and some browsers don't handle it elegantly. IE throws a pop up in your face. Chrome shows a warning indicator on all your HTTPS pages in a session if just one page has mixed content.
5) Still don't understand your PS. Can't tell if you're talking about user perception or actual DNS poisoning, but the first point is out of scope I think- my concern is that WE know what is secure- I'm not counting on the user to know or care. As for the latter, I still don't see how the scenario is any different if both case-1 and case-2 require a user to be redirected to https://example.com/login if they type in http://example.com/login. lost me on this one.
if request.env.http_x_forwarded_for and \
request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
# Is HTTPS...if auth.settings.is_proxied and \
request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
# Is HTTPS...def force_https(trust_proxy = False):
""" Enforces HTTPS in appropriate environments
Args:
trust_proxy: Can we trust proxy header 'http_x_forwarded_proto' to determine SSL.
(Set this only if ALL your traffic comes via trusted proxy.)
"""
# If cronjob or scheduler, exit:
cronjob = request.global_settings.cronjob
cmd_options = request.global_settings.cmd_options
if cronjob or (cmd_options and cmd_options.scheduler):
return
# If local host, exit:
if request.env.remote_addr == "127.0.0.1":
return
# If already HTTPS, exit:
if request.env.wsgi_url_scheme in ['https', 'HTTPS']:
return
# If HTTPS request forwarded over HTTP via SSL-terminating proxy, exit:
if trust_proxy and request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
return
# Redirect to HTTPS:
redirect(URL(scheme='https', args=request.args, vars=request.vars))The completely naive approach would be to do:
if request.env.http_x_forwarded_for and \
request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
# Is HTTPS...
But you cannot detect whether proxied traffic is real because headers are unreliable. Instead it is up to the user to securely set up a server behind a proxy and set the .is_proxied flag themselves.
Example:We put our app server behind an SSL-terminating load balancer on the cloud. The domain app.example.com points to the loadbalancer, so we configure app server's Apache to allow traffic from that domain only, and block any outside direct traffic. Then we set auth.settings.is_proxied to tell web2py "this proxy traffic is legit"HTTPS/443 requests will hit the loadbalancer, and be transformed to HTTP/80 traffic with http_x_forwarded_for and http_x_forwarded_proto headers set. Now we can confidently check:if auth.settings.is_proxied and \
request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
# Is HTTPS...In other words http_x_forwarded_for header is useless and you can't mix direct and proxied traffic. To be able to handle proxy-terminated SSL, we need to know that all the traffic is via a trusted proxy.
if request.env.http_x_forwarded_for and \
request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
# Is HTTPS...
But you cannot detect whether proxied traffic is real because headers are unreliable. Instead it is up to the user to securely set up a server behind a proxy and set the .is_proxied flag themselves.Example:We put our app server behind an SSL-terminating load balancer on the cloud. The domain app.example.com points to the loadbalancer, so we configure app server's Apache to allow traffic from that domain only, and block any outside direct traffic. Then we set auth.settings.is_proxied to tell web2py "this proxy traffic is legit"HTTPS/443 requests will hit the loadbalancer, and be transformed to HTTP/80 traffic with http_x_forwarded_for and http_x_forwarded_proto headers set. Now we can confidently check:if auth.settings.is_proxied and \
request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
# Is HTTPS...In other words http_x_forwarded_for header is useless and you can't mix direct and proxied traffic. To be able to handle proxy-terminated SSL, we need to know that all the traffic is via a trusted proxy.
def force_https(trust_proxy = False, secure_session = False):
""" Enforces HTTPS in appropriate environments
Args:
trust_proxy: Can we trust proxy header 'http_x_forwarded_proto' to determine SSL.
(Set this only if ALL your traffic comes via trusted proxy.)
secure_session: Secure the session as well.
(Do this only when enforcing SSL throughout the session)
"""
# If cronjob or scheduler, exit:
cronjob = request.global_settings.cronjob
cmd_options = request.global_settings.cmd_options
if cronjob or (cmd_options and cmd_options.scheduler):
return
# If local host, exit:
if request.env.remote_addr == "127.0.0.1":
return
# If already HTTPS, exit:
if request.env.wsgi_url_scheme in ['https', 'HTTPS']:
if secure_session:
current.session.secure()
return
# If HTTPS request forwarded over HTTP via a SSL-terminating proxy, exit:
if trust_proxy and request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
if secure_session:
current.session.secure()
return
# Redirect to HTTPS:
redirect(URL(scheme='https', args=request.args, vars=request.vars))
# If a login function, force SSL:
if request.controller == 'default' and request.function == 'user' and (auth.settings.force_ssl_login or auth.settings.force_ssl_session): force_https(trust_proxy = auth.settings.is_proxied, secure_session = auth.settings.force_ssl_session)# If user is logged in and we're enforcing a full SSL session:
elif auth.is_logged_in() and auth.settings.force_ssl_session:
force_https(trust_proxy = auth.settings.is_proxied, secure_session = True)
def on_login(form):
""" Post login redirection"""
# If we're enforcing SSL on login only, redirect from HTTPS to HTTP immediately after login:
if auth.settings.force_ssl_login is True and auth.settings.force_ssl_session is False:
if request.env.wsgi_url_scheme in ['https', 'HTTPS'] or request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
# Extract the post-login url value from auth
# (hack - look at end of login() function in tools.py. This belongs in Auth itself.):
login_next_path = auth.next or auth.settings.login_next
# Build an absolute, HTTP url from it:
login_next_url = URL(scheme='http',c='default',f='index') + login_next_path[1:]
# Redirect to the HTTP URL:
redirect(login_next_url)
auth.settings.login_onaccept = on_loginHere's a complete example of our own implementation (simplified, untested) using the proposed auth settings:
In our model:def force_https(trust_proxy = False, secure_session = False):
""" Enforces HTTPS in appropriate environments
Args:
trust_proxy: Can we trust proxy header 'http_x_forwarded_proto' to determine SSL.
(Set this only if ALL your traffic comes via trusted proxy.)
secure_session: Secure the session as well.
(Do this only when enforcing SSL throughout the session)
"""
# If cronjob or scheduler, exit:
cronjob = request.global_settings.cronjob
cmd_options = request.global_settings.cmd_options
if cronjob or (cmd_options and cmd_options.scheduler):
return
# If local host, exit:
if request.env.remote_addr == "127.0.0.1":
return
# If already HTTPS, exit:
if request.env.wsgi_url_scheme in ['https', 'HTTPS']:
if secure_session:
current.session.secure()
return
# If HTTPS request forwarded over HTTP via a SSL-terminating proxy, exit:
if trust_proxy and request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
if secure_session:
current.session.secure()
return
# Redirect to HTTPS:
redirect(URL(scheme='https', args=request.args, vars=request.vars))
# If a login function, force SSL:
if request.controller == 'default' and request.function == 'user' and auth.settings.force_ssl_login:
force_https(trust_proxy = auth.settings.is_proxied, secure_session = auth.settings.force_ssl_session)
# If user is logged in and we're enforcing a full SSL session:
elif auth.is_logged_in() and auth.settings.force_ssl_session:
force_https(trust_proxy = auth.settings.is_proxied, secure_session = True)
def on_login(form):
""" Post login redirection"""
# If we're enforcing SSL on login only, redirect from HTTPS to HTTP immediately after login:
if auth.settings.force_ssl_login is True and auth.settings.force_ssl_session is False:
if request.env.wsgi_url_scheme in ['https', 'HTTPS'] or request.env.http_x_forwarded_proto in ['https', 'HTTPS']:
# Extract the post-login url value from auth
# (hack - look at end of login() function in tools.py. This belongs in Auth itself.):
login_next_path = auth.next or auth.settings.login_next
# Build an absolute, HTTP url from it:
login_next_url = URL(scheme='http',c='default',f='index') + login_next_path[1:]
# Redirect to the HTTP URL:
redirect(login_next_url)
auth.settings.login_onaccept = on_login
On Friday, September 21, 2012 12:35:37 PM UTC-4, Yarin wrote:
OK. check trunk. Auth(db,secure=True).
--
if secure and not request.is_https:
session.secure()
redirect(URL(args=request.args,vars=request.vars,scheme='http')) if secure:
request.requires_https()