diff --git a/pippo-content-type-parent/pippo-xstream/src/main/java/ro/pippo/xstream/XstreamEngine.java b/pippo-content-type-parent/pippo-xstream/src/main/java/ro/pippo/xstream/XstreamEngine.java index 2922c1209..39f2ce2c9 100644 --- a/pippo-content-type-parent/pippo-xstream/src/main/java/ro/pippo/xstream/XstreamEngine.java +++ b/pippo-content-type-parent/pippo-xstream/src/main/java/ro/pippo/xstream/XstreamEngine.java @@ -15,6 +15,7 @@ */ package ro.pippo.xstream; +import com.thoughtworks.xstream.security.NoTypePermission; import org.kohsuke.MetaInfServices; import ro.pippo.core.Application; import ro.pippo.core.ContentTypeEngine; @@ -49,6 +50,9 @@ private XStream xstream() { // prevent xstream from creating complex XML graphs xstream.setMode(XStream.NO_REFERENCES); + // clear out existing permissions and set own ones + xstream.addPermission(NoTypePermission.NONE); + //setup security xstream.allowTypes((String[]) WhitelistObjectInputStream.getWhitelistedClassNames().toArray()); xstream.allowTypesByRegExp((Pattern[]) WhitelistObjectInputStream.getWhitelistedRegExp().toArray()); diff --git a/pippo-core/src/main/java/ro/pippo/core/gzip/GZipFilter.java b/pippo-core/src/main/java/ro/pippo/core/gzip/GZipFilter.java index 45ea1b46c..24ec5514f 100644 --- a/pippo-core/src/main/java/ro/pippo/core/gzip/GZipFilter.java +++ b/pippo-core/src/main/java/ro/pippo/core/gzip/GZipFilter.java @@ -15,6 +15,8 @@ */ package ro.pippo.core.gzip; +import ro.pippo.core.util.StringUtils; + import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -62,7 +64,9 @@ public void destroy() { protected boolean acceptsGZipEncoding(HttpServletRequest request) { String acceptEncoding = request.getHeader("accept-encoding"); - return acceptEncoding != null && acceptEncoding.contains("gzip"); + return !StringUtils.isNullOrEmpty(acceptEncoding) && ( + acceptEncoding.contains("gzip") || acceptEncoding.contains("*") + ); } } diff --git a/pippo-core/src/main/java/ro/pippo/core/gzip/GZipRequestResponseFactory.java b/pippo-core/src/main/java/ro/pippo/core/gzip/GZipRequestResponseFactory.java index ee423f5f9..0fcd0f79e 100644 --- a/pippo-core/src/main/java/ro/pippo/core/gzip/GZipRequestResponseFactory.java +++ b/pippo-core/src/main/java/ro/pippo/core/gzip/GZipRequestResponseFactory.java @@ -20,6 +20,7 @@ import ro.pippo.core.RequestResponse; import ro.pippo.core.RequestResponseFactory; import ro.pippo.core.Response; +import ro.pippo.core.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -53,7 +54,9 @@ public RequestResponse createRequestResponse(HttpServletRequest httpServletReque protected boolean acceptsGZipEncoding(HttpServletRequest httpServletRequest) { String acceptEncoding = httpServletRequest.getHeader("accept-encoding"); - return acceptEncoding != null && acceptEncoding.contains("gzip"); + return !StringUtils.isNullOrEmpty(acceptEncoding) && ( + acceptEncoding.contains("gzip") || acceptEncoding.contains("*") + ); } } diff --git a/pippo-core/src/main/java/ro/pippo/core/util/WhitelistObjectInputStream.java b/pippo-core/src/main/java/ro/pippo/core/util/WhitelistObjectInputStream.java index 8d3ae6f00..fae53f7f2 100644 --- a/pippo-core/src/main/java/ro/pippo/core/util/WhitelistObjectInputStream.java +++ b/pippo-core/src/main/java/ro/pippo/core/util/WhitelistObjectInputStream.java @@ -22,8 +22,8 @@ import java.io.InvalidClassException; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import java.util.regex.Pattern; /** @@ -33,8 +33,8 @@ */ public class WhitelistObjectInputStream extends ObjectInputStream { - private static List whiteClassNames; - private static List whiteRegExp; + private static Set whiteClassNames; + private static Set whiteRegEx; static { loadWhitelist(WhitelistObjectInputStream.class.getResourceAsStream(PippoConstants.LOCATION_OF_PIPPO_WHITELIST_SERIALIZATION)); @@ -46,25 +46,23 @@ public WhitelistObjectInputStream(InputStream in) throws IOException { protected Class resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException { String className = descriptor.getName(); - if ((!isWhiteListed(className)) && (!isWhiteListedRegex(className))) { + if (!isWhiteClass(className)) { throw new InvalidClassException("Unauthorized deserialization attempt", className); } return super.resolveClass(descriptor); } - private boolean isWhiteListed(String className) { + private boolean isWhiteClass(String className) { + // check in list with white classes for (String name : whiteClassNames) { if (name.equals(className)) { return true; } } - return false; - } - - private boolean isWhiteListedRegex(String className) { - for (Pattern pattern : whiteRegExp) { + // check in list with white regex + for (Pattern pattern : whiteRegEx) { if (pattern.matcher(className).matches()) { return true; } @@ -80,8 +78,8 @@ private boolean isWhiteListedRegex(String className) { * # Java * java.util.ArrayList * java.util.HashMap - * A regular expression whitelisting the whole java.lang package and its sub-packages. - * /java.lang.* / + * # A regular expression whitelisting the whole "java.lang" package and its sub-packages + * >java.lang.* * * # Pippo * ro.pippo.session.DefaultSessionData @@ -89,7 +87,7 @@ private boolean isWhiteListedRegex(String className) { * } * * A line that starts with {@code #} is a comment and will be ignored. - * A line that starts and ends with {@code /} is interpreted as a regular expression. + * A line that starts with {@code >} is interpreted as a regular expression. */ private static void loadWhitelist(InputStream input) { String content; @@ -99,18 +97,24 @@ private static void loadWhitelist(InputStream input) { throw new RuntimeException("Error loading the whitelist input", e); } - whiteClassNames = new ArrayList<>(); + whiteClassNames = new HashSet<>(); + whiteRegEx = new HashSet<>(); String[] lines = content.split("[\\r\\n]+"); for (String line : lines) { + // check for comment if (line.startsWith("#")) { // it's a comment; ignore line continue; - } else if (line.startsWith("/") && (line.endsWith("/"))) { - addWhiteRegExp(Pattern.compile(line.substring(1, line.length() - 2))); } - addWhiteClassName(line); + if (line.startsWith(">")) { + // it's a regexp + addWhiteRegEx(line.substring(1).trim()); + } else { + // it's a regular (full) class name + addWhiteClassName(line.trim()); + } } } @@ -118,24 +122,26 @@ private static void addWhiteClassName(String className) { whiteClassNames.add(className); } - private static void addWhiteRegExp(Pattern pattern) { - whiteRegExp.add(pattern); + private static void addWhiteRegEx(String regex) { + whiteRegEx.add(Pattern.compile(regex)); } /** - * Returns the whitelisted class names. - * @return the whitelisted class names. + * Returns the white class names. + * + * @return the white class names. */ - public static List getWhitelistedClassNames() { - return whiteClassNames; + public static String[] getWhiteClassNames() { + return whiteClassNames.toArray(new String[0]); } /** - * Returns the whitelisted regular expressions. - * @return the whitelisted regular expressions. + * Returns the white regular expressions. + * + * @return the white regular expressions. */ - public static List getWhitelistedRegExp() { - return whiteRegExp; + public static Pattern[] getWhiteRegEx() { + return whiteRegEx.toArray(new Pattern[0]); } } diff --git a/pippo-core/src/test/java/ro/pippo/core/gzip/GZipRequestResponseFactoryTest.java b/pippo-core/src/test/java/ro/pippo/core/gzip/GZipRequestResponseFactoryTest.java new file mode 100644 index 000000000..4aebf4cf4 --- /dev/null +++ b/pippo-core/src/test/java/ro/pippo/core/gzip/GZipRequestResponseFactoryTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 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 ro.pippo.core.gzip; + +import org.junit.Test; +import org.mockito.Mockito; +import ro.pippo.core.Application; + +import javax.servlet.http.HttpServletRequest; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GZipRequestResponseFactoryTest { + + @Test + public void testAcceptGzipEncoding() { + GZipRequestResponseFactory requestResponseFactory = new GZipRequestResponseFactory(new Application()); + HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); + + // Accept-Encoding not specified + Mockito.doReturn(null).when(httpServletRequest).getHeader("accept-encoding"); + assertFalse(requestResponseFactory.acceptsGZipEncoding(httpServletRequest)); + + // Accept-Encoding specified + Mockito.doReturn("gzip,deflate,identity").when(httpServletRequest).getHeader("accept-encoding"); + assertTrue(requestResponseFactory.acceptsGZipEncoding(httpServletRequest)); + + // Explicit Accept-Encoding:* + Mockito.doReturn("*").when(httpServletRequest).getHeader("accept-encoding"); + assertTrue(requestResponseFactory.acceptsGZipEncoding(httpServletRequest)); + + // No Gzip + Mockito.doReturn("deflate,identity").when(httpServletRequest).getHeader("accept-encoding"); + assertFalse(requestResponseFactory.acceptsGZipEncoding(httpServletRequest)); + } +}