diff --git a/module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/server/HttpRestartServer.java b/module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/server/HttpRestartServer.java index 6eae1ef9a8f..508ffb8cfbe 100644 --- a/module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/server/HttpRestartServer.java +++ b/module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/server/HttpRestartServer.java @@ -17,12 +17,17 @@ package org.springframework.boot.devtools.restart.server; import java.io.IOException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; +import java.util.Map; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles; +import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceDirectory; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -40,6 +45,8 @@ public class HttpRestartServer { private static final Log logger = LogFactory.getLog(HttpRestartServer.class); + private final ObjectInputFilter inputFilter = new ClassLoaderFilesObjectInputFilter(); + private final RestartServer server; /** @@ -71,15 +78,38 @@ public class HttpRestartServer { try { Assert.state(request.getHeaders().getContentLength() > 0, "No content"); ObjectInputStream objectInputStream = new ObjectInputStream(request.getBody()); + objectInputStream.setObjectInputFilter(this.inputFilter); ClassLoaderFiles files = (ClassLoaderFiles) objectInputStream.readObject(); objectInputStream.close(); this.server.updateAndRestart(files); response.setStatusCode(HttpStatus.OK); } catch (Exception ex) { - logger.warn("Unable to handler restart server HTTP request", ex); + logger.warn("Unable to handle restart server HTTP request", ex); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); } } + private static final class ClassLoaderFilesObjectInputFilter implements ObjectInputFilter { + + private static final Set> PERMITTED_CLASSES = Set.of(ClassLoaderFiles.class, ClassLoaderFile.class, + ClassLoaderFile.Kind.class, SourceDirectory.class, java.lang.Enum.class, Map.Entry.class, byte.class); + + @Override + public Status checkInput(FilterInfo filterInfo) { + Class serialClass = filterInfo.serialClass(); + if (serialClass == null) { + return Status.UNDECIDED; + } + while (serialClass.isArray()) { + serialClass = serialClass.componentType(); + } + if (PERMITTED_CLASSES.contains(serialClass) || Map.class.isAssignableFrom(serialClass)) { + return Status.ALLOWED; + } + return Status.REJECTED; + } + + } + } diff --git a/module/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/server/HttpRestartServerTests.java b/module/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/server/HttpRestartServerTests.java index 536ad8e6bfa..2ce47ec0013 100644 --- a/module/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/server/HttpRestartServerTests.java +++ b/module/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/server/HttpRestartServerTests.java @@ -18,7 +18,9 @@ package org.springframework.boot.devtools.restart.server; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InvalidClassException; import java.io.ObjectOutputStream; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,6 +31,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.mock.web.MockHttpServletRequest; @@ -107,6 +111,20 @@ class HttpRestartServerTests { assertThat(response.getStatus()).isEqualTo(500); } + @Test + @ExtendWith(OutputCaptureExtension.class) + void sendBadSerializedData(CapturedOutput output) throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + byte[] bytes = serialize(List.of("not", "allowed")); + request.setContent(bytes); + this.server.handle(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response)); + then(this.delegate).shouldHaveNoInteractions(); + assertThat(response.getStatus()).isEqualTo(500); + assertThat(output).contains(InvalidClassException.class.getName()) + .doesNotContain(ClassCastException.class.getName()); + } + private byte[] serialize(Object object) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos);