In computer file systems, files and directories have a set of permissions assigned to them that specify who can Read, Write, or eXecute each file.
This permissions system is one of the basic concepts that provide security for your website. A default WordPress installation comes with permissions settings for its files and folders (i.e. directories) that can be regarded as very secure. Utilizing your server's preexisting permission/umask setting determines the most secure permissions that still allow access.
However, in some cases there can be a trade-off between security and functionality: Some WordPress plugins require more lenient security settings for directories they read from or write to in order to work properly.
There are plugins that provide uploading, editing and managing image files for WordPress. It writes to and reads from a base image directory which can be set up in the plugin's options panel. This directory needs to be writeable by the process running the php/server (chmod 777) in order to work properly on some server installations. However, any directory where permissions have been set to '777' present a (real) security hole: a malicious visitor could upload a script to that directory and hack your site.
The suEXEC feature provides Apache users with the ability to run CGI and SSI programs under user IDs different from the user ID of the calling web server. Normally, when a CGI or SSI program executes, it runs as the same user who is running the web server.
Used properly, this feature can considerably reduce the security risks involved with allowing users to develop and run private CGI or SSI programs. However, if suEXEC is improperly configured, it can cause any number of problems and possibly create new holes in your computer's security. If you aren't familiar with managing setuid root programs and the security issues they present, we highly recommend that you not consider using suEXEC.
How can you secure your WordPress installation while still enjoying the extended functionality that WordPress plugins provide?
One possible solution to this problem is provided by .htaccess. You can add a .htaccess file to any directory that requires lenient permissions settings (such as 760, 766, 775 or 777). You can prevent the execution of scripts inside the directory and all its sub-directories. Also, you can prevent any files other than those of a certain type to be written to it.
The following snippet of code prevents any files other than .jpeg, .jpg, .png, .webp, or .gif to be served from the directory:
Order Allow,Deny <FilesMatch "\.(jpe?g|png|gif|webp)$"> Allow From All </FilesMatch>
This example uses the FilesMatch directive to specifically deny the following types of files from being accessed. This could be used to prevent PHP files from running in the uploads directory.
<FilesMatch "\.(php|exe|flv|cgi)$"> Deny from All </FilesMatch>
The following code helps prevent executable scripts like .pl, .cgi or .php scripts from being executed when requested by a browser. This instructs the Web Server to treat them as text files instead of executables. The result is they will be displayed as plain text inside the browser window:
AddType text/plain .pl .cgi .php
The Options -ExecCGI directive is one of the more powerful directives allowed in .htaccess files. This directive controls what is allowed in .htaccess files by all the other Apache modules. The -ExecCGI specifies that NO files that are registered to be handled by the cgi-script handler are allowed. The AddHandler directive on the next line registers all those file extensions as CGI scripts, thus making any attempts to access them results in a 403 Forbidden - Access is Denied message.
Options -ExecCGI AddHandler cgi-script .php .pl .py .jsp .asp .htm .shtml .sh .cgi
Finally, you can use one more directive to force the type of the document different than the handler. This directive removes all handlers and actions normally associated with these extensions and forces them to be used as text/plain, but it does not override the previous example in scope.
<FilesMatch "\.(php|pl|py|jsp|asp|htm|shtml|sh|cgi)$"> ForceType text/plain </FilesMatch>
By using the AddHandler and Action directives below, we configure Apache to set the REDIRECT_STATUS environment variable. The reason is, when a request is made for a file ending in .php, Apache doesn't just serve the file, instead it serves the file to the /cgi-bin/php.cgi script, which can either be a real php-cgi interpreter, or it could just be a shell script that executes your real php interpreter.
AddHandler php-cgi .php Action php-cgi /cgi-bin/php.cgi
This sets an environment variable PHPRC, then executes the php.cgi file.
#!/bin/sh -p export PHP_FCGI_CHILDREN=3 export PHPRC=/home/custom-ini exec /home/bin/php.cgi
This example just executes the php interpreter (if found) located in the current path of the executing script owner.
#!/bin/sh -p exec php
We can use this information to lock down htaccess directories, files, and even requests with this one simple variable. It is possible because the REDIRECT_ CGI environment variables are only set for local requests. Remember, it is Apache that is requesting the /cgi-bin/php.cgi file, so that is defined as a local request. If someone requests a webpage that ends in .php, the REDIRECT_ variable is only set for Apache when it transfers control over to the /cgi-bin/php.cgi file. Therefore, you can block ALL requests to the /cgi-bin/php.cgi file that do not have the REDIRECT_STATUS variable set.
This variable is created from an internal request and was created originally (it's much older than even php, it's from CGI) to be used to process ErrorDocuments. An ErrorDocument like a 404 page' is triggered by a user caused action like requesting a non-existent page, but then it is Apache that redirects the request to the ErrorDocument, just like it redirected requests for .php files above. This feature enables ErrorDocuments to be aware of the environment settings and variables from the request that caused the error. REDIRECT_STATUS is just one of the many REDIRECT_ variables. Basically, all safe variables get passed to the redirected script prefixed with REDIRECT_.
Since we now know that we only want requests that have the REDIRECT_STATUS environment variable set, we can issue a 403 Forbidden to anything else. You can place this in your /cgi-bin/.htaccess file.
Order Deny,Allow Deny from All Allow from env=REDIRECT_STATUS
Combined Access with FilesMatch
This can go in your /.htaccess file and uses regex to apply to php[0-9].(ini|cgi)</tt>
<FilesMatch "^php5?\.(ini|cgi)$"> Order Deny,Allow Deny from All Allow from env=REDIRECT_STATUS </FilesMatch>
Only Deny REDIRECT_STATUS Not 200
You may also use mod_rewrite's power to further tighten the access by only allowing for redirects with a 200 Status code. This could come into play if your default ErrorDocuments are themselves php scripts. An
ErrorDocument 403 /error.php
will have a REDIRECT_STATUS itself of 403.
Denying Requests with mod_rewrite
RewriteEngine On RewriteCond %{ENV:REDIRECT_STATUS} !=200 RewriteRule /cgi-bin/path/to/php - [F]