WordPress has a configuration option that you can set to force administrative sessions to happen over an SSL connection (https). Just add define('FORCE_SSL_ADMIN', true); to your wp-config.php file and you’re set, as long as your web server supports https and it is configured properly. Of course, this means that you must have an SSL certificate for your server.
It turns out that if your SSL certificate is not trusted, attempts to use the Flash uploader to upload files will give you a cryptic “IO error” message. In my case, this I was using Firefox when I first encountered this. My SSL certificate is self-signed, and even though I added an exception for it in Firefox, the Flash uploader still fails.
Here’s what I learned about this problem after banging on it for a few days.
This isn’t a new problem. There are plenty of people who have noticed this behavior with WordPress before. In fact, there are plenty of people who have noticed this issue with SWFUpload, which is the tool that WordPress actually uses to do the Flash uploads. Some of them point to a bug in Adobe Flash Player. However, it’s actually because the SSL certificate on your server isn’t trusted by your operating system for some reason or another.
Flash doesn’t use your web browser to decide whether or not it trusts an SSL certificate. Instead, it asks the operating system. On Windows, you can add your self-signed certificate to the trusted root certificate store (right-click on your .crt file and choose “Install certificate,” and then make sure it ends up in that store). This will make the certificate trusted by default in Internet Explorer, and in any other program that asks Windows if a certificate is OK. Firefox (and other some browsers) maintain their own database of certificates and certificate authorities, but Flash will always check the central Windows list of certificates, no matter which web browser you are using.
(Note that Mac OS X and Linux also have a central location for trusted root certificates, so the same type of thing applies there.)
This is actually reasonable behavior on Flash’s part. Firefox’s “certificate trust list” (or whatever you want to call it) isn’t available via the NAPI plug-in interface, so there’s no way for Flash to check to see if Firefox trusts a given SSL certificate. It can, however, use the Windows API to check to see if the system trusts it. And if an SSL certificate looks suspicious, Flash won’t send data over the https connection. This is what causes the error.
So, how do we fix this problem? There are a few possible solutions.
(1) If you are using a self-signed certificate, add your certificate to your operating system’s list of trusted certificate authorities. If you are using a certificate signed by a non-trusted authority (like cacert.org), add that authority to your operating system’s list of trusted certificate authorities. Of course, this carries a small risk. If your certificate is self-signed, and your private key were compromised, now someone would be able to fake messages that your computer would trust. And if your certificate was signed by a non-trusted CA, now you trust all certificates signed by that CA, which may not be what you want.
Now, this doesn’t help you if you’re in my boat and you want to run multiple WordPress sites on one server. You can get one of them working, but since you have to use the same SSL certificate for all of them (unless you have a unique IP address to run each of them on), any sites that don’t match the site that your certificate as for will still have the error, because the certificate won’t be trusted even if you add it to the list of trusted CAs if the site you are accessing doesn’t match the site in the certificate. In this case, we need a different solution.
(2) Have Flash uploads to your WordPress site not use SSL. Of course, this means that the files you are uploading won’t be encrypted during transmission, but maybe that’s OK with you. I can think of two ways to implement this solution. But here, I am going to begin to descend into the depths of WordPress.
(2a) WordPress plug-in to disable SSL for the Flash uploader.
I have actually completed and published such a plug-in. The file wp-admin/async-upload.php is used to accept uploads from the Flash uploader. If the FORCE_SSL_ADMIN configuration option is set, WordPress will automatically bump you over to an https connection if you try to access this file via http. So the first thing we need to do is keep that from happening:
- Add a filter to ‘admin_url‘ that removes the https from the URL to async-upload.php, whenever it is about to show up in a WordPress admin page.
- Add a filter to ‘wp_redirect‘ so that when WordPress decides that it needs to redirect you to an https session, the redirect is canceled if you are accessing async-upload.php.
- Modify the ‘auth_redirect‘ function so that WordPress does not bail after attempting to redirect you to https, if async-upload.php is the file you are accessing.
Now, async-upload.php can be accessed without using https. There’s another problem, though. WordPress has two cookies that it may use to authenticate you, one of them is referred to as AUTH_COOKIE and used for regular connections, and the other one is referred to as SECURE_AUTH_COOKIE and is used for https connections. The idea is, the SECURE_AUTH_COOKIE will never be sent in the clear, so if you force SSL connections, there is no way that someone can hijack your authentication cookie, even if they managed to grab one from back when you were using a regular connection. (In fact, SECURE_AUTH_COOKIE is marked as for use with secure connections only, so your web browser won’t ever send it with a regular http request.)
Now, Flash doesn’t always send cookies properly, so WordPress packages up the authentication cookie that you are using and sends it along with the Flash upload request as a POST variable. async-upload.php takes the cookie from the POST variable and stores it back in the COOKIE variable where it belongs. However, since our admin session is SSL, but async-upload.php isn’t accessed via SSL, it stores the cookie that was sent in AUTH_COOKIE instead of SECURE_AUTH_COOKIE, and then it fails to authenticate you. So, we need to…
- Modify the ‘wp_parse_auth_cookie‘ function so that WordPress notices the cookie in the POST variable, if you are accessing async-upload.php.
(By the way, this hack sends your SECURE_AUTH_COOKIE along with the regular http request to upload your file, so… so much for never sending it in the clear.)
Almost done. Now, requests to async-upload.php are made by your web browser (not Flash) to view and edit information about the uploaded files, right after the uploads are done. Because the SECURE_AUTH_COOKIE is marked for secure connections only, your browser won’t send it along with these requests, so you won’t be able to view or edit file data right after you upload the files. To work around this, we take the login cookie as acceptable credentials to do this.
- Modify the ‘auth_redirect‘ function to look for the login cookie and accept it as good authentication, if async-upload.php is the file being accessed.
(This means that if someone hijacked your login cookie, they might be able to mess with your file data.)
So, we add a couple of obscure security holes to WordPress and the Flash uploader is working with your bad SSL certificate. Is the tradeoff worth it? That’s up to you.
Oh, but I said I could think of two ways to ditch SSL for the Flash uploader…
(2b) Modify WordPress itself for a more clean workaround.
The ‘auth_redirect‘ and ‘wp_parse_auth_cookie‘ functions mentioned above are both pluggable. However, the functions we’d need to modify for a more clean solution are not. How might a more clean solution work?
Obviously, the files you are uploading with the Flash plug-in won’t be encrypted. However, we’d like anything else that is transmitted unencrypted to be useless in hacking into your site. The SECURE_AUTH_COOKIE should never be sent in the clear, and the login cookie shouldn’t be sufficient credentials for messing with the data associated with your uploaded files.
I know this sounds a little vague, but I have an idea in my head about how it would work. Maybe I will take a stab at implementing it and present it to the WordPress devs, if I ever get the time and motivation.
But it seems like an awful lot of trouble to go through just to work around this edge case. Just fix your SSL certificate!