/* * Copyright (c) 2014 the original author or authors * * 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. */ package io.werval.modules.sanitize; import io.werval.test.WervalRule; import org.hamcrest.Matcher; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import static io.werval.util.Strings.EMPTY; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; import static io.werval.modules.sanitize.Sanitize.ANTISAMY_ANYTHINGGOES; /** * Sanitize Plugin Test. */ public class SanitizePluginTest { @ClassRule public static final WervalRule WERVAL = new WervalRule(); private static Sanitize sanitize; @BeforeClass public static void setupSanitizer() { sanitize = WERVAL.application().plugin( Sanitize.class ); } @Test public void sanitizeHtml() { // Use a policy that allow <a/> and <img/> tags for the test to be relevant Sanitize sanitize = SanitizePluginTest.sanitize.withPolicy( ANTISAMY_ANYTHINGGOES ); // XSS Filter Evasion Techniques // See https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet assertThat( sanitize.html( "';alert(String.fromCharCode(88,83,83))//';alert(String.fromCharCode(88,83,83))//\";" + "alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//--" + "></SCRIPT>\">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>" ).toLowerCase(), allOf( not( containsString( "script" ) ), containsString( "alert" ) ) ); assertThat( sanitize.html( "<IMG SRC=\"javascript:alert('XSS');\">" ).toLowerCase(), noneOf( containsString( "javascript" ), containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=JaVaScRiPt:alert('XSS')>" ).toLowerCase(), noneOf( containsString( "javascript" ), containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=`javascript:alert(\"RSnake says, 'XSS'\")`>" ).toLowerCase(), noneOf( containsString( "javascript" ), containsString( "img" ) ) ); assertThat( sanitize.html( "<a onmouseover=\"alert(document.cookie)\">xss link</a>" ).toLowerCase(), allOf( not( containsString( "cookie" ) ), containsString( "<a" ) ) ); assertThat( sanitize.html( "<a onmouseover=alert(document.cookie)>xss link</a>" ).toLowerCase(), allOf( not( containsString( "cookie" ) ), containsString( "<a" ) ) ); assertThat( sanitize.html( "<IMG \"\"\"><SCRIPT>alert(\"XSS\")</SCRIPT>\">" ).toLowerCase(), allOf( not( containsString( "script" ) ), containsString( "<img" ) ) ); assertThat( sanitize.html( "<IMG SRC=# onmouseover=\"alert('xss')\">" ).toLowerCase(), allOf( not( containsString( "xss" ) ), containsString( "<img" ) ) ); assertThat( sanitize.html( "<IMG SRC= onmouseover=\"alert('xss')\">" ).toLowerCase(), noneOf( containsString( "xss" ), containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG onmouseover=\"alert('xss')\">" ).toLowerCase(), allOf( not( containsString( "xss" ) ), containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=/ onerror=\"alert(String.fromCharCode(88,83,83))\"></img>" ).toLowerCase(), allOf( not( containsString( "onerror" ) ), containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=javascript:a" + "lert('XSS')>" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=javascript" + ":alert('XS" + "S')>" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=javascript:alert(&" + "#x27XSS')>" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=\"jav ascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=\"jav ascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=\"jav ascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=\"jav ascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<IMG SRC=\"  javascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<SCRIPT/XSS SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>" ).toLowerCase(), not( containsString( "script" ) ) ); assertThat( sanitize.html( "<BODY onload!#$%&()*~+-_.,:;?@[/|\\]^`=alert(\"XSS\")>" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<SCRIPT/SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>" ).toLowerCase(), not( containsString( "script" ) ) ); assertThat( sanitize.html( "<<SCRIPT>alert(\"XSS\");//<</SCRIPT>" ).toLowerCase(), not( containsString( "script" ) ) ); assertThat( sanitize.html( "<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >" ).toLowerCase(), not( containsString( "script" ) ) ); assertThat( sanitize.html( "<SCRIPT SRC=//ha.ckers.org/.j>" ).toLowerCase(), not( containsString( "script" ) ) ); assertThat( sanitize.html( "<IMG SRC=\"javascript:alert('XSS')\"" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<iframe src=http://ha.ckers.org/scriptlet.html <" ).toLowerCase(), not( containsString( "iframe" ) ) ); assertThat( sanitize.html( "</TITLE><SCRIPT>alert(\"XSS\");</SCRIPT>" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<INPUT TYPE=\"IMAGE\" SRC=\"javascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<BODY BACKGROUND=\"javascript:alert('XSS')\">" ).toLowerCase(), not( containsString( "background" ) ) ); assertThat( sanitize.html( "<IMG DYNSRC=\"javascript:alert('XSS')\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<IMG LOWSRC=\"javascript:alert('XSS')\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<STYLE>li {list-style-image: url(\"javascript:alert('XSS')\");}</STYLE><UL><LI>XSS</br>" ).toLowerCase(), not( containsString( "alert" ) ) ); assertThat( sanitize.html( "<IMG SRC='vbscript:msgbox(\"XSS\")'>" ).toLowerCase(), not( containsString( "img" ) ) ); assertThat( sanitize.html( "<BODY ONLOAD=alert('XSS')>" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<BGSOUND SRC=\"javascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<BR SIZE=\"&{alert('XSS')}\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<LINK REL=\"stylesheet\" HREF=\"javascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<META HTTP-EQUIV=\"refresh\" CONTENT=\"0;url=javascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<META HTTP-EQUIV=\"refresh\" " + "CONTENT=\"0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<META HTTP-EQUIV=\"refresh\" CONTENT=\"0; URL=http://;URL=javascript:alert('XSS');\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<IFRAME SRC=\"javascript:alert('XSS');\"></IFRAME>" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<IFRAME SRC=# onmouseover=\"alert(document.cookie)\"></IFRAME>" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<FRAMESET><FRAME SRC=\"javascript:alert('XSS');\"></FRAMESET>" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<TABLE BACKGROUND=\"javascript:alert('XSS')\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<TABLE><TD BACKGROUND=\"javascript:alert('XSS')\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<DIV STYLE=\"background-image: url(javascript:alert('XSS'))\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<DIV STYLE=\"background-image:\\0075\\0072\\006C\\0028'\\006a\\0061\\0076\\0061\\0073" + "\\0063\\0072\\0069\\0070\\0074\\003a\\0061\\006c\\0065\\0072\\0074\\0028.1027\\0058.1053" + "\\0053\\0027\\0029'\\0029\">" ).toLowerCase(), not( containsString( "background-image" ) ) ); assertThat( sanitize.html( "<DIV STYLE=\"background-image: url(javascript:alert('XSS'))\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<DIV STYLE=\"width: expression(alert('XSS'));\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<!--[if gte IE 4]>\n" + "<SCRIPT>alert('XSS');</SCRIPT>\n" + "<![endif]-->" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<BASE HREF=\"javascript:alert('XSS');//\">" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "<OBJECT TYPE=\"text/x-scriptlet\" DATA=\"http://ha.ckers.org/scriptlet.html\"></OBJECT>" ).toLowerCase(), not( containsString( "scriptlet" ) ) ); assertThat( sanitize.html( "<EMBED SRC=\"data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4b" + "Wxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW" + "5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5" + "cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==\" " + "type=\"image/svg+xml\" AllowScriptAccess=\"always\"></EMBED>" ).toLowerCase(), not( containsString( "AllowScriptAccess" ) ) ); // mXSS - Mutation XSS - Relies on the browser dom auto-mutation if used as innerHtml // See https://www.youtube.com/watch?v=Ld6prXQPnf4 assertThat( sanitize.html( "<IMG SRC=\"x\" CLASS=\"``onerror=alert('XSS')\"/>" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "0<aside xmlns=\"x><img src=x onerror=alert('XSS')\">123</aside>" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.html( "0<aside xmlns=\"foo:img src=x onerror=alert('XSS')>\">123" ).toLowerCase(), not( containsString( "xss" ) ) ); // Browser downgrade // See https://www.youtube.com/watch?v=Ld6prXQPnf4 assertThat( sanitize.html( "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=IE5\"/>" ).toLowerCase(), not( containsString( "meta" ) ) ); // CSS escapes harmful if used as innerHtml // See https://www.youtube.com/watch?v=Ld6prXQPnf4 assertThat( sanitize.html( "<p style=\"font-family:'foo\\27\\3b color\\3a expression\\28 alert('XSS'))/* bar'\">hello</p>" ).toLowerCase(), equalTo( "<p style=\"\">hello</p>" ) ); assertThat( sanitize.html( "<p style=\"fon\\22\\3e\\3cimg\\20src\\3dx\\20onerror\\3d alert\\28 1\\29\\3et-family:'foobar'\">h</p>" ).toLowerCase(), equalTo( "<p style=\"\">h</p>" ) ); assertThat( sanitize.html( "<p style=\"filter: 'expression(alert(1))'\"/>" ).toLowerCase(), equalTo( "<p style=\"\"></p>" ) ); assertThat( sanitize.html( "<p style=\"font-family: 'foo\\27\\3b color\\3a expression\\28 alert('XSS'))/* ,bar'\"/>" ).toLowerCase(), equalTo( "<p style=\"\"></p>" ) ); // HTML Entities // See https://www.youtube.com/watch?v=Ld6prXQPnf4 assertThat( sanitize.html( "<svg><style><img src=x onerror=alert(1)></svg>" ).toLowerCase(), equalTo( EMPTY ) ); } @Test public void sanitizeCss() { // Use a policy that allow styles for the test to be relevant Sanitize sanitize = SanitizePluginTest.sanitize.withPolicy( ANTISAMY_ANYTHINGGOES ); // XSS Filter Evasion Techniques // See https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet assertThat( sanitize.css( "li {list-style-image: url(\"javascript:alert('XSS')\");}" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.css( "background-image:\\0075\\0072\\006C\\0028'\\006a\\0061\\0076\\0061\\0073" + "\\0063\\0072\\0069\\0070\\0074\\003a\\0061\\006c\\0065\\0072\\0074\\0028.1027\\0058.1053" + "\\0053\\0027\\0029'\\0029" ).toLowerCase(), not( containsString( "background-image" ) ) ); assertThat( sanitize.css( "background-image: url(javascript:alert('XSS'))" ).toLowerCase(), not( containsString( "xss" ) ) ); // CSS escapes harmful if used as innerHtml // See https://www.youtube.com/watch?v=Ld6prXQPnf4 assertThat( sanitize.css( "font-family:'foo\\27\\3b color\\3a expression\\28 alert('XSS'))/* bar'" ).toLowerCase(), not( containsString( "xss" ) ) ); assertThat( sanitize.css( "fon\\22\\3e\\3cimg\\20src\\3dx\\20onerror\\3d alert\\28 1\\29\\3et-family:'foobar'" ).toLowerCase(), not( containsString( "onerror" ) ) ); assertThat( sanitize.css( "filter: 'expression(alert(1))'" ).toLowerCase(), not( containsString( "filter" ) ) ); assertThat( sanitize.css( "font-family: 'foo\\27\\3b color\\3a expression\\28 alert('XSS'))/* ,bar'" ).toLowerCase(), not( containsString( "font-family" ) ) ); } @Test public void sanitizeJson() { assertThat( sanitize.json( "\uffef\u000042\u0008\ud800\uffff\udc00" ), equalTo( "42" ) ); } @Test public void encodeForHtml() { assertThat( sanitize.forHtml( ">" ), equalTo( ">" ) ); } @Test public void encodeForCss() { assertThat( sanitize.forCssString( "'1" ), equalTo( "\\27 1" ) ); assertThat( sanitize.forCssString( "'x" ), equalTo( "\\27x" ) ); assertThat( sanitize.forCssString( "\0" ), equalTo( "\\0" ) ); assertThat( sanitize.forCssUrl( "'1" ), equalTo( "\\27 1" ) ); assertThat( sanitize.forCssUrl( "'x" ), equalTo( "\\27x" ) ); assertThat( sanitize.forCssUrl( "\0" ), equalTo( "\\0" ) ); } @Test public void encodeForJavascript() { assertThat( sanitize.forJavascript( "\'" ), equalTo( "\\x27" ) ); } @Test public void encodeForXml() { assertThat( sanitize.forXml( "unencoded & encoded" ), equalTo( "unencoded & encoded" ) ); assertThat( sanitize.forCDATA( "]]>" ), equalTo( "]]>]]<![CDATA[>" ) ); } private static <T> Matcher<T> noneOf( Matcher<? super T>... matchers ) { return not( anyOf( matchers ) ); } }