123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503 |
- // 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.HtmlSanitizer.Builder');
- 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;
- }
- }
- if (!opt_sanitizer) {
- // Retry with raw sanitizer created without the builder.
- assertSanitizedHtml(
- originalHtml, expectedHtml, new goog.html.sanitizer.HtmlSanitizer());
- // Retry with an explicitly passed in Builder.
- var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
- assertSanitizedHtml(
- originalHtml, expectedHtml,
- new goog.html.sanitizer.HtmlSanitizer(builder));
- }
- }
- /**
- * @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 testBuilderCanOnlyBeUsedOnce() {
- var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
- var sanitizer = builder.build();
- assertThrows(function() {
- builder.build();
- });
- assertThrows(function() {
- new goog.html.sanitizer.HtmlSanitizer(builder);
- });
- }
- 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.userAgent.WEBKIT) {
- return;
- }
- var input;
- if (goog.array.contains(
- [
- 'BR', 'IMG', 'AREA', 'COL', 'COLGROUP', 'HR', 'INPUT',
- 'SOURCE', 'WBR' // empty elements, ok
- ],
- tag)) {
- input = '<span>a<' + tag.toLowerCase() + '>a</span>';
- } else {
- input = '<span>a<' + tag.toLowerCase() + '>a</' + tag.toLowerCase() +
- '>a</span>';
- }
- assertAfterInsertionEquals(input, input);
- });
- }
- function testTemplateTagToSpan() {
- var input = '<template alt="yes"><p>q</p></template>';
- var expected = '<span alt="yes"><p>q</p></span>';
- // TODO(pelizzi): use unblockTag once it's available
- delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];
- assertSanitizedHtml(input, expected);
- goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;
- }
- var just = goog.string.Const.from('test');
- function testTemplateTagWhitelisted() {
- var input = '<div><template alt="yes"><p>q</p></template></div>';
- // TODO(pelizzi): use unblockTag once it's available
- delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];
- var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
- goog.html.sanitizer.unsafe.alsoAllowTags(just, builder, ['TEMPLATE']);
- assertSanitizedHtml(input, input, builder.build());
- goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;
- }
- function testTemplateTagFake() {
- var input = '<template data-sanitizer-original-tag="template">a</template>';
- var expected = '';
- assertSanitizedHtml(input, expected);
- }
- function testTemplateNested() {
- var input = '<template><p>a</p><zzz alt="a"/><script>z</script><template>' +
- '<p>a</p><zzz alt="a"/><script>z</script></template></template>';
- var expected = '<template><p>a</p><span alt="a"></span><template>' +
- '<p>a</p><span alt="a"></span></template></template>';
- // TODO(pelizzi): use unblockTag once it's available
- delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];
- var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
- goog.html.sanitizer.unsafe.alsoAllowTags(just, builder, ['TEMPLATE']);
- assertSanitizedHtml(input, expected, builder.build());
- goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;
- }
- function testOnlyAllowEmptyAttrList() {
- var input = '<p alt="nope" aria-checked="true" zzz="1">b</p>' +
- '<a target="_blank">c</a>';
- var expected = '<p>b</p><a>c</a>';
- assertSanitizedHtml(
- input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
- .onlyAllowAttributes([])
- .build());
- }
- function testOnlyAllowUnWhitelistedAttr() {
- assertThrows(function() {
- new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes(
- ['alt', 'zzz']);
- });
- }
- function testOnlyAllowAttributeWildCard() {
- var input =
- '<div alt="yes" aria-checked="true"><img alt="yep" avbb="no" /></div>';
- var expected = '<div alt="yes"><img alt="yep" /></div>';
- assertSanitizedHtml(
- input, expected,
- new goog.html.sanitizer.HtmlSanitizer.Builder()
- .onlyAllowAttributes([{tagName: '*', attributeName: 'alt'}])
- .build());
- }
- function testOnlyAllowAttributeLabelForA() {
- var input = '<a label="3" aria-checked="4">fff</a><img label="3" />';
- var expected = '<a label="3">fff</a><img />';
- assertSanitizedHtml(
- input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
- .onlyAllowAttributes([{
- tagName: '*',
- attributeName: 'label',
- policy: function(value, hints) {
- if (hints.tagName !== 'a') {
- return null;
- }
- return value;
- }
- }])
- .build());
- }
- function testOnlyAllowAttributePolicy() {
- var input = '<img alt="yes" /><img alt="no" />';
- var expected = '<img alt="yes" /><img />';
- assertSanitizedHtml(
- input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
- .onlyAllowAttributes([{
- tagName: '*',
- attributeName: 'alt',
- policy: function(value, hints) {
- assertEquals(hints.attributeName, 'alt');
- return value === 'yes' ? value : null;
- }
- }])
- .build());
- }
- function testOnlyAllowAttributePolicyPipe1() {
- var input = '<a target="hello">b</a>';
- var expected = '<a target="_blank">b</a>';
- assertSanitizedHtml(
- input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
- .onlyAllowAttributes([{
- tagName: 'a',
- attributeName: 'target',
- policy: function(value, hints) {
- assertEquals(hints.attributeName, 'target');
- return '_blank';
- }
- }])
- .build());
- }
- function testOnlyAllowAttributePolicyPipe2() {
- var input = '<a target="hello">b</a>';
- var expected = '<a>b</a>';
- assertSanitizedHtml(
- input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
- .onlyAllowAttributes([{
- tagName: 'a',
- attributeName: 'target',
- policy: function(value, hints) {
- assertEquals(hints.attributeName, 'target');
- return 'nope';
- }
- }])
- .build());
- }
- function testOnlyAllowAttributeSpecificPolicyThrows() {
- assertThrows(function() {
- new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes([
- {tagName: 'img', attributeName: 'src', policy: goog.functions.identity}
- ]);
- });
- }
- function testOnlyAllowAttributeGenericPolicyThrows() {
- assertThrows(function() {
- new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes([{
- tagName: '*',
- attributeName: 'target',
- policy: goog.functions.identity
- }]);
- });
- }
- function testOnlyAllowAttributeRefineThrows() {
- var builder =
- new goog.html.sanitizer.HtmlSanitizer.Builder()
- .onlyAllowAttributes(
- ['aria-checked', {tagName: 'LINK', attributeName: 'HREF'}])
- .onlyAllowAttributes(['aria-checked']);
- assertThrows(function() {
- builder.onlyAllowAttributes(['alt']);
- });
- }
- function testUrlWithCredentials() {
- if (isIE8() || isIE9()) {
- return;
- }
- // IE has trouble getting and setting URL attributes with credentials. Both
- // HTMLSanitizer and assertHtmlMatches are affected by the bug, hence the use
- // of plain string matching.
- var url = 'http://foo:bar@example.com';
- var input = '<div style="background-image: url(\'' + url + '\');">' +
- '<img src="' + url + '" /></div>';
- var expectedIE = '<div style="background-image: url("' + url +
- '");"><img src="' + url + '" /></div>';
- var sanitizer =
- new goog.html.sanitizer.HtmlSanitizer.Builder()
- .withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
- .allowCssStyles()
- .build();
- if (goog.userAgent.EDGE_OR_IE) {
- assertEquals(
- expectedIE, goog.html.SafeHtml.unwrap(sanitizer.sanitize(input)));
- } else {
- assertSanitizedHtml(input, input, sanitizer);
- }
- }
|