TinyMCE:Security
From Moxiecode Documentation Wiki
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.
Other PHP classes that mimic TinyMCE's filtering capabilities: List other here.
