I'm about 2 weeks away from publishing the upgrade for the WordPress .htaccess security plugin, and haven't had time to post for awhile as I've been doing mad research for the plugin, which is probably going to set the net on fire once its released... but today I spent all day doing work for one of my longtime security clients, and I thought I'd share some of the .htaccess tricks I used to increase the security.
The site runs on a privately hosted linux server and is setup to be as secure as possible in many different ways. The site is a private forum with sensitive information, and needs to balance the highest amount of security possible without causing the many worldwide members any discomfort.
This is all new, experimental, and very very cool. It literally uses .htaccess techniques to create several virtual "locked gates" that require a specific key to unlock, in a specific order that cannot be bypassed. The cool thing about this new experimental (but working flawlessly for 12+hours now) is that it uses whitelisting ideas to specify exactly what is allowed, instead of trying to specify everything that isn't allowed. That specificity is also used to add additional layers of security onto the request at each virtual gate. Also, by setting specific cookies/tokens after successfully passing through a gate, we can then require the exact cookie/token from the previous gate, which stops an attacker from skipping or bypassing gates. Finally, this tiered approach allows all users who successfully authenticate all the way to the server to be able to bypass these gates for all further requests, and using nonce values and time-sensitive keys, we can specify the amount of time before a user has to do it all over again.
I started out with a simple upgrade from HTTP Basic Authentication to HTTP Digest Authentication, which provides much greater security.. But I noticed that several of the various Browsers out there (like Google Chrome!) do NOT follow the RFC 2617 Specifications which cause a host of problems for the users running them. The problem is bad programming on the part of the browser programmers.. Internet Explorer (IE) is the worst offender, but that is no surprise and even expected as its MS, but Google Chrome? I wondered how Google was able to program an entire browser so fast...It appears they took shortcuts.
Without getting technical, the problem is how the bad browsers handle GET requests for resources that contain a query string like /login.php?THIS_IS=a_query_string
.. The bad browsers compute the hash for the request using the query string, but the auth header request-uri doesn't have the query string, so the hash doesn't match and the authentication failes.
The solution I came up with was to turn Digest Authentication off for everything except favicon.ico
This works because if the client successfully authenticates using Digest Authentication for the file favicon.ico
, we can use .htaccess to set a special secret cookie that can be checked later to verify the Digest Auth was successful... Here's how this security handles any request to the server, without getting to technical:
favicon.ico
which then forces Digest Authentication. If the digest authentication is successful then the server sets a special cookie to bypass this check for all future requests in the session.All of this is done by the server using built-in apache security. The only non-server part is the last cookie that is set upon successful login into the phpBB forum, that cookie is set in the code with a small modification I made to 1 phpBB core file.
The only thing different when logging into this server from the users perspective, is the additional HTTP Digest User/Pass that they will need. Other than that they just login to phpBB as normal.
+----------------+ | client request | +----------------+ | | =========== +HTTPS -=[ Require SSL Encrypted Connection to Continue =========== || || ====================== +LOGIN CREDENTIALS -=[Require Valid Digest User/Pass Authentication to Continue ====================== ||| ||| ==================== +DIGEST COOKIE -=[Set Cookie to Save Digest Auth was successful, Require Cookie to Continue ==================== |||| |||| ====================== +PHPBB CREDENTIALS -=[phpBB Forum Login System, Required to Continue ====================== ||||| ||||| ================== +PHPBB COOKIE -=[Set Cookie to Save success phpBB login, Required for non-login urls ================== |||||| |||||| +----------------+ | server response | +----------------+
Some basics, turning off everything and setting up default document to be used by the unix-dir handler.
Options -Indexes -Includes -ExecCGI -MultiViews DirectoryIndex index.php /priv/index.php
As a workaround to issues with Chrome, MSIE, and a few other browsers, we only require Digest Authentication for the favicon.ico file. This also saves a lot of HTTP Requests and CPU. We use RewriteRule's to force authentication, and then by setting a cookie if the authentication was successful, we can simply check for that cookie elsewhere instead of requiring Digest Auth for every single request.
AuthType Digest AuthName "askapache1" AuthDigestDomain https://www.askapache.com/ / AuthDigestFile /askapache/.htpasswdd require valid-user
This completely forces SSL to be used, http isn't even an option. To keep this from bugging users who type in the address http://
we use the ErrorDocument directive below.
SSLOptions +StrictRequire SSLRequireSSL
By specifying that a 403 forbidden (which SSLRequireSSL sends for non-https requests) should redirect to the login page, users never have errors, and it also fixes the issue of double-password prompts for basic/digest authentication that would otherwise occur here. The /error/ folder is a special folder that uses +IncludesNoExec
to display helpful information about the request and how to contact me if they ever see it.
ErrorDocument 400 /error/400.html ErrorDocument 401 /error/401.html ErrorDocument 403 https://www.askapache.com/priv/ucp.php ErrorDocument 404 /error/404.html ErrorDocument 405 /error/405.html ErrorDocument 500 /error/500.html ErrorDocument 503 /error/503.html
Ok this is what makes it all work. This is the experimental part of this system, as I haven't had time to fully debug it.. so far after 12+hours running, not a single problem.. but I did quite a bit of live http/htts sniffing to tweak it just right. IOW, don't copy and paste this and expect something magical, unless magic to you is hard-to-trace redirect loops, errors, and bizarre user-agent/protocol issues. This is actually pretty crazy... I love it!
The REDIRECT_STATUS
variable is only set after a request is redirected internally, so if its already set.. kill the rewrite processing.
RewriteEngine On RewriteBase / RewriteCond %{ENV:REDIRECT_STATUS} !^$ RewriteRule .* - [L]
Ok as short and simple as this looks, it's actually way serious and complex and at the moment I'm not going to attempt to walk you through it. Like most articles and stuff on this blog, you have to do your own hacking to figure anything out.. partly because I'm not a great writer, but mostly because I spend my time learning new stuff, spelling it out for you makes you stupid, you have to do your own hacking if you want to understand it.
Try to figure out which piece of the below code fits into the chart above...
RewriteCond %{THE_REQUEST} !^[A-Z]{1,4} /favicon.ico HTTP/ RewriteCond %{HTTP_COOKIE} !^.*faviconcheck.*$ RewriteRule .* https://www.askapache.com/favicon.ico? [R,L] RewriteCond %{THE_REQUEST} ^[A-Z]{1,4} /favicon.ico HTTP/ RewriteCond %{HTTP_COOKIE} !^.*faviconcheck.*$ RewriteRule .* https://www.askapache.com/priv/index.php? [CO=faviconcheck:1:www.askapache.com:6400,R] RewriteCond %{HTTP_COOKIE} ^.*phpbb_c3_u=1;.*$ RewriteRule ^cron.php$ - [S=1] RewriteCond %{REQUEST_FILENAME} -d [OR] RewriteCond %{REQUEST_FILENAME} .php RewriteCond %{THE_REQUEST} !^[A-Z]{1,4} /priv/ucp.php.* HTTP/ RewriteCond %{HTTP_COOKIE} !^.*phpbb_c3_u8=keyok;.*$ RewriteRule ^priv.*$ https://www.askapache.com/priv/ucp.php [R=302,L]
See the SetEnvIf directive for more details on conditionally setting environment variables.
Working with MS Internet Explorer
The Digest authentication implementation in previous Internet Explorer for Windows versions (5 and 6) had issues, namely that GET requests with a query string were not RFC compliant. There are a few ways to work around this issue.
The first way is to use POST requests instead of GET requests to pass data to your program. This method is the simplest approach if your application can work with this limitation.
Since version 2.0.51 Apache also provides a workaround in the AuthDigestEnableQueryStringHack environment variable. If AuthDigestEnableQueryStringHack is set for the request, Apache will take steps to work around the MSIE bug and remove the query string from the digest comparison. Using this method would look similar to the following. Using Digest Authentication with MSIE:
BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=OnThis workaround is not necessary for MSIE 7, though enabling it does not cause any compatibility issues or significant overhead.
Every piece of the puzzle above is explained somewhere on this blog.. the search in the top right of this site works great.
As always, please add your comments, especially if you have an idea to make this better or improved in any way..