Personal tools
You are here: Home Documentation How-tos Secure login without plain text passwords
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Secure login without plain text passwords

This How-to applies to: Plone 2.5.x, Plone 2.1.x, Plone 2.0.x
This How-to is intended for: Integrators, Customizers

By default, plain text usernames and passwords go over the wire for both initial login and the subsequent cookie authentication. This how-to shows a safer alternative.

IMPORTANT: These tips only apply partially to Plone 3.0 and later versions. The new implementation of passing along login information on every request is secure by default. The only thing that is insecure is the initial sending of the username/password from the login form (if you don't do that via https).

By default, 2.x has some insecure defaults. It is no real problem, as there are trade-offs for the safer alternatives. But you can make your site more secure with a few measures that are not too hard.

Insecure http

In default plone, passwords go over the wire in plain text. When you submit the login form, you send over two parameters: __ac_name=yourname and __ac_password=yoursecretpassword.

When someone is able to sniff the network traffic, this is one potential attack point. This could be remedied by encrypting the form data with https. Plone - or rather, zope - doesn't speak https, but that's not really the problem as you should put apache or squid in front of plone anyway.

Insecure default authentication cookie

After authentication, plone sets a cookie that gets passed on every subsequent request. Using firefox' LiveHttpHeaders extension is very instructive in this case:

    GET /plone/folder_icon.gif HTTP/1.1
    Host: localhost:3030
    Accept: image/png,*/*;q=0.5
    Cookie: __ac="dGVzdHVzZXI6cmV1dGVsCg%3D%3D"

To scare you even more: that cookie value looks pretty safe, but it is a base64 encoded string. %3d is urlencoded for =, so replace that and run the string through a base64 decoder:

    $> echo dGVzdHVzZXI6cmV1dGVsCg== | base64 -d
    testuser:reutel

So that innocent-looking cookie contains our username testuser and password reutel in basically plain text :-)

Secure sessions

If you install SessionCrumbler according to the installation instructions (basically replacing cookie_crumbler with session_crumbler), the login mechanism will use a cookie with a session ID instead of a cookie with the login data. Using LiveHttpHeaders:

   Cookie: _ZopeId="58768270A2TfqKoWLsw"

This is one less thing to worry about. It comes at the price of some disadvantages, see plip 48 :

  • In the standard setup, sessions apparently only allow 1000 users. And every session takes up some memory, so if your site has lots of logged in users, you can get a performance hit.

    You can solve the 1000-user-limitation with a config setting, though of course not the memory hit, in your zope.conf:

         maximum-number-of-session-objects 10000     
    

  • With a multiple-zeo-setup you need to look at http://www.zopelabs.com/cookbook/1061234337

To remove the cookie_authentication and replace it with a session_authentication, you can use the following code in your installer:

    # Add this as a method in your (App)Install.py
    if 'cookie_authentication' in portal.objectIds():
        portal.manage_delObjects(ids=['cookie_authentication'])
        out.write("Cookie auth found, removed it.\n")
        SCfactory = portal.manage_addProduct['SessionCrumbler'].manage_addSC
        SCfactory('session_authentication')

After you've done that, you now suddenly have a problem with your plone logins. It turns out that CMFPlone/skins/plone_login/login_form.cpt and CMFPlone/skins/plone_portlets/portlet_login.pt have a hardcoded line in there that checks for cookie_authentication:

    auth nocall:here/cookie_authentication|nothing

You'll have to change that to session_authentication in both places.

Secure https

When you use https instead of http, you encrypt all your traffic. Cookies, form parameters, the page itself, all. The drawback is that https is not cached, which is a good way of killing the performance of your site :-)

You can get away with doing the minimum: just use https for sending the login data. The came_from parameter that plone passes along in the login form 'll put you right back in normal http country afterwards, so that's OK.

So you need to change the login portlet and the login_form to have https as their action instead of just http. The login mechanism should redirect back to the http site afterwards. But you probably only want this on the production site, not on the development machines. It can get more complicated: say that you have both a production and a preview site, then you'd need to take care of not accidentally redirecting from preview to production or the other way around.

To solve this, create a getLoginAction.py script that returns the normal "portal url + /login_form" action for sending the login data, except when the URL starts with a specific string that you use to identify your partially-https-fronted production website. Send back the https URL instead:

    ## Script (Python) "getLoginAction"
    ##bind container=container
    ##bind context=context
    ##bind namespace=
    ##bind script=script
    ##bind subpath=traverse_subpath
    ##parameters=
    ##title=return the action for logging in, taking into account https

    portal_url = context.portal_url()
    NEEDS_HTTPS = ['http://your.production.website.nl/',
                   ]
    for url_start in NEEDS_HTTPS:
        if portal_url.startswith(url_start):
            portal_url = portal_url.replace('http://', 'https://')

    return portal_url + '/login_form'

You need to call this script from both the login_form and the portlet_login. The following example is from the login_form.cpt:

      <form tal:attributes="action context/getLoginAction"
            method="post"
            id="login_form"
            tal:condition="python:auth">

Now the only thing left to do is to make sure your webserver accepts https connections, at least to /login_form.

(Original source of this how-to)

by Reinout van Rees last modified November 12, 2007 - 09:53
Contributors: Daniel Nouri
All content is copyright Plone Foundation and the individual contributors.

Id of Session Crumbler

Posted by Martin Aspeli at May 28, 2006 - 22:33
The name cookie_authentication is hard-coded into a number of templates, including:

skins/plone_login/failsafe_login_form.cpt
skins/plone_login/login_form.cpt
skins/plone_login/login_form_validate.vpy
skins/plone_login/logout.cpy
skins/plone_login/registered.pt
skins/plone_portlets/portlet_login.pt

Apart from the aesthetics, it may be better to just create the new Session Crumbler with the id 'cookie_authentication', to avoid the need to change all of those, not to mention any third party product that depends on that name being there.

Martin

Dirty, but works :-)

Posted by Reinout van Rees at May 28, 2006 - 23:17
Good idea. Kind of dirty, but the dirtyness is on plone's side. The crumbler could best be called "authenticationcrumbler" or so instead of hardcoding "cookie" into there. Ah well. Good hint!

Actually, I should eventually turn this into a small product. Replacing the crumbler, that's about it. Giving it a good "this is now a session crumbler" name.

The second thing would be a configlet to configure which (if any) https-redirection to use for which port (not for localhost:8080, for instance).

+1

Posted by Martin Aspeli at May 28, 2006 - 23:43
This stuff is a bit scary. I certainly didn't know about the security implications of using cookie crumbler!

I'd love to see it bundled up into a more re-usable product and made a bit more plug-and-play.

Martin

Sill plaintext

Posted by Martin Aspeli at May 28, 2006 - 23:59
With the change to login_form, you're still in plain-http when login_form is invoked.

I think you need to change the logic of the login form so that it loads https when needed. One crazy hack I could think of, would be to replace login_form with a pyscript that redirected to https but made sure the cameFrom argument was non-https.

You don't need to replace it, of course, you could change the login action and the auto-login id in cookie_authentication to refer to some intermediary script with a new name, but then you run the risk of people using /login_form by typing it in, or some link somewhere pointing to it explicitly. :-(

Martin

A possible hack

Posted by Martin Aspeli at May 29, 2006 - 00:13
Here's another hack :) This adds the logic into login_template, which is Bad (tm) but you know. The getLoginAction script is now used only to determine whether we want HTTP or HTTPS. I actually have a different version of it that returns 'http' for localhost and 'https' for everything else, which may be a saner default, but probably not what you want in all cases. A nicer way would be to look at a property in portal_properties to (a) decide when HTTPS is enabled and (b) decide which domains get HTTPS.

<tal:https define="loginAction here/getLoginAction;
portalUrl here/portal_url;
came_from request/came_from|request/HTTP_REFERER|nothing;">

<tal:if condition="python:loginAction.startswith('https') and not portalUrl.startswith('https')">
<tal:block define="dummy python:request.RESPONSE.redirect(loginAction + '?came_from=%s' % came_from)" />
</tal:if>
</tal:https>

Context

Posted by Martin Aspeli at May 29, 2006 - 00:15
All that is inside:

<metal:block fill-slot="top_slot">
[insert TAL code above here]
</metal:block>

Not needed?

Posted by Reinout van Rees at May 29, 2006 - 08:15
Why is this needed? You see the login form in plain http, but the actual form parameters are send to the "action", which is https. So the username/password are encoded OK. The came_from parameter gets you right back in http-land transparently.

The problem might be the transparency, but the process is quite safe behind the scenes. When I log in to vanrees.org I get a familiar "hey, this ssl certificate doesn't match" 'cause I didn't fix that up in apache yet, so it's https behind the scenes.

Perhaps

Posted by Martin Aspeli at May 29, 2006 - 11:45
Yes; I kind of realised this afterwards. I think a lot of people will feel a bit "unsafe" because they don't see the pretty padlock icon in their toolbar.

I assume you're in https land following a validation error (invalid username and password)?

Did you try approaches that use https via Apache RewriteRules, e.g. rewriting requests to /login_form to point to https (I'm not even sure how this works exactly, it all just confuses me).

Martin

Apache RewriteRule

Posted by Mike Takahashi at May 30, 2006 - 16:04

>Did you try approaches that use https via Apache RewriteRules, e.g. rewriting requests to /login_form to point to https >(I'm not even sure how this works exactly, it all just confuses me).

Via Apache, this is one way to do it. When users go to the login_form, it will be in https.

# Switch to HTTPS upon login_form RewriteRule ^/login_form(.*) https://www.yoursite.com/login_form$1 [NE,L]

Apache RewriteRule

Posted by Damian Brasher at February 18, 2008 - 15:37
I achieved this using some re-write rules that after some fettling worked, here they are for full https login, this also enables the user to remain logged in until they logout which then leaves them at the http front page of our site using RHEL4. ReWrite rules are logged (switched off here - level 0) if required.

* Plone 3.0.4 runs on port 8080 - it does not talk https, Plone runs behind apache which uses port 80 and 443. The modifications needed to make apache talk to Plone are:

* /etc/httpd/conf/httpd.conf # Modifications

NameVirtualHost 232.45.18.35:80
<VirtualHost 232.45.18.35:80>
ServerName site.ac.uk
ProxyPass /
http://localhost:8080/VirtualHostBase/http/site.ac.uk:80/Plone/VirtualHostRoot/
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteLog "/var/log/httpd/rewrite_log"
RewriteLogLevel 0
# Redirect to https if the __ac cookie is set
RewriteCond ${HTTPS} "!=on"
RewriteCond %{HTTP_COOKIE} __ac=
RewriteRule ^(.*) https://%{SERVER_NAME}$1 [NE,L]
# Rewrites the came_from in the URL
RewriteCond %{QUERY_STRING} came_from=http(.*)
RewriteRule ^/(.*)login_form$
https://site.ac.uk/$1login_form?came_from=https%1 [NE,L]
RewriteRule ^/login_(.*) https://site.ac.uk/login_$1 [NE,L]
</IfModule>
</VirtualHost>

* /etc/httpd/conf.d/ssl.conf # Modifications - bottom of tag <virtual></virtual>

ProxyPass / http://localhost:8080/VirtualHostBase/https/site.ac.uk:443/Plone/VirtualHostRoot/

RewriteEngine On
RewriteLog "/var/log/httpd/rewrite_log"
RewriteLogLevel 0
# Logs user out of https upon logout
RewriteRule ^/(.*)logged_out http://www.site.ac.uk/ [L]

Needed

Posted by Eric Smith at August 15, 2006 - 22:33

Because if you got the login form via plain HTTP, there's no way to tell that it's the real login form. It could do anything with the user-entered credentials.

Secure https using IE

Posted by Mike Takahashi at May 30, 2006 - 16:12

The method described for "Secure https" works great with Firefox, however I have found for some strange reason in testing that with IE (tested with v6.0) it does not work. After login, the user is still directed back to http. This was using Plone 2.1.2 and Zope 2.8.6.

What?

Posted by Reinout van Rees at May 30, 2006 - 18:23
I don't understand. (a) the user is supposed to end up back in http, so that's OK. (b) IE doesn't simply rewrite URLs or so. If the action points at an (https) url, it goes there.

Re: What?

Posted by Mike Takahashi at May 30, 2006 - 19:24

Sorry, this was my mistake as I just realized. Your implementation redirects back to http once the user is logged in. I was looking for an implementation to allow the user to stay in https once logged in.

I was able to find a solution. Plone assigns to the variable “came_from” the browsers HTTP_REFERER.

came_from request/came_from|request/HTTP_REFERER|nothing;

When a user accesses the login_form, the came_from field is http. By changing this to https, you can enable Plone to keep the user in https once logged in by modifying the following line in login_form

Re: What?

Posted by Mike Takahashi at May 30, 2006 - 19:27

Modify the attributes field in the came_from input field:

tal:attributes="value python:came_from.replace(http://, https://)" />

httpslogin Product

Posted by Daniel Nouri at August 10, 2006 - 12:57

We at Zest Software created a Product called httpslogin that implements this HOWTO. All that's left to do for you is to install the SessionCrumbler product and set the number of sessions higher in your zope.conf, just like described in the HOWTO. The rest is handled by the Product. Requires Plone 2.5 and Zope 2.9: http://plone.org/products/httpslogin

Get the latest version from the collective and report bugs to either here or Zest Software: https://svn.plone.org/svn/collective/httpslogin/trunk

PAS

Posted by Justin Ryan at August 21, 2006 - 16:54

How do we do this with PlonePAS? I'm not sure it is correct for this how-to to say it works with any version. ;)

adapted it

Posted by Reinout van Rees at August 22, 2006 - 11:34
You're right. I changed the version to 2.0/2.1. I expect to get it working with with PAS somewhere in september/early october. It'll be in that product Daniel mentioned. A client of us needs it :-)

Use SessionAuthHelper

Posted by Jan Hackel at November 2, 2006 - 11:18
For my installation of Plone 2.5 the PAS SessionAuthHelper seems to do the desired. Just add it to your acl_users folder and activate all responsibilities (Reset, Update, Extraction) for it. Then remove the responsibility "Update" from the CookieAuthHelper plugin.

The latter step prevents CookieAuthHelper from storing the base64-encoded credentials in a cookie (which makes the name cookie auth a bit misleading...).

Thanks!

Posted by Justin Ryan at November 7, 2006 - 13:56
This did work. Is there any reason not to remove or entirely disable the cookie auth helper? I assume this allows legacy logins to continue working smoothly. After a few days, it seems inevitable that these will all expire, so would it make sense to disable cookie auth? This way, if someone has stolen credentials and doesn't know how to de-encode them, they won't be able to blindly re-use them.

Any kid can tell you that a stream containing authentication credentials may be reusable via trial-and-error.

Odd..

Posted by Justin Ryan at November 7, 2006 - 14:14
Hm, I'd feel better if I fully understood the chain here. When I said "it works", i meant, "i could still log out and back in", however, when I refreshed a url in another window of my browser, I was provided a login from credentials_cookie_auth. I changed the url so that it got a login form from credentials_session_auth, reloaded, and logged in, and this seems to have "worked" more persistently, but I haven't had a chance to dig through my cookies and see what exactly is happening. Now that I check a bit, I can't reproduce this behaviour, and when I log in, I am presented with a login form, though I am shown as logged in and can manually load my came_from url.

I seem to have wreaked havoc after the initial successful test by trying to raise the relevance of the session auth helper in relation to the cookie auth helper. removing and re-adding the plugin improved things a bit, but it's still behaving erratically and i can't get into some areas, such as two or three pages deep in a configlet, at all.

NO!

Posted by Justin Ryan at November 8, 2006 - 16:13
DO NOT DO THIS, IT HAS HOSED MY PAS AND I CANNOT GET BACK TO DEFAULT FUNCTIONALITY.

Sorry for yelling, it's important. Any site the session_auth_helper touches goes down the drain and only respects zmi logins.

For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by