7

Does anyone have any idea how to sanitize CSS entered via user input? I am concerned about cross-site scripting via CSS. I am using wp_filter_kses to clean up user entered HTML, but I need a like solution for user entered styles. So far I am using the following ugly and incomplete function but I'd like something more complete.

function sanitizeCSS ( $css ) {
    $css = str_replace( '/-moz-binding/', '', $css );
    $css = str_replace( '/expression/', '', $css );
    $css = str_replace( '/javascript/', '', $css );
    $css = str_replace( '/vbscript/', '', $css );
    return $css; 
}
4
  • What is the context here? Who is entering the CSS? If it's the owner of the site, they can attack their own site via XSS in other ways ...
    – EAMann
    Jun 1, 2012 at 17:31
  • It is the administrator, and I agree, but $_POST could be compromised between what the admin enters, and what actually gets committed to the database. The admin area is the most vulnerable to CSRF.
    – radiok
    Jun 1, 2012 at 17:50
  • @radiok Isn't that partly what nonces are for? Might be better to implement some kind of nonce or CSRF token checking mechanism than to sanitize every posted value.
    – MathSmath
    Jun 1, 2012 at 18:48
  • I do agree, but if you look at something simple, like Blog Name. Nonce's are used on the settings page, then before commit sanitize_text is applied to the value. I'm just trying to live up to the same principles of WordPress core, the problem is, WordPress has no need for CSS sanitization, since there's absolutely no where you can enter your own CSS. You could have a CSS file, but that's not vulnerable in the same way, since you authored that file. I digress, the only complete solution I've found is CSSTidy, but I can't easily incorporate that into a plugin.
    – radiok
    Jun 1, 2012 at 18:54

6 Answers 6

3

You’ll a need a real CSS parser like this one to filter the CSS. Regular expressions or simple string replacements are not safe enough.

2

Jetpack doesn't really sanitize CSS, it just cleans it up, which has the side-effect of removing some vulnerabilities, but not all.

An attacker can still use @import to import unsanitized CSS; expression() to introduce XSS to older IE browsers; @charset to trick the browser into interpreting the CSS as HTML, opening up XSS opportunities; set URLs with arbitrary protocols to launch SSRF attacks, etc.

If you want full sanitization, you'll need to do something like WordCamp.org's css-sanitization.php.

You should run your sanitization against comprehensive test cases to verify that it's cleaning everything.

CSSTidy isn't really abandoned, though; one of the original authors forked the PHP class, and the Jetpack team and others have made contributions to keep it reasonably up to date. If you don't feel comfortable with it, though, Caja is another option.

1

I use the following function that combines the strength of wp_filter_nohtml_kses and then restores the required escape characters, namely quotes and the > selector.

    function escape_css($css) {
    $escaped_css = strtr(wp_filter_nohtml_kses($css),
            [
                '>' => '>',
                "\'"   => "'",
                '\"'   => '"',
    ]);
    return $escaped_css;
}
0

The best approach I found was to incorporate CSSTidy into my plugin. I took a page from JetPack which has a Custom CSS feature.

JetPack's CSSTidy implementation is contained in the Jetpack_Safe_CSS class as function, filter_attr. I couldn't entirely figure out why JetPack was nesting the user entered CSS inside a 'div' element prior to parsing, but after some trial and error I learned that CSSTidy isn't really capable of parsing a CSS file. That lesson learned I literally ripped off the code from JetPack.

I downloaded CSSTidy, created a directory to contain the CSSTidy PHP files. I then require class.csstidy.php as necessary in my code and initialize a new csstidy object. Nest the CSS to be validated in a 'div' element, parse it through CSSTidy, then yank the plain CSS back out of the csstidy object. So far so good with thousands of installs (although I'll never know how many users actually use the CSS feature of my plugin).

I may not have noted this is my original question, but I was looking for the simplest, easiest approach, not the most technical, sophisticated, or powerful approach. I value modifying the WordPress experience as little as possible. This is why I turned to JetPack as the closest thing to "source". I consider JetPack nearly canonical as it is developed by Automattic (the developers of WordPress itself). My only... regret?... is that CSSTidy was abandoned in 2007, so I imagine I will need to find another solution at some point.

require_once( 'csstidy/class.csstidy.php' );
$csstidy = new csstidy();
$csstidy->set_cfg( 'remove_bslash', FALSE );
$csstidy->set_cfg( 'compress_colors', FALSE );
$csstidy->set_cfg( 'compress_font-weight', FALSE );
$csstidy->set_cfg( 'discard_invalid_properties', TRUE );
$csstidy->set_cfg( 'merge_selectors', FALSE );
$csstidy->set_cfg( 'remove_last_;', FALSE );
$csstidy->set_cfg( 'css_level', 'CSS3.0' );
$csstovalidateindiv = 'div {' . $csstovalidate . '}';
$csstovalidateindiv = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $csstovalidateindiv );
$csstovalidateindiv = wp_kses_split( $csstovalidateindiv, array(), array() );
$csstidy->parse( $csstovalidateindiv );
$csstovalidateindiv = $csstidy->print->plain();
$csstovalidateindiv = str_replace( array( "\n", "\r", "\t" ), '', $csstovalidateindiv );
preg_match( "/^div\s*{(.*)}\s*$/", $csstovalidateindiv, $matches );
if ( !empty( $matches[1] ) ) $cssvalidated = $matches[1];

(code or it didn't happen)

1
  • 2
    CSStidy, do not secure the CSS, just ensures it is valid. CSS can be used to request external resource which might be used to executed JS via SVG for example. Aug 6, 2017 at 7:30
0
wp_filter_nohtml_kses()

doesn't appear to work properly because it escapes the " character, so you can't write any attribute selectors. It seems like there is no "simple" way to do this.

-1

Well, if you want the 50mm-barrel gun approach, you could use wp_filter_nohtml_kses(), but there may be better ways.

2
  • I don't think that would remove potentially bad css like -moz-binding, but I will I will have to look harder at that function's code.
    – radiok
    Jun 1, 2012 at 18:55
  • WordPress might not have a CSS-specific sanitization function. You might have to roll your own. Jun 1, 2012 at 19:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.