Attention

This document was written for an unsupported version of Plone, Plone 2.5.x, and was last updated 1601 days ago.

For more information, see the version support policy.

To learn how to upgrade to the current version of Plone, read the upgrade manual.

Secure login without plain text passwords

by Reinout van Rees last modified Dec 30, 2008 03:02 PM
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)


Contribute

Something wrong or out of date? Anybody can edit or create a new article in the knowledge base. Simply create an account on this site, log in, and click the Edit button to contribute.