TinyMCE:Security

From Moxiecode Documentation Wiki

Jump to: navigation, search

Contents

How to prevent XSS if the User Disables JavaScript

There are a lot of examples that describe how to configure TinyMCE. However, there is hardly anything on this wiki that describes overall security concepts. For example, let's say you are in a LAMP environment (Linux, Apache, MySQL, PHP) and you have a PHP script that uses TinyMCE to add comments to a MySQL-based bulletin board.

You first create a page that has a FORM tag. This FORM contains a TEXTAREA tag where visitors type their text for inclusion on the bulletin board. TinyMCE uses JavaScript to dynamically replace the TEXTAREA with a TinyMCE editor. TinyMCE has extensive security features designed to strip unwanted HTML tags and tag attributes from any user-supplied text. The question is: what happens if the user disables JavaScript?

Modern browsers, like FireFox, allow users to disable JavaScript entirely. A visitor to your bulletin board can simply disable JavaScript and bypass all of TinyMCE's security features.

Secondly, hackers and spammers can write scripts hosted on their own servers that submit bogus data to your FORM's target destination. Thus completely bypassing TinyMCE's security features.

Thirdly, TinyMCE does not protect against MySQL injection attacks.

Solutions For "JavaScript is Disabled"

Use JavaScript to create the TEXTAREA tag. If a visitor has disabled JavaScript, the TEXTAREA will not exist.

Quick-and-Dirty method:

This script will write a textarea to the browser. Most browsers support it, but it is not DOM-compliant. This code will display a message if scripting is turned off. Change the name of the field to match your application.

<script language="javascript" type="text/javascript">
<!--
  document.writeln ('<textarea name="your_content_field_name"></textarea>');
//-->
</script>
<noscript>
  The editor requires scripting to be enabled.
</noscript>

DOM-Compliant Method

This method is more "future-proof," as it complies with the Document Object Model for cross-browser compatibility. Make sure to substitute the appropriate field name for your form. You will also want to adjust the parameters as you see fit. You can place the "content_placeholder" block anywhere in the page, as long as the script is called after it is read by the browser.

<span id="content_placeholder"></span>
<script language="javascript" type="text/javascript">
  with (document.getElementById ("content_placeholder")) {
    with (appendChild (document.createElement ("TEXTAREA"))) {
      name = "your_content_field_name";
      cols = 80;
      rows = 25;
      value = "Place default content here.";
    }
  }
//-->
</script>
<noscript>
  The editor requires scripting to be enabled.
</noscript>
<!--

Set a Style

This method only prevents the textarea from spilling raw code on the page if scripting is disabled. It does not prevent the form from being submitted, so it is more a cosmetic improvement than a security enhancement.

  <textarea name="your_content_field_name" style="visibility:hidden"></textarea>

Solutions For "Data submitted form off-site"

According to your hypothetical example. You created this web page on a LAMP based system. You will need to employ some sort of PHP-based solution to determine if the data submitted to the FORM's target originated from your form. Regardless, the safest method for handling information sent to you from TinyMCE is to filter it. For example, lets suppose you have the following TinyMCE configuration. Please pay close attention to the extended_valid_elements configuration options. This is where we define what tags and tag attributes are allowed.

Generally, you want to avoid JavaScript related attributes like onmouseover and onclick. The style attribute can also be abused to create invisible links, or invisible paragraphs that take up the whole page. Combine this with onclick, and anyone clicking anywhere on your bulletin board could be whisked away to some adult-oriented site.


