4.5. Common Mistakes with Users and Permissions

Now that you understand how to create and check permissions properly, let's look at some common mistakes related to permissions. Many of the problems that exist in Drupal are commonly repeated mistakes. Sometimes the code is simply copied from one module to another. In other cases people make the same incorrect assumptions about the way the code works. By highlighting these common mistakes, it should be easier for you to avoid both these examples and other problems in other situations:

  • You will learn about a common mistake in creating menu items and upgrading modules from Drupal 5.x to 6.x.

  • You'll learn about how improper use of the permission system can lead to improper configurations of a site.

  • You'll learn about a common mistake with the function for sending users an access-denied page.

  • You'll see how Drupal code can perform actions as different users without accidentally creating a privilege escalation.

4.5.1. Insufficient or Incorrect Menu Access

The hook_menu examples you looked at in the last section show how to correctly use the access callback and access arguments attributes, but module developers do occasionally get these wrong. This has particularly been a problem in the upgrade from Drupal 5.x to 6.x, where the menu system changed a bit.

For 5.x, the menu definition would include the function and arguments for the path as a single array element for the access parameter:

'access' => user_access('uninstall plugins'),

As of Drupal 6.x, there are two significant changes:

  • First, menus no longer inherit security from a parent menu item, so they must be set explicitly. An addition to Drupal core early in the 6.x life cycle ensured that all menu items define their own access to secure against missing definitions.

  • Second, they are split apart from the one access element into the two elements for callback and arguments. A developer who doesn't pay close attention here is likely to make a mistake like this:

'access callback' => user_access('uninstall plugins'),
Instead the code should be upgraded as:
'access arguments' => array('uninstall plugins'),

A quick search through the contributed modules on your site may reveal weaknesses like this. You can quickly check them by logging out of your site and then visiting the page defined by that menu item as an anonymous user or as a user with lower privileges than should be necessary for the item. If you gain access to the page when logged out, then it is a weakness. In Chapter 10, you will learn more about how to search for weaknesses, and in Chapter 10, you will see how to properly report them.

4.5.2. Overloading a Permission

When a module developer creates a module, she has to strike a balance in defining permissions. If she creates too many, it can overwhelm users. The other extreme is to create no new permissions and instead rely on the site-wide administer site configuration, which is one of the most powerful permissions on a site. In general, the administer site configuration permission should be reused for small modules or modules where the control needs to be given to only very powerful users. Another best practice is to create a separate permission for any activities related to administration of features that could be used to take control of a server, such as file uploads, command execution, output filtering, and PHP execution.

Weaknesses with overloaded permissions are generally more difficult to exploit. You have to find a site that has the module installed, gain an account on the site, and then probe for the misconfiguration. That said, a site that is totally misconfigured and allows anonymous users to perform the actions can often be found via a search engine. Again, this will be covered more thoroughly in Chapter 9.

4.5.3. Access Definitely Denied

One common action on a site is to declare that access has been denied for a particular request or action. In the browser, this appears as an "Access denied" message and an HTTP status code of 403 to let the browser know that there was a problem. If you were writing your own code, you would have to create the specific HTTP headers and some content to send to the user. In Drupal there is a convenience function called drupal_access_denied that handles that for you.

The menu system is one common place where this function is called. If you can, you should use the access elements of the menu item array so that the menu system handles this for you. There are, however, situations where it is more convenient or more appropriate to call drupal_access_denied in your own code.

Where possible, use a custom access callback and access arguments in the menu definition so that the access check is handled in the menu system. Then you won't have to worry about properly exiting when access is denied.


Menus that take multiple arguments are common situations where writing an access callback to catch all of the scenarios is difficult. One example of this is the profile_browse function from the core Profile module. It allows visitors to look at lists of users organized into groups based on the data in their profile fields. This function includes the following code:

if (!user_access('administer users') &&
     ($field->visibility == PROFILE_PRIVATE ||
    $field->visibility == PROFILE_HIDDEN)) {
  drupal_access_denied();
  return;
}

Note how right after the drupal_access_denied function the code executes a return. A common misconception is that drupal_access_denied is a complete solution that will stop the code from executing further. However, drupal_access_denied can be used in situations where further processing is necessary, so it is not possible for it to simply stop processing with a call to exit, for example. Instead your code must be written in a way that after the call to drupal_access_denied the normal flow of execution stops and only code appropriate for the access-denied situation is executed.

4.5.4. Acting as Another User—and Getting Stuck

It's possible in Drupal for code to behave as another user on the site. This is a useful feature when code needs to temporarily escalate the permissions of a user to take an action or to have some actions on a site attributed to a "robot" instead of the user who is visiting the pages. The code to do so looks something like this:

global $user;
$current_user = $user;

$user = user_load(array('uid' => 1));
action_as_another_user();
$user = $current_user;

This code does the following:

  • It brings the global $user object into scope.

  • It saves that object into a temporary variable called $current_user.

  • It loads the user 1 account (administrator account) into the $user object so that any code that runs next will execute as though user 1 were performing the actions.

  • At this point the custom code runs—for this example the code is inside the function action_as_another_user.

  • Finally the user object gets set back to the temporary value.

What happens if there is an error inside the action_as_another_user function? What happens if code is called that breaks the normal code flow and exits? The user will then be logged in to the site with the permission of user 1 and be able to do whatever he wants.


The Vulnerable module contains an example of this problem. To demonstrate the problem, log in to your site as a user other than user 1 and visit the page vulnerable/session-switcher, where you should get an error message: "Fatal error: Call to undefined function action_as_another_user()." Depending on your site configuration, the message may be written to a log file instead of the screen. If you then refresh the home page of your site, you will see two messages like those in Figure 4-2, which show how the user object has changed. You should also note that you are now logged in as user 1 with access to user 1's account and all of the administration pages.

Figure 4.2. The Vulnerable module alerting about user changes

To protect against this, Drupal's session-handling code provides the function session_save_session, which keeps track of changes like this and saves the $user into the session data only if it is set to TRUE. Here is the safer implementation of the previous code:

global $user;
$current_user = $user;
session_save_session(FALSE);
$user = user_load(array('uid' => 1));
action_as_another_user();
$user = $current_user;
session_save_session(TRUE);

There are several required conditions to exploit this weakness:

  • Code that loads the $user object and changes it to another user

  • The ability to halt the flow of processing before the $user object gets set back

  • Code that fails to use, or improperly uses, the session_save_session function

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.97.202