views:

290

answers:

2

I am working on a site and I would like to make a user able to enter custom CSS into that will be publicly displayed.

However, seeing as a good deal of XSS attacks can be preformed through CSS, I would like to be able to find a way to "clean" the CSS output, similar to how HTML Purifier works, by parsing the CSS, running the parsed CSS against a whitelist, and then outputting a new stylesheet based on the parsed and whitelisted CSS.

Is there already a library like this out there? If not, is there a CSS parsing library that can be used to create a custom implementation?

A: 

It may be a hack depending on the scale of the system, but a simple regex for "http://" (or "url" or even just ":") should prevent any XSS attacks. There's no XSS if you don't allow the "X".

anschauung
No, there's still ways that don't require the URL thing (such as nasty IE-only things), but not allowing URLs would prevent useful things like external background images from being used.
MiffTheFox
True, and fair enough. I'll go ahead and delete the answer if someone else downvotes. But, you're not going to avoid XSS if you allow external background images: there's nothing stopping someone from having "http://foo.com/background.png" actually run a script.
anschauung
Assuming they control "foo.com, of course.
anschauung
Or, heck, even "background-image: url('http://foo.com/nasty-nasty.aspx')" I think URLs are more your problem than CSS. Even if you parse are re-form the CSS, the meat of the solution will come to evaluating the URLs as much as the CSS components.
anschauung
What harm would come from allowing a background-image from there, if the URL is over HTTP, the browser should throw it out if it's not an image, right?
MiffTheFox
While we're in the domain of "crazy non-standard things one can do in IE" a lot is possible. At very least, one could be use passive XSS like logging views, IP addresses, etc. If you're committed to allowing external URLs, you've opened up a challenge that's much larger than CSS alone, and a CSS parser won't do much for you on that front.
anschauung
I see then. I'll keep some of that in mind. However, I still need a way to sanatise the rest of the CSS.
MiffTheFox
+2  A: 

I guess you're going to write your own CSS parser and filter, so here's what I'd consider, although I've never done such a thing:

  • Make a (white) list of acceptable CSS properties that your users can use. Like: color, font-family.
  • I believe it might be better to not allow short-hand forms such as background, at least in the beginning, so that you can easily parse the values. Require that they explicitly write background-color, background-image.
  • If you want URLs, only allow relative URLs, and discard everything that doesn't even closely look like a URL. Log these issues anyway, so that you can improve your parser and validator.
  • Be very strict in your parsing, discard everything that your parser doesn't understand even if it would be valid CSS. In other words, make your own CSS subset.

When parsing, the hardest part would be the parsing of complex CSS selectors. But you can impose your own subset here too.

Here's some (pseudo)code, maybe it will help you somehow:

<?php

function tokenizeCSS() {
    return array(
        array(
            'selector'   => '#foo .bar',
            'properties' => array(
                'background-color' => 'transparent',
                'color'            => '#fff',
            ),
        );
    );
}

function colorValidator($color)
{}

/**
 * This is basically the white list. Keys are accepted CSS properties
 * and values are the validator callbacks.
 */
$propertyValidators = array(
    'background-color' => 'colorValidator',
    'color'            => 'colorValidator',
);

$filteredRules = array();

foreach (tokenizeCSS() as $rule) {
    if (! validSelector($rule['selector'])) {
        continue;
    }

    foreach ($rule['properties'] as $property => $value) {
        /**
         * Check property is in white list
         */
        if (! isset($propertyValidators[$property]) {
            continue;
        }

        /**
         * Check property is valid
         */
        if (! $propertyValidators[$property]($value)) {
            continue;
        }

        /**
         * Valid rule
         */
        $filteredRules[$rule['selector']][$property] = $value;
    }
}
Ionuț G. Stan