I’ve been really enjoying working with Tim Bray, Pete Lacey, Elias Torres and Sam Ruby on improving AtomPub in WordPress. This work is in WordPress 2.3, which will be released later this month. You can try it out right now by downloading the beta. Sam has also started some documentation on AtomPub in WordPress at http://codex.wordpress.org/AtomPub.
There is a lot of ground to cover in the post so to start with I want to distinguish between two topics that are closely related, but for our purposes today are also separate and distinct from each other. The first is authentication, specifically HTTP Basic Authentication. The second is security, which will focus on SSL/TLS (i.e. using https:// URLs).
To start with, the AtomPub spec has a section on Securing the Atom Publishing Protocol that deals with authentication. In general, you can use nothing or what ever you want, but HTTP Basic Authentication with TLS needs to be able to work. Think of it as HTTP Basic Authentication being the lowest common denominator that AtomPub clients and servers have to support, along with TLS if you’d like.
In WordPress there are actually two ways that a user could be authenticated when using AtomPub, HTTP basic and cookies. The cookie mechanism just looks to see if you sent along an authenticated WordPress cookie with your request. Since we’d been using Tim’s Atom Protocol Exerciser (APE) for testing, all authentication was being done via HTTP basic. Which worked fine, most of the time.
I started running APE against WordPress running under different situations and I ran into a problem with authentication when PHP was being run as a CGI under Apache. When running as a server module (mod_php) PHP takes care of decoding HTTP basic for you (see HTTP basic authentication in PHP). When a using HTTP basic PHP will automatically populate $_SERVER[‘PHP_AUTH_USER’] and $_SERVER[‘PHP_AUTH_PW’] variables with the username and password that were provided. IF and ONLY IF PHP is being run as a server module (like mod_php). If you are running PHP as a CGI then those two variables won’t get created at all, ever, even when using HTTP basic authentication. And since you can’t do anything in WordPress via AtomPub without authenticating you are dead in the water. Well, not exactly.
PHP not supporting HTTP basic auth when being run as a CGI is a known issue, so folks have come up with clever work ways to work around this. One common work around is to use mod_rewrite to add HTTP basic auth into $_SERVER[‘HTTP_AUTHORIZATION’]:
RewriteEngine on RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
The idea here is that mod_rewrite watches for an HTTP basic auth attempt and then injects the HTTP header in to the PHP environment as HTTP_AUTHORIZATION. From there is it an easy job of parsing and decoding the HTTP header and manually populating $_SERVER[‘PHP_AUTH_USER’] and $_SERVER[‘PHP_AUTH_PW’] yourself. This is currently being done in the WordPress AtomPub code, so if you are on a host that runs PHP as a CGI and you have access to .htaccess and mod_rewrite then you can try it out.
Unfortunately I’ve seen times where this doesn’t work either. A modified version of this that I’ve had better success with is to pass the authentication back in via GET. Here’s an example from a test WordPress blog that redirects AtomPub authentication:
RewriteEngine on RewriteBase /test/atompub/ RewriteCond %{HTTP:Authorization} !^$ RewriteRule wp-app.php wp-app.php?HTTP_AUTHORIZATION=%{HTTP:Authorization} [QSA,L]
Instead of parsing and decoding from $_SERVER[‘HTTP_AUTHORIZATION’] you would do it from $_GET[‘HTTP_AUTHORIZATION’]. This isn’t exactly ideal either, but I’ve had better luck getting it to work in PHP as a CGI environments. Code to support this isn’t in WordPress AtomPub yet, but we might add it.
The four of us went back and forth on this a bit then Tim Bray asked the elephant in the room question: why doesn’t PHP support HTTP basic when running as a CGI? I didn’t have a good answer for him, so I went hunting on Google. It turns out that this has nothing to do with PHP, it is how Apache works. Apache does not pass the HTTP basic headers to CGI applications, so they never see them. This has been mentioned in several places, for brevity I’ll only quote one, from Jon Udell talking about CGI and mod_perl:
“Note that such a module has complete access to the HTTP headers sent by the client. If you write a CGI script to enforce a security policy, à la the ByteCal example above, that script will normally see only the user’s name (HTTP_REMOTE_USER) and not the full credentials (HTTP_AUTHORIZATION).
That’s because Apache, as a security measure, withholds the Authorization header from CGI scripts. (If you really want to build a CGI-based access-control script, you can tweak Apache to make it send this header.) But an Apache/Perl authentication module, running inside the server, knows everything that Apache knows about a request.”
So far I’ve used WordPress and AtomPub as an example, but this problem is not specific to either. This is an issue with CGI applications being able to use HTTP basic authentication, and the ways people have worked around it. While there are ways to deal with this (like the two I mentioned above), they aren’t ideal and only work if you can use .htaccess and mod_rewrite.
There have been lots of alternatives to authentication that get around this issue. Lots of people have looked at this, hopefully we’ll have a generalized way of dealing with this at some point. Until then it looks like we’ll see API specific variations of authentication.
Ok, I also mentioned that we’d talk about security. This one is more to the point, if you aren’t using SSL/TLS then your communications aren’t secure. Although HTTP basic doesn’t send your plain text password and username, it is the next best thing (base64 encoded). So anyone with access to your traffic (wireless network sniffing anyone?) can easily grab your username and password. So how do you secure this authentication process? By doing it over SSL/TLS. If your web traffic isn’t using SSL/TLS it isn’t secure.
In the context of WordPress there is a trade off here. We can’t guarantee that every WordPress install is going to support SSL/TLS, so we can’t make it a requirement. That said, there is nothing in WordPress (or the APIs: AtomPub and XML-RPC) that prevent you being able to use SSL/TLS. This leaves it up the person running the WordPress blog to decide what level of security is needed.
On WordPress.com we support TLS/SSL. You can point your XML-RPC client at https://<your_blog_here>.wordpress.com/xmlrpc.php and it will encrypt the data back and forth between your computer and WordPress.com servers. Same for AtomPub, only the URL would look like https://<your_blog_here>.wordpress.com/wp-app.php.
Hopefully everyone takes away two things from this. One, you can’t depend on HTTP basic authentication working. Two, if you aren’t using SSL/TLS then your traffic isn’t secure.
32 replies on “HTTP Basic Authentication, A Tale of AtomPub, WordPress, PHP, Apache, CGI and SSL/TLS”
Interesting bit about the security info being stripped from CGI. The same is true for Java servlets on the handful of servlet servers I’ve used; if you’ve requested basic auth for the servlet, the server will do the checks for you, and all you get is the userid. The actual auth header is stripped from the list of incoming headers. Safe. Easy. Unless you’d like to bypass the typically byzantine security junk in your servlet server and do it yourself. Because then you’re screwed.
There are probably was around this on a per-servlet server basis, but who wants to deal with that, if you’re building a ‘portable’ servlet that can run in multiple servlet servers.
Maybe a servlet doesn’t have access to the Authorization HTTP request-header field, but servlet Filters do; and you most probably have access to the web.xml if you can do servlets.
Alfresco uses a filter to do HTTP Basic authentication for their WebDAV or “REST services” (WebScripts) entry points.
See https://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/HEAD/root/projects/remote-api/source/java/org/alfresco/repo/webdav/auth/AuthenticationFilter.java
With regard to “Unfortunately I’ve seen times where this doesn’t work either”, you may want to try to have PHP also look for REDIRECT_HTTP_AUTHORIZATION — depending on the Apache setup and mod_rewrite. That should make the GET hack unneccessary.
I can understand the reasoning behind not passing on auth headers to CGI scripts by default, but it’s pretty amazing that there isn’t a more elegant solution to it than abusing mod_rewrite. The fact that you need either .htaccess or mod_php support to enable this means that those with CGI + no .htaccess won’t be able to run AtomPub. We’ll just have to wait for mod_atom to become a part of the default Apache httpd configuration with over 90% market penetration, then, I guess. 😉
I’ve been dealing with this for a couple of years, and my solution has generally been to put the auth credentials as CGI arguments, which is safe enough as long as you’re going over SSL, and has the advantage (over cookie-based schemes) that it’s easy to construct URLs for scripts to take advantage of, giving easy automated interaction.
The trouble with the rewrite scheme outlined here is that if you have made the choice to write your server as a CGI script, then you benefit from the great advantage that you can just tell people “Put this script in your cgi-bin directory”.
There’s no need for them to configure their webserver – and indeed their webserver doesn’t have to be Apache, it can be any webserver that supports the CGI standard (wasn’t that the point of having a CGI standard?)
Nevertheless, it’s a nice trick which I hadn’t seen before.
Oh – and an addendum.
If you’re running on Apache, you can force the use of TLS by checking the existence and value of the CGI variable HTTPS. (I don’t do PHP, but I’m guessing this is $_SERVER[’HTTPS’]) and refusing to go any further if it’s not set appropriately.
I’ve been going through some similar pain lately. My project is just a simple site-wide login backed by LDAP that will keep the username and password secure (not encrypting all traffic). Basic Authentication really is a big let down. HTTPS isn’t a viable option in my situation so I’ve turned to brewing my own variation of authentication using a client-side RSA algorithm I found and the server-side PEAR RSA module. They didn’t play nice at first but I’m really close to a good solution.
What about WSSE?
http://www.xml.com/pub/a/2003/12/17/dive.html
Even if you don’t keep track of which nonces you’ve seen, it’s still somewhat more secure than Basic.
@Joe Cheng
The problem with WSSE is that it requires the server to store a plain text version of the password. For WordPress this is definitely a problem because it doesn’t support doing that, which isn’t likely to change.
I think this is also a bad general idea, no matter who you are.
Maybe I’m just being dense here, but why would you check a user’s credentials twice? In your example, it looks like you’re doing a check for username and password match once in Apache during the HTTP Basic auth, then again in WP (whether running in a CGI or mod_php context).
Personally, I consider it a Bad Thing that that mod_php-hosted PHP code is given access to the original password used in the basic auth step by default. Once you’ve authenticated someone, passing their password around through the rest of the application stack seems to me to be a fairly open invitation for information leakage.
You may want the password to authenticate as the user against another resource. Otherwise you may end up asking the user for a password again.
If PHP_AUTH_USER is not populated, $_SERVER[‘REMOTE_USER’] should be populated as that’s a standard Apache environment variable.
You will also see this behaviour if the PHP installation is configured in “safe mode”, see http://www.php.net/manual/en/features.safe-mode.functions.php (safe mode is removed in PHP 6)
The Apache behaviour existed in NCSA.
[…] NEW LINK joseph.randomnetworks.com/…/http-basic-authentication-…-php-apache-cgi-and-ssltls/ […]
[…] I hadn’t read Joe Cheng’s comment or Joseph Scott’s reply in the comments of the post I linked to above before I started off on […]
[…] PHP as a CGI […]
The situation above leads to people coding their own authentication systems, which can have gigantic holes like WordPress’: http://lwn.net/Articles/259204/
[…] neither the “HTTP Authentication with PHP” suggestion the codex links to or Joseph Scott’s get hack […]
I consider it a Bad Thing that that mod_php-hosted PHP code is given access to the original password used in the basic auth step by default.
@ #17 –
If you are going to authenticate using HTTP Basic then it must have access to it.
I never bother with cookies. SSL is the way to go. The only drawback to SSL is the speed issue, as encryption is obviously more intensive
I have cookie problem with safari in wordpress 🙂
I also have the cookie problem with my wordpress…
The basic authentication scheme is based on the model that the
client must authenticate itself with a user-ID and a password for
each realm. The realm value should be considered an opaque string
which can only be compared for equality with other realms on that
server. Another scheme is digest access. The Digest Access Authentication scheme is not intended to be a complete answer to the need for security in the World Wide Web. This scheme provides no encryption of message content. The intent is simply to create an access authentication method that avoids the most serious flaws of Basic authentication.
i think cookie is common problem in safari
I don’t trust on HTTP basic authentication working in wordpress.
[…] challenge.Solution: Provide a separate URL for the service document, probably /atom/service.PHP as a CGIWhen PHP is running as a CGI the HTTP Authorization header is not passed on to the script. Currently […]
I don’t think HTTP authentication in wordpress is a good idea. I’m sticking with the original cookies/session.
Do all secure sites use SSL/TSL?
These two solutions are displayed everywhere you look for an answer to this problem – but I have never got either one to work on my host (Dreamhost).
Ditto’ing Stephen – been hacking wp-app and .htaccess all day – unable to get it working on Dreamhost.
[…] And there’s a spoiler: it can’t be done :)A WSSE client will send an Authorization header which, as we know, will get dropped if Apache is passing the request off to a CGI, and a X-WSSE header that looks […]
Someone wrote that the RewriteRule method of passing the Http Basic authorization string to the CGI application is “abusing” mod_rewrite. I disagree. Just glance at the mod_rewrite docs — it was obviously meant for much more than just URL aliasing, and method described above is a trivial usage of the module. Don’t expect Apache to change its CGI security behavior to make the password available by default, because such a change is likely to make many existing scripts less secure and that would be irresponsible. A line or two of configuration for those who need it is not so bad in comparison.
“….Apache does not pass the HTTP basic headers to CGI applications, so they never see them…”
So how do we work around this, for those of us who don’t have access to an IIS server?
I can’t get it figured out on my WP blog, and we i=even use WP as a CMS for iother types of web sites.
Can you please give me more inof on this matter? Thanks! I really appreciate it.
“I have cookie problem with safari in wordpress” did any one have any more info on this topic? would be appreciated :~}