Important Update for Deny List Rebuilding

Community Auth has had a feature for some time that locks a person from attempting to login if they exceed a defined amount of login attempts. When that person waits 10 minutes, or however long you configure the waiting time, the person can then attempt to login once again. This works great, but if somebody bypasses the form and attempts to login via a script, Community Auth will at some point attempt to deny access to the IP address where those requests are coming from.

This feature may or may not be all that useful, but as it was before v3.1.0, a bug in the regular expression (regex) that rebuilds the deny list could render a site completely unusable. The condition leading to such a case would be brought about by an .htaccess file that is of medium to large filesize.

The Bad Regex

 * Location: community_auth/models/Auth_model.php
 * Near line 582 for Community Auth < v3.1.0
$pattern = '/(?<=# BEGIN DENY LIST --)(.|\n)*(?=# END DENY LIST --)/';

There are a few problems with this regex. It is using a greedy quantifier, the quantifier is inside a capturing group, and the PCRE library will smash the stack trying to remember everything. This leads to preg_replace outputting an empty string without any warning.

Some Better Regex

$pattern = '/(?<=# BEGIN DENY LIST --).*?(?=# END DENY LIST --)/s';

Here I’ve added a question mark, making the quantifier non-greedy, removed the capturing group, and used the s flag instead of .|\n. This regex can now handle much larger input, and this is the quick fix you need to apply if you don’t want to update to v3.1.0.

Rebuilding the Deny List Without preg_replace

The best solution may not even include a regular expression and preg_replace at all. Assuming we will always have our deny list at the top of our .htaccess file, Community Auth now splits the contents of the .htaccess file using PHP’s explode function. Everything after “# END DENY LIST –” is “captured” in a simple array element, and the freshly rebuilt deny list is prepended to it. There are other slight modifications to the method, so here is the current version:

 * From Community Auth v3.1.0 - May 21, 2016
protected function _rebuild_deny_list()
	// Get all of the IP addresses in the denied access database
	$query_result = $this->get_deny_list('ip_address');

	// If we have denials
	if( $query_result !== FALSE )
		// Create the denial list to be inserted into the Apache config file
		$deny_list = '<Limit GET POST>' . "\n" . 'order deny,allow';

		foreach( $query_result as $row )
			$deny_list .= "\n" . 'deny from ' . $row->ip_address;

		$deny_list .= "\n" . '</Limit>' . "\n# END DENY LIST --\n";

	// Else we have no denials
		$deny_list = "# END DENY LIST --\n";

	// Get the path to the Apache config file
	$htaccess = config_item('apache_config_file_location');


	// Store the file permissions so we can reset them after writing to the file
	$initial_file_permissions = fileperms( $htaccess );

	// Change the file permissions so we can read/write
	@chmod( $htaccess, 0644);

	// Read in the contents of the Apache config file
	$string = read_file( $htaccess );

	// Remove the original deny list
	$arr = explode( 'END DENY LIST --', $string );

	// Add the new deny list to the top of the file contents
			"# BEGIN DENY LIST --\n" . $deny_list . "\n" . trim( $arr[1] ) . "\n";

	// Write the new file contents
	if ( ! write_file( $htaccess, $string ) )
	     die('Could not write to Apache configuration file');

	// Change the file permissions back to what they were before the read/write
	@chmod( $htaccess, $initial_file_permissions );