Removing PHP Inputs from Drupal

30 November -0001

Drupal is a complex, PHP based, content management system. Like most content management systems, Drupal allows users to easily and conveniently input and manage dynamic content for websites. While most common usages of Drupal involve manipulation of text and HTML based input, Drupal allows users to write and deploy dynamic code via the web interface. This functionality enables users to develop PHP directly through the web based administration, without having to manipulate files on the filesystem, which is the traditional method for developing PHP.

As with any sufficiently complex system, Drupal suffers from a number of security issues. However, Drupal is a relatively mature system and a large number of the code based flaws have been addressed and corrected. These fixes have greatly increased the overall security of Drupal, but a side consequence of the growing complexity of the Drupal system has been an elevation of the risk of insecure configurations to one of the largest concerns facing Drupal deployments. Drupal offers a host of functionality that can be manipulated by malicious users to damage or compromise a Drupal site. Chief among these dangerous features is the ability to write PHP code. Because PHP is executed with the privileges of the web server, and because the web server has extensive privileges such as reading and writing files to the filesystem and access to the database, execution of PHP by malicious users can easily lead to compromise of the web server and then the host operating system.

Drupal's architecture is modular in nature, allowing extensions to be installed to enable new functionality. Drupal also includes a built in access control system consisting of users and groups. Each user is assigned to a group. By default there are two groups on every Drupal system: anonymous users and authenticated users. Additionally each Drupal installation has a default administrative account with a special user id. All users are granted privileges to functionality based on their groups except for the administrative user, which has access to all privileges. When attempting to compromise a Drupal site, attackers will target user accounts that give them the ability to execute PHP code as a precursor to seizing control of the Drupal system. Restricting access to these dangerous permission is essential to thwarting these efforts.

Drupal includes many "core" modules as part of the default installation. One of these is the PHP input filter. This module allows users to write PHP directly into content areas and have that code execute every time the content is accessed. Although this module is included by default (in Drupal 6) it is not necessary for most installations. While many sites simply leave the module in place, without enabling the module or assigning any privileges to it, and assume it is benign. However, by retaining the module the possibility exists for it to be enabled by accident, deliberately by the administrative user, or via trickery (such as with the use of cross site scripting attacks). Removing the module from the filesystem will not negatively impact Drupal systems and ensures that it cannot be enabled. Simply delete the entire directory found under the Drupal modules directory.

Once the PHP module is deleted there are fewer ways to execute PHP code from the administrative interface. However, other core and many third party contributed modules, including many popular modules such as the Block module, the Auto Nodetitle module, the Print and the Webform module, all allow the input and use of additional PHP code as part of their functionality. In order to protect Drupal systems it is wise to disallow the granting of these privileges. Although this makes good policy sense, any user with the 'administer permissions' privilege can change permissions to allow use of these modules' functionality. In order to prevent this situation the Drupal core code must be slightly modified. The core "User" module controls the display of the user privileges grid found by default at the URL ?q=/admin/user/permissions. In the User module, the file user.admin.inc contains the function user_admin_perm() that queries each module installed on the Drupal system for the privileges they offer, and draws out the form of checkboxes displayed to the user. By excluding all permissions that begin with the string "use php" most of the dangerous PHP permissions can be removed from display on the user privileges screen, effectively blocking users from being able to enable these permissions. By applying a conditional restraint in the form "if (stripos($perm, 'use php') === FALSE)" immediately following the foreach check in the rendering of role to permissions overview on line 534 (of Drupal 6.20) it is possible to screen out these dangerous permissions for display. The following patch effects this change:

--- drupal-6.20/modules/user/user.admin.inc	2010-03-01 06:15:38.000000000 -0500
+++ drupal-6.20.patched/modules/user/user.admin.inc	2011-03-10 13:52:05.998763792 -0500
@@ -532,14 +532,16 @@ function user_admin_perm($form_state, $r
       );
       asort($permissions);
       foreach ($permissions as $perm) {
-        $options[$perm] = '';
-        $form['permission'][$perm] = array('#value' => t($perm));
-        foreach ($role_names as $rid => $name) {
-          // Builds arrays for checked boxes for each role
-          if (strpos($role_permissions[$rid], $perm .',') !== FALSE) {
-            $status[$rid][] = $perm;
-          }
-        }
+      	if (stripos($perm, 'use php') === FALSE) {
+	        $options[$perm] = '';
+	        $form['permission'][$perm] = array('#value' => t($perm));
+	        foreach ($role_names as $rid => $name) {
+	          // Builds arrays for checked boxes for each role
+	          if (strpos($role_permissions[$rid], $perm .',') !== FALSE) {
+	            $status[$rid][] = $perm;
+	          }
+	        }
+      	}
       }
     }
   }

This patch effectively eliminates most of the problematic permissions from modules. It is important to note that many modules, including the popular Ctools module, allow for PHP input as part of their functionality but actually use other modules' permissions for access (such as "Use PHP for block visibility" from the core Block module).

While this patch does prevent enabling the aforementioned dangerous permission through the web based interface it is still trivially possible to enable permissions via the database. Drupal permissions are stored as a long string the "permissions" table, and appending a permission to that string grants permissions. Additionally, removing permissions from display does not prevent the administrative account from using the screened functionality. Unfortunately the only effective way to completely remove the functionality is to remove the code from modules that provide these features directly. This becomes unmanageable over time given the rapid pace of upgrade in most modules' lifecycle. Altering the Drupal core is a much more direct, and feasible approach given the much slower pace of upgrade releases for Drupal core.