Protect against RFD exploits
Issue: SPR-13548
This commit is contained in:
committed by
Stephane Nicoll
parent
161fd98656
commit
2bd1daa75e
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -53,7 +54,8 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.FlashMap;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RequestMappingHandlerAdapter}.
|
||||
@@ -128,7 +130,7 @@ public class RequestMappingHandlerAdapterTests {
|
||||
HandlerMethodReturnValueHandler viewHandler = new ViewNameMethodReturnValueHandler();
|
||||
|
||||
this.handlerAdapter.setArgumentResolvers(Arrays.asList(redirectAttributesResolver, modelResolver));
|
||||
this.handlerAdapter.setReturnValueHandlers(Arrays.asList(viewHandler));
|
||||
this.handlerAdapter.setReturnValueHandlers(Collections.singletonList(viewHandler));
|
||||
this.handlerAdapter.setIgnoreDefaultModelOnRedirect(true);
|
||||
this.handlerAdapter.afterPropertiesSet();
|
||||
|
||||
@@ -143,7 +145,7 @@ public class RequestMappingHandlerAdapterTests {
|
||||
@Test
|
||||
public void setCustomArgumentResolvers() throws Exception {
|
||||
HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
|
||||
this.handlerAdapter.setCustomArgumentResolvers(Arrays.asList(resolver));
|
||||
this.handlerAdapter.setCustomArgumentResolvers(Collections.singletonList(resolver));
|
||||
this.handlerAdapter.afterPropertiesSet();
|
||||
|
||||
assertTrue(this.handlerAdapter.getArgumentResolvers().contains(resolver));
|
||||
@@ -153,7 +155,7 @@ public class RequestMappingHandlerAdapterTests {
|
||||
@Test
|
||||
public void setArgumentResolvers() throws Exception {
|
||||
HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
|
||||
this.handlerAdapter.setArgumentResolvers(Arrays.asList(resolver));
|
||||
this.handlerAdapter.setArgumentResolvers(Collections.singletonList(resolver));
|
||||
this.handlerAdapter.afterPropertiesSet();
|
||||
|
||||
assertMethodProcessorCount(1, INIT_BINDER_RESOLVER_COUNT, HANDLER_COUNT);
|
||||
@@ -162,7 +164,7 @@ public class RequestMappingHandlerAdapterTests {
|
||||
@Test
|
||||
public void setInitBinderArgumentResolvers() throws Exception {
|
||||
HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
|
||||
this.handlerAdapter.setInitBinderArgumentResolvers(Arrays.<HandlerMethodArgumentResolver>asList(resolver));
|
||||
this.handlerAdapter.setInitBinderArgumentResolvers(Collections.singletonList(resolver));
|
||||
this.handlerAdapter.afterPropertiesSet();
|
||||
|
||||
assertMethodProcessorCount(RESOLVER_COUNT, 1, HANDLER_COUNT);
|
||||
@@ -171,7 +173,7 @@ public class RequestMappingHandlerAdapterTests {
|
||||
@Test
|
||||
public void setCustomReturnValueHandlers() {
|
||||
HandlerMethodReturnValueHandler handler = new ViewNameMethodReturnValueHandler();
|
||||
this.handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(handler));
|
||||
this.handlerAdapter.setCustomReturnValueHandlers(Collections.singletonList(handler));
|
||||
this.handlerAdapter.afterPropertiesSet();
|
||||
|
||||
assertTrue(this.handlerAdapter.getReturnValueHandlers().contains(handler));
|
||||
@@ -181,7 +183,7 @@ public class RequestMappingHandlerAdapterTests {
|
||||
@Test
|
||||
public void setReturnValueHandlers() {
|
||||
HandlerMethodReturnValueHandler handler = new ModelMethodProcessor();
|
||||
this.handlerAdapter.setReturnValueHandlers(Arrays.asList(handler));
|
||||
this.handlerAdapter.setReturnValueHandlers(Collections.singletonList(handler));
|
||||
this.handlerAdapter.afterPropertiesSet();
|
||||
|
||||
assertMethodProcessorCount(RESOLVER_COUNT, INIT_BINDER_RESOLVER_COUNT, 1);
|
||||
@@ -240,20 +242,37 @@ public class RequestMappingHandlerAdapterTests {
|
||||
this.handlerAdapter.setMessageConverters(converters);
|
||||
|
||||
this.webAppContext.registerSingleton("rba", ResponseCodeSuppressingAdvice.class);
|
||||
this.webAppContext.registerSingleton("ja", JsonpAdvice.class);
|
||||
this.webAppContext.refresh();
|
||||
|
||||
this.request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE);
|
||||
this.request.setParameter("c", "callback");
|
||||
|
||||
HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handleWithResponseEntity");
|
||||
HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handleBadRequest");
|
||||
this.handlerAdapter.afterPropertiesSet();
|
||||
this.handlerAdapter.handle(this.request, this.response, handlerMethod);
|
||||
|
||||
assertEquals(200, this.response.getStatus());
|
||||
assertEquals("callback({\"status\":400,\"message\":\"body\"});", this.response.getContentAsString());
|
||||
assertEquals("{\"status\":400,\"message\":\"body\"}", this.response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonpResponseBodyAdvice() throws Exception {
|
||||
|
||||
List<HttpMessageConverter<?>> converters = new ArrayList<>();
|
||||
converters.add(new MappingJackson2HttpMessageConverter());
|
||||
this.handlerAdapter.setMessageConverters(converters);
|
||||
|
||||
this.webAppContext.registerSingleton("jsonpAdvice", JsonpAdvice.class);
|
||||
this.webAppContext.refresh();
|
||||
|
||||
testJsonp("callback", true);
|
||||
testJsonp("_callback", true);
|
||||
testJsonp("_Call.bAcK", true);
|
||||
testJsonp("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.", true);
|
||||
|
||||
testJsonp("<script>", false);
|
||||
testJsonp("!foo!bar", false);
|
||||
}
|
||||
|
||||
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
|
||||
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
|
||||
@@ -266,6 +285,26 @@ public class RequestMappingHandlerAdapterTests {
|
||||
assertEquals(handlerCount, this.handlerAdapter.getReturnValueHandlers().size());
|
||||
}
|
||||
|
||||
private void testJsonp(String value, boolean validValue) throws Exception {
|
||||
|
||||
this.request = new MockHttpServletRequest("GET", "/");
|
||||
this.request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE);
|
||||
this.request.setParameter("c", value);
|
||||
this.response = new MockHttpServletResponse();
|
||||
|
||||
HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handleWithResponseEntity");
|
||||
this.handlerAdapter.afterPropertiesSet();
|
||||
this.handlerAdapter.handle(this.request, this.response, handlerMethod);
|
||||
|
||||
assertEquals(200, this.response.getStatus());
|
||||
if (validValue) {
|
||||
assertEquals("/**/" + value + "({\"foo\":\"bar\"});", this.response.getContentAsString());
|
||||
}
|
||||
else {
|
||||
assertEquals("{\"foo\":\"bar\"}", this.response.getContentAsString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class SimpleController {
|
||||
@@ -279,9 +318,15 @@ public class RequestMappingHandlerAdapterTests {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ResponseEntity<String> handleWithResponseEntity() {
|
||||
public ResponseEntity<Map<String, String>> handleWithResponseEntity() {
|
||||
return new ResponseEntity<Map<String, String>>(Collections.singletonMap(
|
||||
"foo", "bar"), HttpStatus.OK);
|
||||
}
|
||||
|
||||
public ResponseEntity<String> handleBadRequest() {
|
||||
return new ResponseEntity<String>("body", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -355,12 +400,12 @@ public class RequestMappingHandlerAdapterTests {
|
||||
}
|
||||
}
|
||||
|
||||
@ControllerAdvice
|
||||
private static class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
|
||||
@ControllerAdvice
|
||||
private static class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
|
||||
|
||||
public JsonpAdvice() {
|
||||
super("c");
|
||||
public JsonpAdvice() {
|
||||
super("c");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
@@ -56,6 +54,7 @@ import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -68,6 +67,13 @@ import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Test fixture for a {@link RequestResponseBodyMethodProcessor} with
|
||||
@@ -326,6 +332,37 @@ public class RequestResponseBodyMethodProcessorTests {
|
||||
processor.writeWithMessageConverters(new ByteArrayOutputStream(), returnType, this.webRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addContentDispositionHeader() throws Exception {
|
||||
|
||||
ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
|
||||
factory.addMediaType("pdf", new MediaType("application", "pdf"));
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
Collections.singletonList(new StringHttpMessageConverter()),
|
||||
factory.getObject());
|
||||
|
||||
assertContentDisposition(processor, false, "/hello.json", "whitelisted extension");
|
||||
assertContentDisposition(processor, false, "/hello.pdf", "registered extension");
|
||||
assertContentDisposition(processor, true, "/hello.dataless", "uknown extension");
|
||||
|
||||
// path parameters
|
||||
assertContentDisposition(processor, false, "/hello.json;a=b", "path param shouldn't cause issue");
|
||||
assertContentDisposition(processor, true, "/hello.json;a=b;setup.dataless", "uknown ext in path params");
|
||||
assertContentDisposition(processor, true, "/hello.dataless;a=b;setup.json", "uknown ext in filename");
|
||||
assertContentDisposition(processor, false, "/hello.json;a=b;setup.json", "whitelisted extensions");
|
||||
|
||||
// encoded dot
|
||||
assertContentDisposition(processor, true, "/hello%2Edataless;a=b;setup.json", "encoded dot in filename");
|
||||
assertContentDisposition(processor, true, "/hello.json;a=b;setup%2Edataless", "encoded dot in path params");
|
||||
assertContentDisposition(processor, true, "/hello.dataless%3Bsetup.bat", "encoded dot in path params");
|
||||
|
||||
this.servletRequest.setAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE, "/hello.bat");
|
||||
assertContentDisposition(processor, true, "/bonjour", "forwarded URL");
|
||||
this.servletRequest.removeAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsReturnTypeResponseBodyOnType() throws Exception {
|
||||
Method method = ResponseBodyController.class.getMethod("handle");
|
||||
@@ -598,6 +635,27 @@ public class RequestResponseBodyMethodProcessorTests {
|
||||
assertTrue(content.contains("\"name\":\"bar\""));
|
||||
}
|
||||
|
||||
private void assertContentDisposition(RequestResponseBodyMethodProcessor processor,
|
||||
boolean expectContentDisposition, String requestURI, String comment) throws Exception {
|
||||
|
||||
this.servletRequest.setRequestURI(requestURI);
|
||||
processor.handleReturnValue("body", this.returnTypeString, this.mavContainer, this.webRequest);
|
||||
|
||||
String header = servletResponse.getHeader("Content-Disposition");
|
||||
if (expectContentDisposition) {
|
||||
assertEquals("Expected 'Content-Disposition' header. Use case: '" + comment + "'",
|
||||
"attachment;filename=f.txt", header);
|
||||
}
|
||||
else {
|
||||
assertNull("Did not expect 'Content-Disposition' header. Use case: '" + comment + "'", header);
|
||||
}
|
||||
|
||||
this.servletRequest = new MockHttpServletRequest();
|
||||
this.servletResponse = new MockHttpServletResponse();
|
||||
this.webRequest = new ServletWebRequest(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
|
||||
|
||||
String handle(
|
||||
@RequestBody List<SimpleBean> list,
|
||||
|
||||
@@ -324,43 +324,14 @@ public class MappingJackson2JsonViewTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderWithJsonpDefaultParameterName() throws Exception {
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("foo", "bar");
|
||||
request.addParameter("otherparam", "value");
|
||||
request.addParameter("jsonp", "jsonpCallback");
|
||||
public void renderWithJsonp() throws Exception {
|
||||
testJsonp("jsonp", "callback", true);
|
||||
testJsonp("jsonp", "_callback", true);
|
||||
testJsonp("jsonp", "_Call.bAcK", true);
|
||||
testJsonp("jsonp", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.", true);
|
||||
|
||||
view.render(model, request, response);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
assertEquals("jsonpCallback({\"foo\":\"bar\"});", content);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderWithCallbackDefaultParameterName() throws Exception {
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("foo", "bar");
|
||||
request.addParameter("otherparam", "value");
|
||||
request.addParameter("callback", "jsonpCallback");
|
||||
|
||||
view.render(model, request, response);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
assertEquals("jsonpCallback({\"foo\":\"bar\"});", content);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderWithCustomJsonpParameterName() throws Exception {
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("foo", "bar");
|
||||
request.addParameter("otherparam", "value");
|
||||
request.addParameter("custom", "jsonpCallback");
|
||||
view.setJsonpParameterNames(new LinkedHashSet(Arrays.asList("jsonp", "callback", "custom")));
|
||||
|
||||
view.render(model, request, response);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
assertEquals("jsonpCallback({\"foo\":\"bar\"});", content);
|
||||
testJsonp("jsonp", "<script>", false);
|
||||
testJsonp("jsonp", "!foo!bar", false);
|
||||
}
|
||||
|
||||
private void validateResult() throws Exception {
|
||||
@@ -376,6 +347,26 @@ public class MappingJackson2JsonViewTests {
|
||||
assertEquals("application/json", response.getContentType());
|
||||
}
|
||||
|
||||
private void testJsonp(String paramName, String paramValue, boolean validValue) throws Exception {
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("foo", "bar");
|
||||
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request.addParameter("otherparam", "value");
|
||||
this.request.addParameter(paramName, paramValue);
|
||||
this.response = new MockHttpServletResponse();
|
||||
|
||||
this.view.render(model, this.request, this.response);
|
||||
|
||||
String content = this.response.getContentAsString();
|
||||
if (validValue) {
|
||||
assertEquals("/**/" + paramValue + "({\"foo\":\"bar\"});", content);
|
||||
}
|
||||
else {
|
||||
assertEquals("{\"foo\":\"bar\"}", content);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface MyJacksonView1 {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user