<script type="text/javascript">
<!--//
tinyMCE.init({
    mode : "specific_textareas",
    editor_selector : "mceEditor",
    theme : "advanced",
    content_css : "/style/editor_content.css",
    plugins : "spellchecker,fullscreen,searchreplace,preview,print,save,table",
    table_cell_limit : 100,
    table_row_limit : 10,
    table_col_limit : 10,
    convert_fonts_to_spans : false,
    inline_styles: false,
    fix_table_elements : true,
    fix_list_elements : true,
    relative_urls : false,
    convert_urls : false,
    spellchecker_languages : "+English=en",
    theme_advanced_buttons1 : "preview,print,save,fullscreen,separator,image,separator,"+
                              "spellchecker,separator,tablecontrols ",
    theme_advanced_buttons2 : "code,separator,bold,italic,underline,strikethrough,"+
                              "charmap,forecolor,backcolor,separator,bullist,numlist,undo,redo",
    theme_advanced_buttons3 : "removeformat,formatselect,justifyleft,justifycenter,"+
                              "justifyright,justifyfull,outdent,indent,separator,"+
                              "link,unlink,separator,justifyleft,justifycenter,justifyright,separator,"+
                              "cut,copy,paste,separator,search,replace,separator",
    theme_advanced_toolbar_location : "top",
    theme_advanced_toolbar_align : "left",
    theme_advanced_path_location : "bottom", 
    extended_valid_elements : 
        "a[href|rel|rev|target|title|type]," +
        "b[],"+
        "blink[],"+
        "blockquote[align|cite|clear|height|type|width],"+
        "br[clear],"+
        "caption[align|height|valign|width],"+
        "center[align|height|width],"+
        "col[align|bgcolor|char|charoff|span|valign|width],"+
        "colgroup[align|bgcolor|char|charoff|span|valign|width],"+
        "comment[],"+
        "em[],"+
        "font[color|face|font-weight|point-size|size],"+
        "h1[align|clear|height|width],"+
        "h2[align|clear|height|width],"+
        "h3[align|clear|height|width],"+
        "h4[align|clear|height|width],"+
        "h5[align|clear|height|width],"+
        "h6[align|clear|height|width],"+
        "hr[align|clear|color|noshade|size|width],"+
        "img[align|alt|border|height|hspace|src|vspace|width],"+
        "li[align|clear|height|type|value|width],"+
        "marquee[behavior|bgcolor|direction|height|hspace|loop|scrollamount|scrolldelay|vspace|width],"+
        "ol[align|clear|height|start|type|width],"+
        "p[align|clear|height|width],"+
        "pre[clear|width|wrap],"+
        "s[],"+
        "small[],"+
        "span[align],"+
        "strike[],"+
        "strong[],"+
        "sub[],"+
        "sup[],"+
        "table[align|background|bgcolor|border|bordercolor|bordercolordark|bordercolorlight|"+
               "bottompadding|cellpadding|cellspacing|clear|cols|height|hspace|leftpadding|"+
               "rightpadding|rules|summary|toppadding|vspace|width],"+
        "tbody[align|bgcolor|char|charoff|valign],"+
        "td[abbr|align|axis|background|bgcolor|bordercolor|"+
           "bordercolordark|bordercolorlight|char|charoff|headers|"+
           "height|nowrap|rowspan|scope|valign|width],"+
        "tfoot[align|bgcolor|char|charoff|valign],"+
        "th[abbr|align|axis|background|bgcolor|bordercolor|"+
           "bordercolordark|bordercolorlight|char|charoff|headers|"+
           "height|nowrap|rowspan|scope|valign|width],"+
        "thead[align|bgcolor|char|charoff|valign],"+
        "tr[align|background|bgcolor|bordercolor|"+
           "bordercolordark|bordercolorlight|char|charoff|"+
           "height|nowrap|valign],"+
        "tt[],"+
        "u[],"+
        "ul[align|clear|height|start|type|width]",
    fullscreen_settings : {
        theme_advanced_path_location : "top"
    }
});
// -->
</script>

You have a pretty secure installation of TinyMCE. Unfortunately, all of this can be bypassed. Therefore, you need to create a secure backend, in our case, we are using PHP. Your destination script should filter out all the same baddies that TinyMCE does. This is duplication of effort, but it is needed. The following PHP snippet uses the PHP Input Filter Class [1] created by Daniel Morris.


<?php
//------------------------------------------------------------------------------------
// strip bad html, javascript, and troublesome html tag attributes 
//------------------------------------------------------------------------------------
/* 
  please note that if TinyMCE also strips out bad tags. This is a backup should someone 
  try and post from off-site, or someone somehow defeates TinyMCE. This list comes 
  directly from TinyMCE allowed_extended_attributes config setting. The list has been 
  alphabetized. The following is taken directly  from the documentation for the 
  inputFilter PHP class

  [quote]
    
    Instantiate the class with your settings.
    1st (tags array):    Optional (since 1.2.0)
    2nd (attr array):    Optional
    3rd (tags method):   0 = remove ALL BUT these tags (default)
                         1 = remove ONLY these tags
    4th (attr method):   0 = remove ALL BUT these attributes (default)
                         1 = remove ONLY these attributes
    5th (xss autostrip): 1 = remove all identified problem tags (default)
                         0 = turn this feature off
    
    Eg.. $myFilter = new InputFilter($tags, $attributes, 0, 0);

  [/quote]

*/


# if you add a new allowed tag to the TinyMCE config, you have to add it here too.
$aAllowedTags = array("a", "b", "blink", "blockquote", "br", "caption", "center", "col", "colgroup", "comment", 
                      "em", "font", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "img", "li", "marquee", "ol", "p", "pre", "s",
                      "small", "span", "strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", 
                      "thead", "tr", "tt", "u", "ul");


# of you add a new allowed attribute to the TinyMCE config, you must add it here too.
$aAllowedAttr = array("abbr", "align", "alt", "axis", "background", "behavior", "bgcolor", "border", "bordercolor", 
                      "bordercolordark", "bordercolorlight", "bottompadding", "cellpadding", "cellspacing", "char", 
                      "charoff", "cite", "clear", "color", "cols", "direction", "face", "font-weight", "headers", 
                      "height", "href", "hspace", "leftpadding", "loop", "noshade", "nowrap", "point-size", "rel", 
                      "rev", "rightpadding", "rowspan", "rules", "scope", "scrollamount", "scrolldelay", "size", 
                      "span", "src", "start", "summary", "target", "title", "toppadding", "type", "valign", 
                      "value", "vspace", "width", "wrap");


# the following two lines strip away bad tags, attributes, and cross site scripts
$oMyFilter = new InputFilter($aAllowedTags, $aAllowedAttr, 0, 0, 1);
$tTinyMceTextArea = $oMyFilter->process($_POST['tTinyMceTextArea']);

# $tTinyMceTextArea is now untainted. You can trust it.
?>

Please note that the above PHP code snippet is only part of a complete PHP script that you must create.

Update: The PHP Input Filter class has not been updated to allow proper protection against all modern XSS attacks (See these comments). Popular alternatives include:

Solutions for Python users

If you have lxml installed, you can use lxml.html.clean. Incidentally, lxml is also the best-performing HTML/XHTML/XML manipulation library for Python by a large margin.

Personal tools