Path: blob/trunk/third_party/closure/goog/html/sanitizer/htmlsanitizer_test.js
2868 views
// Copyright 2016 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.131415/**16* @fileoverview Unit tests for HTML Sanitizer17*/1819goog.setTestOnly();2021goog.require('goog.array');22goog.require('goog.dom');23goog.require('goog.html.SafeHtml');24goog.require('goog.html.SafeUrl');25goog.require('goog.html.sanitizer.HtmlSanitizer');26goog.require('goog.html.sanitizer.HtmlSanitizer.Builder');27goog.require('goog.html.sanitizer.TagWhitelist');28goog.require('goog.html.sanitizer.unsafe');29goog.require('goog.html.testing');30goog.require('goog.object');31goog.require('goog.string.Const');32goog.require('goog.testing.dom');33goog.require('goog.testing.jsunit');34goog.require('goog.userAgent');353637/**38* @return {boolean} Whether the browser is IE8 or below.39*/40function isIE8() {41return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9);42}434445/**46* @return {boolean} Whether the browser is IE9.47*/48function isIE9() {49return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(10) && !isIE8();50}515253/**54* Sanitizes the original HTML and asserts that it is the same as the expected55* HTML. If present the config is passed through to the sanitizer.56* @param {string} originalHtml57* @param {string} expectedHtml58* @param {?goog.html.sanitizer.HtmlSanitizer=} opt_sanitizer59*/60function assertSanitizedHtml(originalHtml, expectedHtml, opt_sanitizer) {61var sanitizer =62opt_sanitizer || new goog.html.sanitizer.HtmlSanitizer.Builder().build();63try {64var sanitized = sanitizer.sanitize(originalHtml);65if (isIE9()) {66assertEquals('', goog.html.SafeHtml.unwrap(sanitized));67return;68}69goog.testing.dom.assertHtmlMatches(70expectedHtml, goog.html.SafeHtml.unwrap(sanitized),71true /* opt_strictAttributes */);72} catch (err) {73if (!isIE8()) {74throw err;75}76}77if (!opt_sanitizer) {78// Retry with raw sanitizer created without the builder.79assertSanitizedHtml(80originalHtml, expectedHtml, new goog.html.sanitizer.HtmlSanitizer());81// Retry with an explicitly passed in Builder.82var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();83assertSanitizedHtml(84originalHtml, expectedHtml,85new goog.html.sanitizer.HtmlSanitizer(builder));86}87}888990/**91* @param {!goog.html.SafeHtml} safeHtml Sanitized HTML which contains a style.92* @return {string} cssText contained within SafeHtml.93*/94function getStyle(safeHtml) {95var tmpElement = goog.dom.safeHtmlToNode(safeHtml);96return tmpElement.style ? tmpElement.style.cssText : '';97}9899100function testHtmlSanitizeSafeHtml() {101var html;102html = 'hello world';103assertSanitizedHtml(html, html);104105html = '<b>hello world</b>';106assertSanitizedHtml(html, html);107108html = '<i>hello world</i>';109assertSanitizedHtml(html, html);110111html = '<u>hello world</u>';112assertSanitizedHtml(html, html);113114// NOTE(user): original did not have tbody115html = '<table><tbody><tr><td>hello world</td></tr></tbody></table>';116assertSanitizedHtml(html, html);117118html = '<h1>hello world</h1>';119assertSanitizedHtml(html, html);120121html = '<div>hello world</div>';122assertSanitizedHtml(html, html);123124html = '<a>hello world</a>';125assertSanitizedHtml(html, html);126127html = '<div><span>hello world</span></div>';128assertSanitizedHtml(html, html);129130html = '<div><a target=\'_blank\'>hello world</a></div>';131assertSanitizedHtml(html, html);132}133134135// TODO(pelizzi): name of test does not make sense136function testDefaultCssSanitizeImage() {137var html = '<div></div>';138assertSanitizedHtml(html, html);139}140141142function testBuilderCanOnlyBeUsedOnce() {143var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();144var sanitizer = builder.build();145assertThrows(function() {146builder.build();147});148assertThrows(function() {149new goog.html.sanitizer.HtmlSanitizer(builder);150});151}152153154function testAllowedCssSanitizeImage() {155var testUrl = 'http://www.example.com/image3.jpg';156var html = '<div style="background: url(' + testUrl + ');"></div>';157158var sanitizer =159new goog.html.sanitizer.HtmlSanitizer.Builder()160.allowCssStyles()161.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)162.build();163164try {165var sanitizedHtml = sanitizer.sanitize(html);166if (isIE9()) {167assertEquals('', goog.html.SafeHtml.unwrap(sanitizedHtml));168return;169}170assertRegExp(171/background(?:-image)?:.*url\(.?http:\/\/www.example.com\/image3.jpg.?\)/,172getStyle(sanitizedHtml));173} catch (err) {174if (!isIE8()) {175throw err;176}177}178}179180181function testHtmlSanitizeXSS() {182// NOTE(user): xss cheat sheet found on http://ha.ckers.org/xss.html183var safeHtml, xssHtml;184// Inserting <script> tags is unsafe185// Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]186safeHtml = '';187xssHtml = '<SCRIPT SRC=xss.js><\/SCRIPT>';188assertSanitizedHtml(xssHtml, safeHtml);189// removes strings like javascript:, alert, etc190// Image XSS using the javascript directive191// Browser Support [IE6.0|IE8.0|NS8.1-IE]192safeHtml = '<img />';193xssHtml = '<IMG SRC="javascript:xss=true;">';194assertSanitizedHtml(xssHtml, safeHtml);195196safeHtml = '<div><a>hello world</a></div>';197xssHtml = '<div><a target=\'_xss\'>hello world</a></div>';198assertSanitizedHtml(xssHtml, safeHtml);199200safeHtml = '';201xssHtml = '<IFRAME SRC="javascript:xss=true;">';202assertSanitizedHtml(xssHtml, safeHtml);203204safeHtml = '';205xssHtml = '<iframe src=" javascript:xss=true;">';206assertSanitizedHtml(xssHtml, safeHtml);207208// no quotes and no semicolon209// Browser Support [IE6.0|NS8.1-IE]210safeHtml = '<img />';211xssHtml = '<IMG SRC=javascript:alert("XSS")>';212assertSanitizedHtml(xssHtml, safeHtml);213214// case insensitive xss attack215// Browser Support [IE6.0|NS8.1-IE]216safeHtml = '<img />';217xssHtml = '<IMG SRC=JaVaScRiPt:alert("XSS")>';218assertSanitizedHtml(xssHtml, safeHtml);219220// HTML Entities221// Browser Support [IE6.0|NS8.1-IE]222safeHtml = '<img />';223xssHtml = '<IMG SRC=javascript:alert("XSS")>';224assertSanitizedHtml(xssHtml, safeHtml);225226// Grave accent obfuscation (If you need to use both double and single quotes227// you can use a grave accent to encapsulate the JavaScript string)228// Browser Support [IE6.0|NS8.1-IE]229safeHtml = '<img />';230xssHtml = '<IMG SRC=`javascript:alert("foo \'bar\'")`>';231assertSanitizedHtml(xssHtml, safeHtml);232233safeHtml = '<img />';234xssHtml = '<IMG data-xxx=`yyy`>';235assertSanitizedHtml(xssHtml, safeHtml);236237// Malformed IMG tags238// http://www.begeek.it/2006/03/18/esclusivo-vulnerabilita-xss-in-firefox/#more-300239// Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]240safeHtml = '<img />">';241xssHtml = '<IMG """><SCRIPT defer>exploited = true;<\/SCRIPT>">';242assertSanitizedHtml(xssHtml, safeHtml);243244// UTF-8 Unicode encoding245// Browser Support [IE6.0|NS8.1-IE]246safeHtml = '<img />';247xssHtml = '<IMG SRC=javascrip' +248't:alert('XSS'' +249')>';250assertSanitizedHtml(xssHtml, safeHtml);251252// Long UTF-8 Unicode encoding without semicolons (this is often effective253// in XSS that attempts to look for "&#XX;", since most people don't know254// about padding - up to 7 numeric characters total). This is also useful255// against people who decode against strings like256// $tmp_string =~ s/.*\&#(\d+);.*/$1/; which incorrectly assumes a semicolon257// is required to terminate a html encoded string:258// Browser Support [IE6.0|NS8.1-IE]259safeHtml = '<img />';260xssHtml = '<IMG SRC=javasc' +261'ript:al' +262'ert('XS' +263'S')>';264assertSanitizedHtml(xssHtml, safeHtml);265266// Hex encoding without semicolons (this is also a viable XSS attack against267// the above string $tmp_string =~ s/.*\&#(\d+);.*/$1/; which assumes that268// there is a numeric character following the pound symbol - which is not true269// with hex HTML characters). Use the XSS calculator for more information:270// Browser Support [IE6.0|NS8.1-IE]271safeHtml = '<img />';272xssHtml = '<IMG SRC=javascript:' +273'alert('XSS')>';274assertSanitizedHtml(xssHtml, safeHtml);275276// Embedded tab277// Browser Support [IE6.0|NS8.1-IE]278safeHtml = '<img />';279xssHtml = '<IMG SRC="jav\tascript:xss=true;">';280assertSanitizedHtml(xssHtml, safeHtml);281282// Embedded encoded tab283// Browser Support [IE6.0|NS8.1-IE]284safeHtml = '<img />';285xssHtml = '<IMG SRC="jav	ascript:xss=true;">';286assertSanitizedHtml(xssHtml, safeHtml);287288// Embeded newline to break up XSS. Some websites claim that any of the chars289// 09-13 (decimal) will work for this attack. That is incorrect. Only 09290// (horizontal tab), 10 (newline) and 13 (carriage return) work. See the ascii291// chart for more details. The following four XSS examples illustrate this292// vector:293// Browser Support [IE6.0|NS8.1-IE]294safeHtml = '<img />';295xssHtml = '<IMG SRC="jav
ascript:xss=true;">';296assertSanitizedHtml(xssHtml, safeHtml);297298// Multiline Injected JavaScript using ASCII carriage returns (same as above299// only a more extreme example of this XSS vector) these are not spaces just300// one of the three characters as described above:301// Browser Support [IE6.0|NS8.1-IE]302safeHtml = '<img />';303xssHtml = '<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt' +304'\n(\n"\nX\nS\nS\n"\n)\n"\n>';305assertSanitizedHtml(xssHtml, safeHtml);306307// Null breaks up JavaScript directive. Okay, I lied, null chars also work as308// XSS vectors but not like above, you need to inject them directly using309// something like Burp Proxy or use %00 in the URL string or if you want to310// write your own injection tool you can either use vim (^V^@ will produce a311// null) or the following program to generate it into a text file. Okay, I312// lied again, older versions of Opera (circa 7.11 on Windows) were vulnerable313// to one additional char 173 (the soft hypen control char). But the null314// char %00 is much more useful and helped me bypass certain real world315// filters with a variation on this example:316// Browser Support [IE6.0|IE7.0|NS8.1-IE]317safeHtml = '<img />';318xssHtml = '<IMG SRC=java\0script:alert("hey");>';319assertSanitizedHtml(xssHtml, safeHtml);320321// On IE9, the null character actually causes us to only see <SCR. The322// sanitizer on IE9 doesn't "recover as well" as other browsers but the323// result is safe.324safeHtml = isIE9() ? '' : '<span>alert("XSS")</span>';325xssHtml = '<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>';326assertSanitizedHtml(xssHtml, safeHtml);327328// Spaces and meta chars before the JavaScript in images for XSS (this is329// useful if the pattern match doesn't take into account spaces in the word330// "javascript:" -which is correct since that won't render- and makes the331// false assumption that you can't have a space between the quote and the332// "javascript:" keyword. The actual reality is you can have any char from333// 1-32 in decimal):334// Browser Support [IE7.0|NS8.1-IE]335safeHtml = '<img />';336xssHtml = '<IMG SRC="  javascript:alert(window);">';337assertSanitizedHtml(xssHtml, safeHtml);338339// Non-alpha-non-digit XSS. While I was reading the Firefox HTML parser I340// found that it assumes a non-alpha-non-digit is not valid after an HTML341// keyword and therefor considers it to be a whitespace or non-valid token342// after an HTML tag. The problem is that some XSS filters assume that the343// tag they are looking for is broken up by whitespace.344// Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]345safeHtml = '';346xssHtml = '<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';347assertSanitizedHtml(xssHtml, safeHtml);348349// Non-alpha-non-digit part 2 XSS. yawnmoth brought my attention to this350// vector, based on the same idea as above, however, I expanded on it, using351// my fuzzer. The Gecko rendering engine allows for any character other than352// letters, numbers or encapsulation chars (like quotes, angle brackets,353// etc...) between the event handler and the equals sign, making it easier354// to bypass cross site scripting blocks. Note that this also applies to the355// grave accent char as seen here:356// Browser support: [NS8.1-G|FF2.0]357safeHtml = '';358xssHtml = '<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>';359assertSanitizedHtml(xssHtml, safeHtml);360361// Non-alpha-non-digit part 3 XSS. Yair Amit brought this to my attention362// that there is slightly different behavior between the IE and Gecko363// rendering engines that allows just a slash between the tag and the364// parameter with no spaces. This could be useful if the system does not365// allow spaces.366// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]367safeHtml = '';368xssHtml = '<SCRIPT/SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';369assertSanitizedHtml(xssHtml, safeHtml);370371// Extraneous open brackets. Submitted by Franz Sedlmaier, this XSS vector372// could defeat certain detection engines that work by first using matching373// pairs of open and close angle brackets and then by doing a comparison of374// the tag inside, instead of a more efficient algorythm like Boyer-Moore that375// looks for entire string matches of the open angle bracket and associated376// tag (post de-obfuscation, of course). The double slash comments out the377// ending extraneous bracket to supress a JavaScript error:378// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]379safeHtml = '<';380xssHtml = '<<SCRIPT>xss=true;//<<\/SCRIPT>';381assertSanitizedHtml(xssHtml, safeHtml);382383// No closing script tags. In Firefox and Netscape 8.1 in the Gecko rendering384// engine mode you don't actually need the "><\/SCRIPT>" portion of this Cross385// Site Scripting vector. Firefox assumes it's safe to close the HTML tag and386// add closing tags for you. How thoughtful! Unlike the next one, which387// doesn't effect Firefox, this does not require any additional HTML below it.388// You can add quotes if you need to, but they're not needed generally,389// although beware, I have no idea what the HTML will end up looking like once390// this is injected:391// Browser support: [NS8.1-G|FF2.0]392safeHtml = '';393xssHtml = '<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>';394assertSanitizedHtml(xssHtml, safeHtml);395396// Protocol resolution in script tags. This particular variant was submitted397// by Lukasz Pilorz and was based partially off of Ozh's protocol resolution398// bypass below. This cross site scripting example works in IE, Netscape in399// IE rendering mode and Opera if you add in a <\/SCRIPT> tag at the end.400// However, this is especially useful where space is an issue, and of course,401// the shorter your domain, the better. The ".j" is valid, regardless of the402// encoding type because the browser knows it in context of a SCRIPT tag.403// Browser support: [NS8.1-G|FF2.0]404safeHtml = '';405xssHtml = '<SCRIPT SRC=//ha.ckers.org/.j>';406assertSanitizedHtml(xssHtml, safeHtml);407408// Half open HTML/JavaScript XSS vector. Unlike Firefox the IE rendering409// engine doesn't add extra data to your page, but it does allow the410// javascript: directive in images. This is useful as a vector because it411// doesn't require a close angle bracket. This assumes there is any HTML tag412// below where you are injecting this cross site scripting vector. Even though413// there is no close ">" tag the tags below it will close it. A note: this414// does mess up the HTML, depending on what HTML is beneath it. It gets around415// the following NIDS regex: /((\%3D)|(=))[^\n]*((\%3C)|<)[^\n]+((\%3E)|>)/416// because it doesn't require the end ">". As a side note, this was also417// affective against a real world XSS filter I came across using an open418// ended <IFRAME tag instead of an <IMG tag:419// Browser support: [IE6.0|NS8.1-IE]420safeHtml = isIE9() ? '<img>' : '';421xssHtml = '<IMG SRC="javascript:alert(this)"';422assertSanitizedHtml(xssHtml, safeHtml);423424// Double open angle brackets. This is an odd one that Steven Christey425// brought to my attention. At first I misclassified this as the same XSS426// vector as above but it's surprisingly different. Using an open angle427// bracket at the end of the vector instead of a close angle bracket causes428// different behavior in Netscape Gecko rendering. Without it, Firefox will429// work but Netscape won't:430// Browser support: [NS8.1-G|FF2.0]431safeHtml = '';432xssHtml = '<iframe src=http://ha.ckers.org/scriptlet.html <';433assertSanitizedHtml(xssHtml, safeHtml);434435// End title tag. This is a simple XSS vector that closes <TITLE> tags,436// which can encapsulate the malicious cross site scripting attack:437// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]438safeHtml = '';439xssHtml = '</TITLE><SCRIPT>alert(window);<\/SCRIPT>';440assertSanitizedHtml(xssHtml, safeHtml);441442// Input Image.443// Browser support: [IE6.0|NS8.1-IE]444safeHtml = '<input type="IMAGE" />';445xssHtml = '<INPUT TYPE="IMAGE" SRC="javascript:alert(window);">';446assertSanitizedHtml(xssHtml, safeHtml);447448// Body image.449// Browser support: [IE6.0|NS8.1-IE]450safeHtml = '';451xssHtml = '<BODY BACKGROUND="javascript:alert(window)">';452assertSanitizedHtml(xssHtml, safeHtml);453454// BODY tag (I like this method because it doesn't require using any variants455// of "javascript:" or "<SCRIPT..." to accomplish the XSS attack).456// Dan Crowley additionally noted that you can put a space before the equals457// sign ("onload=" != "onload ="):458// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]459safeHtml = '';460xssHtml = '<BODY ONLOAD=alert(window)>';461assertSanitizedHtml(xssHtml, safeHtml);462463// IMG SYNSRC.464// Browser support: [IE6.0|NS8.1-IE]465safeHtml = '<img />';466xssHtml = '<IMG DYNSRC="javascript:alert(window)">';467assertSanitizedHtml(xssHtml, safeHtml);468469// IMG LOWSRC.470// Browser support: [IE6.0|NS8.1-IE]471safeHtml = '<img />';472xssHtml = '<IMG LOWSRC="javascript:alert(window)">';473assertSanitizedHtml(xssHtml, safeHtml);474475// BGSOUND476safeHtml = '';477xssHtml = '<BGSOUND SRC="javascript:alert(window);">';478assertSanitizedHtml(xssHtml, safeHtml);479480// & Javascript includes481// Browser support: netscape 4482safeHtml = '<br size="&{alert(window)}" />';483xssHtml = '<BR SIZE="&{alert(window)}">';484assertSanitizedHtml(xssHtml, safeHtml);485486// Layer487// Browser support: netscape 4488safeHtml = '';489xssHtml = '<LAYER SRC="http://ha.ckers.org/scriptlet.html"></LAYER>';490assertSanitizedHtml(xssHtml, safeHtml);491492// STYLE sheet493// Browser support: [IE6.0|NS8.1-IE]494safeHtml = '';495xssHtml = '<LINK REL="stylesheet" HREF="javascript:alert(window);">';496assertSanitizedHtml(xssHtml, safeHtml);497498// List-style-image. Fairly esoteric issue dealing with embedding images for499// bulleted lists. This will only work in the IE rendering engine because of500// the JavaScript directive. Not a particularly useful cross site scripting501// vector:502// Browser support: [IE6.0|NS8.1-IE]503safeHtml = '<ul><li>XSS</li></ul>';504xssHtml = '<STYLE>li {list-style-image: url("javascript:alert(window)");}' +505'</STYLE><UL><LI>XSS';506assertSanitizedHtml(xssHtml, safeHtml);507508// VBscript in an image:509// Browser support: [IE6.0|NS8.1-IE]510safeHtml = '<img />';511xssHtml = '<IMG SRC=\'vbscript:msgbox("XSS")\'>';512assertSanitizedHtml(xssHtml, safeHtml);513514// Mock in an image:515// Browser support: [NS4]516safeHtml = '<img />';517xssHtml = '<IMG SRC="mocha:[code]">';518assertSanitizedHtml(xssHtml, safeHtml);519520// Livescript in an image:521// Browser support: [NS4]522safeHtml = '<img />';523xssHtml = '<IMG SRC="livescript:[code]">';524assertSanitizedHtml(xssHtml, safeHtml);525526// META (the odd thing about meta refresh is that it doesn't send a referrer527// in the header - so it can be used for certain types of attacks where you528// need to get rid of referring URLs):529// Browser support: [IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]530safeHtml = '';531xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0;url=' +532'javascript:alert(window);">';533assertSanitizedHtml(xssHtml, safeHtml);534535// META using data: directive URL scheme. This is nice because it also doesnt536// have anything visibly that has the word SCRIPT or the JavaScript directive537// in it, because it utilizes base64 encoding. Please see RFC 2397 for more538// details or go here or here to encode your own. You can also use the XSS539// calculator below if you just want to encode raw HTML or JavaScript as it540// has a Base64 encoding method:541// Browser support: [NS8.1-G|FF2.0] [O9.02]542safeHtml = '';543xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,' +544'PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">';545assertSanitizedHtml(xssHtml, safeHtml);546547// META with additional URL parameter. If the target website attempts to see548// if the URL contains "http://" at the beginning you can evade it with the549// following technique (Submitted by Moritz Naumann):550safeHtml = '';551xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=' +552'javascript:alert(window);">';553assertSanitizedHtml(xssHtml, safeHtml);554555// IFRAME (if iframes are allowed there are a lot of other XSS problems as556// well):557// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]558safeHtml = '';559xssHtml = '<IFRAME SRC="javascript:alert(window);"></IFRAME>';560assertSanitizedHtml(xssHtml, safeHtml);561562// FRAME (frames have the same sorts of XSS problems as iframes):563// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]564safeHtml = '';565xssHtml = '<FRAMESET><FRAME SRC="javascript:alert(window);"></FRAMESET>';566assertSanitizedHtml(xssHtml, safeHtml);567568// TABLE (who would have thought tables were XSS targets... except me, of569// course):570// Browser support: [IE6.0|NS8.1-IE] [O9.02]571safeHtml = isIE9() ? '<table><div></div></table>' : '<table></table>';572xssHtml = '<TABLE BACKGROUND="javascript:alert(window)">';573// TODO(danesh): Investigate why this is different for IE9.574assertSanitizedHtml(xssHtml, safeHtml);575576// TD (just like above, TD's are vulnerable to BACKGROUNDs containing577// JavaScript XSS vectors):578// Browser support: [IE6.0|NS8.1-IE] [O9.02]579// NOTE(user): original lacked tbody tags580safeHtml = '<table><tbody><tr><td></td></tr></tbody></table>';581xssHtml = '<TABLE><TD BACKGROUND="javascript:alert(window)">';582assertSanitizedHtml(xssHtml, safeHtml);583584// TD (just like above, TD's are vulnerable to BACKGROUNDs containing585// JavaScript XSS vectors):586// Browser support: [IE6.0|NS8.1-IE] [O9.02]587safeHtml = '<div></div>';588xssHtml = '<DIV STYLE="background-image: url(javascript:alert(window))">';589assertSanitizedHtml(xssHtml, safeHtml);590591// DIV background-image plus extra characters. I built a quick XSS fuzzer to592// detect any erroneous characters that are allowed after the open parenthesis593// but before the JavaScript directive in IE and Netscape 8.1 in secure site594// mode. These are in decimal but you can include hex and add padding of595// course. (Any of the following chars can be used: 1-32, 34, 39, 160,596// 8192-8.13, 12288, 65279):597// Browser support: [IE6.0|NS8.1-IE]598safeHtml = '<div></div>';599xssHtml = '<DIV STYLE="background-image: url(javascript:alert(window))">';600assertSanitizedHtml(xssHtml, safeHtml);601602// DIV expression - a variant of this was effective against a real world603// cross site scripting filter using a newline between the colon and604// "expression":605// Browser support: [IE7.0|IE6.0|NS8.1-IE]606safeHtml = '<div></div>';607xssHtml = '<DIV STYLE="width: expression(alert(window));">';608assertSanitizedHtml(xssHtml, safeHtml);609610// STYLE tags with broken up JavaScript for XSS (this XSS at times sends IE611// into an infinite loop of alerts):612// Browser support: [IE6.0|NS8.1-IE]613safeHtml = '';614xssHtml = '<STYLE>@im\port\'\ja\vasc\ript:alert(window)\';</STYLE>';615assertSanitizedHtml(xssHtml, safeHtml);616617// STYLE attribute using a comment to break up expression (Thanks to Roman618// Ivanov for this one):619// Browser support: [IE7.0|IE6.0|NS8.1-IE]620safeHtml = '<img />';621xssHtml = '<IMG STYLE="xss:expr/*XSS*/ession(alert(window))">';622assertSanitizedHtml(xssHtml, safeHtml);623624// Anonymous HTML with STYLE attribute (IE6.0 and Netscape 8.1+ in IE625// rendering engine mode don't really care if the HTML tag you build exists626// or not, as long as it starts with an open angle bracket and a letter):627safeHtml = '<span></span>';628xssHtml = '<XSS STYLE="xss:expression(alert(window))">';629assertSanitizedHtml(xssHtml, safeHtml);630631// IMG STYLE with expression (this is really a hybrid of the above XSS632// vectors, but it really does show how hard STYLE tags can be to parse apart,633// like above this can send IE into a loop):634// Browser support: [IE7.0|IE6.0|NS8.1-IE]635safeHtml = isIE9() ? 'undefined' : 'exp/*<a></a>';636xssHtml = 'exp/*<A STYLE="no\\xss:noxss("*//*");xss:ex/*XSS*//*' +637'/*/pression(alert(window))">';638assertSanitizedHtml(xssHtml, safeHtml);639640// STYLE tag (Older versions of Netscape only):641// Browser support: [NS4]642safeHtml = '';643xssHtml = '<STYLE TYPE="text/javascript">xss=true;</STYLE>';644assertSanitizedHtml(xssHtml, safeHtml);645646// STYLE tag using background-image:647// Browser support: [IE6.0|NS8.1-IE]648safeHtml = isIE9() ? 'undefined' : '<a></a>';649xssHtml = '<STYLE>.XSS{background-image:url("javascript:alert("XSS")");}' +650'</STYLE><A CLASS=XSS></A>';651assertSanitizedHtml(xssHtml, safeHtml);652653// BASE tag. Works in IE and Netscape 8.1 in safe mode. You need the // to654// comment out the next characters so you won't get a JavaScript error and655// your XSS tag will render. Also, this relies on the fact that the website656// uses dynamically placed images like "images/image.jpg" rather than full657// paths. If the path includes a leading forward slash like658// "/images/image.jpg" you can remove one slash from this vector (as long as659// there are two to begin the comment this will work):660// Browser support: [IE6.0|NS8.1-IE]661safeHtml = '';662xssHtml = '<BASE HREF="javascript:xss=true;//">';663assertSanitizedHtml(xssHtml, safeHtml);664665// OBJECT tag (if they allow objects, you can also inject virus payloads to666// infect the users, etc. and same with the APPLET tag). The linked file is667// actually an HTML file that can contain your XSS:668// Browser support: [O9.02]669safeHtml = '';670xssHtml = '<OBJECT TYPE="text/x-scriptlet" ' +671'DATA="http://ha.ckers.org/scriptlet.html"></OBJECT>';672assertSanitizedHtml(xssHtml, safeHtml);673674// Using an EMBED tag you can embed a Flash movie that contains XSS. Click675// here for a demo. If you add the attributes allowScriptAccess="never" and676// allownetworking="internal" it can mitigate this risk (thank you to Jonathan677// Vanasco for the info).:678// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]679safeHtml = '';680xssHtml = '<EMBED SRC="http://ha.ckers.org/xss.swf" ' +681'AllowScriptAccess="always"></EMBED>';682assertSanitizedHtml(xssHtml, safeHtml);683684// You can EMBED SVG which can contain your XSS vector. This example only685// works in Firefox, but it's better than the above vector in Firefox because686// it does not require the user to have Flash turned on or installed. Thanks687// to nEUrOO for this one.688// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]689safeHtml = '';690xssHtml = '<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH' +691' A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv Mj' +692'AwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB' +693'2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBp' +694'ZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpO' +695'zwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" ' +696'AllowScriptAccess="always"></EMBED>';697assertSanitizedHtml(xssHtml, safeHtml);698699// XML namespace. The htc file must be located on the same server as your XSS700// vector:701// Browser support: [IE7.0|IE6.0|NS8.1-IE]702safeHtml = '<span>XSS</span>';703xssHtml = '<HTML xmlns:xss>' +704'<?import namespace="xss" implementation="http://ha.ckers.org/xss.htc">' +705'<xss:xss>XSS</xss:xss>' +706'</HTML>';707assertSanitizedHtml(xssHtml, safeHtml);708709// XML data island with CDATA obfuscation (this XSS attack works only in IE710// and Netscape 8.1 in IE rendering engine mode) - vector found by Sec Consult711// while auditing Yahoo:712// Browser support: [IE6.0|NS8.1-IE]713safeHtml = isIE9() ? '<span><span></span></span>' :714'<span><span><span>]]></span></span></span>' +715'<span></span>';716xssHtml = '<XML ID=I><X><C><![CDATA[<IMG SRC="javas]]>' +717'<![CDATA[cript:xss=true;">]]>' +718'</C></X></xml><SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>';719assertSanitizedHtml(xssHtml, safeHtml);720721// HTML+TIME in XML. This is how Grey Magic hacked Hotmail and Yahoo!. This722// only works in Internet Explorer and Netscape 8.1 in IE rendering engine723// mode and remember that you need to be between HTML and BODY tags for this724// to work:725// Browser support: [IE7.0|IE6.0|NS8.1-IE]726safeHtml = '<span></span>';727xssHtml = '<HTML><BODY>' +728'<?xml:namespace prefix="t" ns="urn:schemas-microsoft-com:time">' +729'<?import namespace="t" implementation="#default#time2">' +730'<t:set attributeName="innerHTML" to="XSS<SCRIPT DEFER>' +731'alert("XSS")</SCRIPT>">' +732'</BODY></HTML>';733assertSanitizedHtml(xssHtml, safeHtml);734735// IMG Embedded commands - this works when the webpage where this is injected736// (like a web-board) is behind password protection and that password737// protection works with other commands on the same domain. This can be used738// to delete users, add users (if the user who visits the page is an739// administrator), send credentials elsewhere, etc.... This is one of the740// lesser used but more useful XSS vectors:741// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]742safeHtml = '<img />';743xssHtml = '<IMG SRC="http://www.thesiteyouareon.com/somecommand.php?' +744'somevariables=maliciouscode">';745assertSanitizedHtml(xssHtml, safeHtml);746747// This was tested in IE, your mileage may vary. For performing XSS on sites748// that allow "<SCRIPT>" but don't allow "<SCRIPT SRC..." by way of a regex749// filter "/<script[^>]+src/i":750// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]751safeHtml = '';752xssHtml = '<SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';753assertSanitizedHtml(xssHtml, safeHtml);754755safeHtml = '';756xssHtml = '<SCRIPT =">" SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';757assertSanitizedHtml(xssHtml, safeHtml);758759// This XSS still worries me, as it would be nearly impossible to stop this760// without blocking all active content:761// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]762safeHtml = 'PT SRC="http://ha.ckers.org/xss.js">';763xssHtml = '<SCRIPT>document.write("<SCRI");<\/SCRIPT>PT ' +764'SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';765assertSanitizedHtml(xssHtml, safeHtml);766767// US-ASCII encoding (found by Kurt Huwig). This uses malformed ASCII encoding768// with 7 bits instead of 8. This XSS may bypass many content filters but769// only works if the host transmits in US-ASCII encoding, or if you set the770// encoding yourself. This is more useful against web application firewall771// cross site scripting evasion than it is server side filter evasion. Apache772// Tomcat is the only known server that transmits in US-ASCII encoding. I773// highly suggest anyone interested in alternate encoding issues look at my774// charsets issues page:775// Browser support: [IE7.0|IE6.0|NS8.1-IE]776// NOTE(danesh): We'd sanitize this if we received the (mis-)appropriately777// encoded version of this.778// safeHtml = ' script alert( XSS ) /script ';779// xssHtml = '¼script¾alert(¢XSS¢)¼/script¾';780// assertSanitizedHtml(xssHtml, safeHtml);781782// Escaping JavaScript escapes. When the application is written to output some783// user information inside of a JavaScript like the following:784// <SCRIPT>var a="$ENV{QUERY_STRING}";<\/SCRIPT> and you want to inject your785// own JavaScript into it but the server side application escapes certain786// quotes you can circumvent that by escaping their escape character. When787// this is gets injected it will read788// <SCRIPT>var a="\\";alert('XSS');//";<\/SCRIPT> which ends up un-escaping789// the double quote and causing the Cross Site Scripting vector to fire.790// The XSS locator uses this method.:791// Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]792// NOTE(danesh): We expect this to fail. More of a JS sanitizer check or a793// server-side template vulnerability test.794// safeHtml = '';795// xssHtml = '\";alert(window);//';796// assertSanitizedHtml(xssHtml, safeHtml);797}798799800function testDataAttributes() {801var html = '<div data-xyz="test">Testing</div>';802var safeHtml = '<div>Testing</div>';803assertSanitizedHtml(html, safeHtml);804805html = '<div data-goomoji="test" data-other="xyz">Testing</div>';806var expectedHtml = '<div data-goomoji="test">Testing</div>';807assertSanitizedHtml(808html, expectedHtml, new goog.html.sanitizer.HtmlSanitizer.Builder()809.allowCssStyles()810.allowDataAttributes(['data-goomoji'])811.build());812}813814815function testDisallowedDataWhitelistingAttributes() {816assertThrows(function() {817new goog.html.sanitizer.HtmlSanitizer.Builder()818.allowDataAttributes(['datai'])819.build();820});821822// Disallow internal attribute used by html sanitizer823assertThrows(function() {824new goog.html.sanitizer.HtmlSanitizer.Builder()825.allowDataAttributes(['data-i', 'data-sanitizer-safe'])826.build();827});828}829830831function testFormBody() {832var safeHtml = '<form>stuff</form>';833var formHtml = '<form name="body">stuff</form>';834assertSanitizedHtml(835formHtml, safeHtml,836new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build());837}838839840function testStyleTag() {841var safeHtml = '';842var xssHtml = '<STYLE>P.special {color : green;border: solid red;}</STYLE>';843assertSanitizedHtml(xssHtml, safeHtml);844}845846847function testOnlyAllowTags() {848var result = '<div><span></span>' +849'<a href="http://www.google.com">hi</a>' +850'<br>Test.<span></span><div align="right">Test</div></div>';851// If we were mimicing goog.labs.html.sanitizer, our output would be852// '<div><a>hi</a><br>Test.<div>Test</div></div>';853assertSanitizedHtml(854'<div><img id="bar" name=foo class="c d" ' +855'src="http://wherever.com">' +856'<a href=" http://www.google.com">hi</a>' +857'<br>Test.<hr><div align="right">Test</div></div>',858result, new goog.html.sanitizer.HtmlSanitizer.Builder()859.onlyAllowTags(['bR', 'a', 'DIV'])860.build());861}862863864function testDisallowNonWhitelistedTags() {865assertThrows('Should error on elements not whitelisted', function() {866new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowTags(['x']);867});868}869870871function testDefaultPoliciesAreApplied() {872var result = '<img /><a href="http://www.google.com">hi</a>' +873'<a href="ftp://whatever.com">another</a>';874assertSanitizedHtml(875'<img id="bar" name=foo class="c d" ' +876'src="http://wherever.com">' +877'<a href=" http://www.google.com">hi</a>' +878'<a href=ftp://whatever.com>another</a>',879result);880}881882883function testCustomNamePolicyIsApplied() {884var result = '<img name="myOwnPrefix-foo" />' +885'<a href="http://www.google.com">hi</a>' +886'<a href="ftp://whatever.com">another</a>';887assertSanitizedHtml(888'<img id="bar" name=foo class="c d" ' +889'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +890'<a href=ftp://whatever.com>another</a>',891result, new goog.html.sanitizer.HtmlSanitizer.Builder()892.withCustomNamePolicy(function(name) {893return 'myOwnPrefix-' + name;894})895.build());896}897898899function testCustomTokenPolicyIsApplied() {900var result = '<img id="myOwnPrefix-bar" ' +901'class="myOwnPrefix-c myOwnPrefix-d" />' +902'<a href="http://www.google.com">hi</a>' +903'<a href="ftp://whatever.com">another</a>';904assertSanitizedHtml(905'<img id="bar" name=foo class="c d" ' +906'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +907'<a href=ftp://whatever.com>another</a>',908result, new goog.html.sanitizer.HtmlSanitizer.Builder()909.withCustomTokenPolicy(function(name) {910return 'myOwnPrefix-' + name;911})912.build());913}914915916function testMultipleCustomPoliciesAreApplied() {917var result = '<img id="plarpalarp-bar" name="larlarlar-foo" ' +918'class="plarpalarp-c plarpalarp-d" />' +919'<a href="http://www.google.com">hi</a>' +920'<a href="ftp://whatever.com">another</a>';921assertSanitizedHtml(922'<img id="bar" name=foo class="c d" ' +923'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +924'<a href=ftp://whatever.com>another</a>',925result,926new goog.html.sanitizer.HtmlSanitizer.Builder()927.withCustomTokenPolicy(function(token) {928return 'plarpalarp-' + token;929})930.withCustomNamePolicy(function(name) { return 'larlarlar-' + name; })931.build());932}933934935function testNonTrivialCustomPolicy() {936var result = '<img /><a href="http://www.google.com" name="Alacrity">hi</a>' +937'<a href="ftp://whatever.com">another</a>';938assertSanitizedHtml(939'<img id="bar" name=foo class="c d" src="http://wherever.com">' +940'<a href=" http://www.google.com" name=Alacrity>hi</a>' +941'<a href=ftp://whatever.com>another</a>',942result,943new goog.html.sanitizer.HtmlSanitizer.Builder()944.withCustomNamePolicy(function testNamesMustBeginWithTheLetterA(945name) { return name.charAt(0) != 'A' ? null : name; })946.build());947}948949950function testNetworkRequestUrlsAllowed() {951var result = '<img src="http://wherever.com" />' +952'<img src="https://secure.wherever.com" />' +953'<img alt="test" src="//wherever.com" />' +954'<a href="http://www.google.com">hi</a>' +955'<a href="ftp://whatever.com">another</a>';956assertSanitizedHtml(957'<img id="bar" name=foo class="c d" src="http://wherever.com">' +958'<img src="https://secure.wherever.com">' +959'<img alt="test" src="//wherever.com">' +960'<a href=" http://www.google.com">hi</a>' +961'<a href=ftp://whatever.com>another</a>',962result, new goog.html.sanitizer.HtmlSanitizer.Builder()963.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)964.build());965}966967968function testCustomNRUrlPolicyMustNotContainParameters() {969var result = '<img src="http://wherever.com" /><img />';970assertSanitizedHtml(971'<img id="bar" class="c d" src="http://wherever.com">' +972'<img src="https://www.bank.com/withdraw?amount=onebeeeelion">',973result, new goog.html.sanitizer.HtmlSanitizer.Builder()974.withCustomNetworkRequestUrlPolicy(function(url) {975return url.match(/\?/) ? null :976goog.html.testing.newSafeUrlForTest(url);977})978.build());979}980981982function testPolicyHints() {983var sanitizer =984new goog.html.sanitizer.HtmlSanitizer.Builder()985.allowFormTag()986.withCustomNetworkRequestUrlPolicy(function(url, policyHints) {987if ((policyHints.tagName == 'img' &&988policyHints.attributeName == 'src') ||989(policyHints.tagName == 'input' &&990policyHints.attributeName == 'src')) {991return goog.html.testing.newSafeUrlForTest(992'https://imageproxy/?' + url);993} else {994return null;995}996})997.withCustomUrlPolicy(function(url, policyHints) {998if (policyHints.tagName == 'a' &&999policyHints.attributeName == 'href') {1000return goog.html.testing.newSafeUrlForTest(1001'https://linkproxy/?' + url);1002}1003return goog.html.SafeUrl.sanitize(url);1004})1005.build();10061007// TODO(user): update this test to include a stylesheet once they're1008// supported (in order to view both branches of the NRUrlPolicy).1009var result = '<img src="https://imageproxy/?http://image" /> ' +1010'<input type="image" src="https://imageproxy/?http://another" />' +1011'<a href="https://linkproxy/?http://link">a link</a>' +1012'<form action="http://formaction"></form>';1013assertSanitizedHtml(1014'<img src="http://image"> <input type="image" ' +1015'src="http://another"><a href="http://link">a link</a>' +1016'<form action="http://formaction"></form>',1017result, sanitizer);1018}101910201021function testNRUrlPolicyAffectsCssSanitization() {1022var sanitizer =1023new goog.html.sanitizer.HtmlSanitizer.Builder()1024.allowCssStyles()1025.withCustomNetworkRequestUrlPolicy(function(url, policyHints) {1026// Network request URLs may only be over https.1027if (!/^https:\/\//i.test(url)) {1028return null;1029}1030// CSS background URLs may only come from google.com.1031if (policyHints.cssProperty === 'background-image') {1032if (!/^https:\/\/www\.google\.com\//i.test(url)) {1033return null;1034}1035}1036return goog.html.SafeUrl.sanitize(url);1037})1038.build();10391040var sanitizedHtml;1041try {1042sanitizedHtml = sanitizer.sanitize(1043'<div style="background: url(\'https://www.google.com/i.png\')"></div>');1044if (isIE9()) {1045assertEquals('', goog.html.SafeHtml.unwrap(sanitizedHtml));1046return;1047}1048assertRegExp(1049/background(?:-image)?:.*url\(.?https:\/\/www.google.com\/i.png.?\)/,1050getStyle(sanitizedHtml));1051} catch (err) {1052if (!isIE8()) {1053throw err;1054}1055}10561057try {1058sanitizedHtml = sanitizer.sanitize(1059'<div style="background: url(\'https://wherever/\')"></div>');1060assertNotContains(1061'https://wherever/', goog.html.SafeHtml.unwrap(sanitizedHtml));1062} catch (err) {1063if (!isIE8()) {1064throw err;1065}1066}10671068sanitizedHtml = '<img src="https://www.google.com/i.png">';1069assertSanitizedHtml(sanitizedHtml, sanitizedHtml, sanitizer);10701071sanitizedHtml = '<img src="https://wherever/">';1072assertSanitizedHtml(sanitizedHtml, sanitizedHtml, sanitizer);1073}107410751076function testAllowOnlyHttpAndHttpsAndFtpForNRUP() {1077var input = '<img src="http://whatever">' +1078'<img src="https://whatever">' +1079'<img src="ftp://nope">' +1080'<img src="garbage:nope">' +1081'<img src="data:yep">';1082var expected = '<img src="http://whatever" />' +1083'<img src="https://whatever" />' +1084'<img src="ftp://nope">' +1085'<img />' +1086'<img />';1087assertSanitizedHtml(1088input, expected,1089new goog.html.sanitizer.HtmlSanitizer.Builder()1090.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)1091.build());1092}109310941095function testUriSchemesOnNonNetworkRequestUrls() {1096var input = '<a href="ftp://yep">something</a>' +1097'<a href="gopher://yep">something</a>' +1098'<a href="gopher:nope">something</a>' +1099'<a href="http://yep">something</a>' +1100'<a href="https://yep">something</a>' +1101'<a href="garbage://nope">something</a>' +1102'<a href="relative/yup">something</a>' +1103'<a href="nope">something</a>' +1104'<a>lol</a>';1105var expected = '<a href="ftp://yep">something</a>' +1106'<a>something</a>' +1107'<a>something</a>' +1108'<a href="http://yep">something</a>' +1109'<a href="https://yep">something</a>' +1110'<a>something</a>' +1111'<a href="relative/yup">something</a>' +1112'<a href="nope">something</a>' +1113'<a>lol</a>';1114assertSanitizedHtml(1115input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1116.withCustomUrlPolicy(goog.html.SafeUrl.sanitize)1117.build());1118}111911201121function testOverridingGetOrSetAttribute() {1122var input = '<form>' +1123'<input name=setAttribute />' +1124'<input name=getAttribute />' +1125'</form>';1126var expected = '<form><input><input></form>';1127assertSanitizedHtml(1128input, expected,1129new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build());1130}113111321133function testOverridingBookkeepingAttribute() {1134var input = '<div data-sanitizer-foo="1" alt="b">Hello</div>';1135var expected = '<div alt="b">Hello</div>';1136assertSanitizedHtml(1137input, expected,1138new goog.html.sanitizer.HtmlSanitizer.Builder()1139.withCustomTokenPolicy(function(token) { return token; })1140.build());1141}114211431144function testTemplateRemoved() {1145var input = '<div><template><h1>boo</h1></template></div>';1146var expected = '<div></div>';1147assertSanitizedHtml(input, expected);1148}114911501151/**1152* Shorthand for sanitized tags1153* @param {string} tag1154* @return {string}1155*/1156function otag(tag) {1157return 'data-sanitizer-original-tag="' + tag + '"';1158}115911601161function testOriginalTag() {1162var input = '<p>Line1<magic></magic></p>';1163var expected = '<p>Line1<span ' + otag('magic') + '></span></p>';1164assertSanitizedHtml(1165input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1166.addOriginalTagNames()1167.build());1168}116911701171function testOriginalTagOverwrite() {1172var input = '<div id="qqq">hello' +1173'<a:b id="hi" class="hnn a" boo="3">qqq</a:b></div>';1174var expected = '<div>hello<span ' + otag('a:b') + ' id="HI" class="hnn a">' +1175'qqq</span></div>';1176assertSanitizedHtml(1177input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1178.addOriginalTagNames()1179.withCustomTokenPolicy(function(token, hints) {1180var an = hints.attributeName;1181if (an === 'id' && token === 'hi') {1182return 'HI';1183} else if (an === 'class') {1184return token;1185}1186return null;1187})1188.build());1189}119011911192function testOriginalTagClobber() {1193var input = '<a:b data-sanitizer-original-tag="xss"></a:b>';1194var expected = '<span ' + otag('a:b') + '></span>';1195assertSanitizedHtml(1196input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1197.addOriginalTagNames()1198.build());1199}120012011202// the tests below investigate how <span> behaves when it is unknowingly put1203// as child or parent of other elements due to sanitization. <div> had even more1204// problems (e.g. cannot be a child of <p>)120512061207/**1208* Sanitize content, let the browser apply its own HTML tree correction by1209* attaching the content to the document, and then assert it matches the1210* expected value.1211* @param {string} expected1212* @param {string} input1213*/1214function assertAfterInsertionEquals(expected, input) {1215var sanitizer =1216new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build();1217input = goog.html.SafeHtml.unwrap(sanitizer.sanitize(input));1218var div = document.createElement('div');1219document.body.appendChild(div);1220div.innerHTML = input;1221goog.testing.dom.assertHtmlMatches(1222expected, div.innerHTML, true /* opt_strictAttributes */);1223div.parentNode.removeChild(div);1224}122512261227function testSpanNotCorrectedByBrowsersOuter() {1228if (isIE8() || isIE9()) {1229return;1230}1231goog.array.forEach(1232goog.object.getKeys(goog.html.sanitizer.TagWhitelist), function(tag) {1233if (goog.array.contains(1234[1235'BR', 'IMG', 'AREA', 'COL', 'COLGROUP', 'HR', 'INPUT',1236'SOURCE', 'WBR'1237],1238tag)) {1239return; // empty elements, ok1240}1241if (goog.array.contains(['CAPTION'], tag)) {1242return; // potential problems1243}1244if (goog.array.contains(['NOSCRIPT'], tag)) {1245return; // weird/not important1246}1247if (goog.array.contains(1248[1249'SELECT', 'TABLE', 'TBODY', 'TD', 'TR', 'TEXTAREA', 'TFOOT',1250'THEAD', 'TH'1251],1252tag)) {1253return; // consistent in whitelist, ok1254}1255var input = '<' + tag.toLowerCase() + '>a<span></span>a</' +1256tag.toLowerCase() + '>';1257assertAfterInsertionEquals(input, input);1258});1259}126012611262function testSpanNotCorrectedByBrowsersInner() {1263if (isIE8() || isIE9()) {1264return;1265}1266goog.array.forEach(1267goog.object.getKeys(goog.html.sanitizer.TagWhitelist), function(tag) {1268if (goog.array.contains(1269[1270'CAPTION', 'TABLE', 'TBODY', 'TD', 'TR', 'TEXTAREA', 'TFOOT',1271'THEAD', 'TH'1272],1273tag)) {1274return; // consistent in whitelist, ok1275}1276if (goog.array.contains(['COL', 'COLGROUP'], tag)) {1277return; // potential problems1278}1279// TODO(pelizzi): Skip testing for FORM tags on Chrome until b/325506951280// is fixed.1281if (tag == 'FORM' && goog.userAgent.WEBKIT) {1282return;1283}1284var input;1285if (goog.array.contains(1286[1287'BR', 'IMG', 'AREA', 'COL', 'COLGROUP', 'HR', 'INPUT',1288'SOURCE', 'WBR' // empty elements, ok1289],1290tag)) {1291input = '<span>a<' + tag.toLowerCase() + '>a</span>';1292} else {1293input = '<span>a<' + tag.toLowerCase() + '>a</' + tag.toLowerCase() +1294'>a</span>';1295}1296assertAfterInsertionEquals(input, input);1297});1298}129913001301function testTemplateTagToSpan() {1302var input = '<template alt="yes"><p>q</p></template>';1303var expected = '<span alt="yes"><p>q</p></span>';1304// TODO(pelizzi): use unblockTag once it's available1305delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];1306assertSanitizedHtml(input, expected);1307goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;1308}130913101311var just = goog.string.Const.from('test');131213131314function testTemplateTagWhitelisted() {1315var input = '<div><template alt="yes"><p>q</p></template></div>';1316// TODO(pelizzi): use unblockTag once it's available1317delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];1318var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();1319goog.html.sanitizer.unsafe.alsoAllowTags(just, builder, ['TEMPLATE']);1320assertSanitizedHtml(input, input, builder.build());1321goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;1322}132313241325function testTemplateTagFake() {1326var input = '<template data-sanitizer-original-tag="template">a</template>';1327var expected = '';1328assertSanitizedHtml(input, expected);1329}133013311332function testTemplateNested() {1333var input = '<template><p>a</p><zzz alt="a"/><script>z</script><template>' +1334'<p>a</p><zzz alt="a"/><script>z</script></template></template>';1335var expected = '<template><p>a</p><span alt="a"></span><template>' +1336'<p>a</p><span alt="a"></span></template></template>';1337// TODO(pelizzi): use unblockTag once it's available1338delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];1339var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();1340goog.html.sanitizer.unsafe.alsoAllowTags(just, builder, ['TEMPLATE']);1341assertSanitizedHtml(input, expected, builder.build());1342goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;1343}134413451346function testOnlyAllowEmptyAttrList() {1347var input = '<p alt="nope" aria-checked="true" zzz="1">b</p>' +1348'<a target="_blank">c</a>';1349var expected = '<p>b</p><a>c</a>';1350assertSanitizedHtml(1351input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1352.onlyAllowAttributes([])1353.build());1354}135513561357function testOnlyAllowUnWhitelistedAttr() {1358assertThrows(function() {1359new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes(1360['alt', 'zzz']);1361});1362}136313641365function testOnlyAllowAttributeWildCard() {1366var input =1367'<div alt="yes" aria-checked="true"><img alt="yep" avbb="no" /></div>';1368var expected = '<div alt="yes"><img alt="yep" /></div>';1369assertSanitizedHtml(1370input, expected,1371new goog.html.sanitizer.HtmlSanitizer.Builder()1372.onlyAllowAttributes([{tagName: '*', attributeName: 'alt'}])1373.build());1374}137513761377function testOnlyAllowAttributeLabelForA() {1378var input = '<a label="3" aria-checked="4">fff</a><img label="3" />';1379var expected = '<a label="3">fff</a><img />';1380assertSanitizedHtml(1381input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1382.onlyAllowAttributes([{1383tagName: '*',1384attributeName: 'label',1385policy: function(value, hints) {1386if (hints.tagName !== 'a') {1387return null;1388}1389return value;1390}1391}])1392.build());1393}139413951396function testOnlyAllowAttributePolicy() {1397var input = '<img alt="yes" /><img alt="no" />';1398var expected = '<img alt="yes" /><img />';1399assertSanitizedHtml(1400input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1401.onlyAllowAttributes([{1402tagName: '*',1403attributeName: 'alt',1404policy: function(value, hints) {1405assertEquals(hints.attributeName, 'alt');1406return value === 'yes' ? value : null;1407}1408}])1409.build());1410}141114121413function testOnlyAllowAttributePolicyPipe1() {1414var input = '<a target="hello">b</a>';1415var expected = '<a target="_blank">b</a>';1416assertSanitizedHtml(1417input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1418.onlyAllowAttributes([{1419tagName: 'a',1420attributeName: 'target',1421policy: function(value, hints) {1422assertEquals(hints.attributeName, 'target');1423return '_blank';1424}1425}])1426.build());1427}142814291430function testOnlyAllowAttributePolicyPipe2() {1431var input = '<a target="hello">b</a>';1432var expected = '<a>b</a>';1433assertSanitizedHtml(1434input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()1435.onlyAllowAttributes([{1436tagName: 'a',1437attributeName: 'target',1438policy: function(value, hints) {1439assertEquals(hints.attributeName, 'target');1440return 'nope';1441}1442}])1443.build());1444}144514461447function testOnlyAllowAttributeSpecificPolicyThrows() {1448assertThrows(function() {1449new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes([1450{tagName: 'img', attributeName: 'src', policy: goog.functions.identity}1451]);1452});1453}145414551456function testOnlyAllowAttributeGenericPolicyThrows() {1457assertThrows(function() {1458new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes([{1459tagName: '*',1460attributeName: 'target',1461policy: goog.functions.identity1462}]);1463});1464}146514661467function testOnlyAllowAttributeRefineThrows() {1468var builder =1469new goog.html.sanitizer.HtmlSanitizer.Builder()1470.onlyAllowAttributes(1471['aria-checked', {tagName: 'LINK', attributeName: 'HREF'}])1472.onlyAllowAttributes(['aria-checked']);1473assertThrows(function() {1474builder.onlyAllowAttributes(['alt']);1475});1476}147714781479function testUrlWithCredentials() {1480if (isIE8() || isIE9()) {1481return;1482}1483// IE has trouble getting and setting URL attributes with credentials. Both1484// HTMLSanitizer and assertHtmlMatches are affected by the bug, hence the use1485// of plain string matching.1486var url = 'http://foo:[email protected]';1487var input = '<div style="background-image: url(\'' + url + '\');">' +1488'<img src="' + url + '" /></div>';1489var expectedIE = '<div style="background-image: url("' + url +1490'");"><img src="' + url + '" /></div>';1491var sanitizer =1492new goog.html.sanitizer.HtmlSanitizer.Builder()1493.withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)1494.allowCssStyles()1495.build();1496if (goog.userAgent.EDGE_OR_IE) {1497assertEquals(1498expectedIE, goog.html.SafeHtml.unwrap(sanitizer.sanitize(input)));1499} else {1500assertSanitizedHtml(input, input, sanitizer);1501}1502}150315041505