Drupal Security Best Practices When Outputting Text Into HTML

When creating a module in Drupal it is very important to be aware of some security best practices when outputting text into HTML. This helps prevent XSS (Cross Site Scripting) exploits and keeps your code in general good health as it prevents problems with user input like angle brackets or ampersands.

Be sure to read the documentation for db_query() on how to use the database API securely.

When passing plain-text from the user to HTML markup, you need to pass it through the check_plain() function first. Drupal’s check_plain() converts quotes, ampersands and angle brackets into entities. This causes the string to be shown in a literal way on the browser screen.

There are a few themeable functions that automatically sanitize text by first passing it through check_plain(). They are: t() , menu items and breadcrumbs, Block descriptions (‘but not titles’), theme(‘ plain-text placeholder’), theme_username() and Drupal Form API #default_value element and #options element when the type is a select box.

Examples:


        $form['safe_way'] = array(
          '#type' => 'textfield',
          '#default_value' => $user_supplied,
        );

        $form['not_safe_way'] = array(
          '#type' => 'select',
          '#default_value' => 0, //FORM API will pass this through 
                                 //check_plain(),
          '#options' => node_get_types('names'),  // DANGER: FORM 
                                 // API will NOT sanitize the  
                                 // '#options' attribute with 
                                 // check_plain().
        );
?>

As a Drupal module developer, it is important to commit to memory some common places where sanitizing plain text is important.

Setting the page title:
Examples:



        drupal_set_title($node->title);//BAD, XSS vulnerability
        drupal_set_title(check_plain($node->title));// Correct way
        //NOTE: The same applies to block titles that are 
        //      passed through hook_block().

?>

Form elements #description and #title
Examples:



        $form['wrong_way'] = array(
        '#type' => 'textfield',
        '#default_value' => check_plain($user_supplied),  //←-escaped twice
        '#description' => t("Old data: !data", array('!data' => $user_supplied)), // XSS
        );

        $form['right_way'] = array(
        '#type' => 'textfield',
        '#default_value' => $user_supplied,
        '#description' => t("Old data: @data", array('@data' => $user_supplied)),
        );

?>

Form elements – #options when #type = checkboxes or #type = radios
Examples:



        $form['wrong_way'] = array(
          '#type' => 'checkboxes',
          '#options' => array($user_supplied0, $u_supplied1),
        );

        $form['right_way'] = array(
          '#type' => 'checkboxes',
          '#options' => array(check_plain($user_supplied0), check_plain($user_supplied1)),
        );

?>

Form elements – #value of #type markup and item need to be safe.
Note that the default form element #type is markup!
Examples:



        $form['wrong_way'] = array('#value' => $user->name); //XSS
        $form['right_way'] = array('#value' => check_plain($user->name));
         //       - or -
        $form['right_way'] = array('#value' => theme('username', $user));

?>

This information was gleaned from the Drupal documentation at “Handle text in a secure fashion”, which covers this topic more extensively than this post.

Leave a Reply

Your email address will not be published. Required fields are marked *