This demo application partially covers the vulnerability CVE-2024-38828

This commit is contained in:
FIRST ROMAN
2025-04-15 02:01:54 +03:00
parent ba879728ea
commit 40127e7e62
12 changed files with 31156 additions and 0 deletions

89
pom.xml Normal file
View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.memlastic</groupId>
<artifactId>app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>app</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>

View File

@@ -0,0 +1,13 @@
package com.memlastic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

View File

@@ -0,0 +1,26 @@
package com.memlastic.config;
import com.memlastic.converter.ByteArrayConverter;
import com.memlastic.properties.AppConfig;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* A configuration class that adapts the context of the spring application. Adds a custom version of the converter.
*/
@Data
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AppConfig appConfig;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new ByteArrayConverter(appConfig.getLimit()));
}
}

View File

@@ -0,0 +1,17 @@
package com.memlastic.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* A simple controller
*/
@RestController
public class TestController {
@PostMapping("/upload")
public String upload(@RequestBody byte[] data) {
return "Ok";
}
}

View File

@@ -0,0 +1,54 @@
package com.memlastic.converter;
import com.memlastic.exception.DataLimitExceededException;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* A converter with improved logic for checking the maximum allowable size of the received file,
* which converts incoming content into an array of bytes.
*/
public class ByteArrayConverter extends AbstractHttpMessageConverter<byte[]> {
private final Integer limit;
public ByteArrayConverter(Integer limit) {
super(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL);
this.limit = limit;
}
@Override
public boolean supports(Class<?> clazz) {
return byte[].class == clazz;
}
@Override
public byte[] readInternal(Class<? extends byte[]> clazz, HttpInputMessage inputMessage) throws IOException {
long contentLength = inputMessage.getHeaders().getContentLength();
if (contentLength > limit) {
throw new DataLimitExceededException("Exceeding the limit");
}
ByteArrayOutputStream bos =
new ByteArrayOutputStream(contentLength >= 0 ? (int) contentLength : StreamUtils.BUFFER_SIZE);
StreamUtils.copy(inputMessage.getBody(), bos);
return bos.toByteArray();
}
@Override
protected Long getContentLength(byte[] bytes, @Nullable MediaType contentType) {
return (long) bytes.length;
}
@Override
protected void writeInternal(byte[] bytes, HttpOutputMessage outputMessage) throws IOException {
StreamUtils.copy(bytes, outputMessage.getBody());
}
}

View File

@@ -0,0 +1,9 @@
package com.memlastic.exception;
import java.io.IOException;
public class DataLimitExceededException extends IOException {
public DataLimitExceededException(String text){
super(text);
}
}

View File

@@ -0,0 +1,21 @@
package com.memlastic.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.annotation.PostConstruct;
/**
* A configuration class that sets up file upload limits in mb
*/
@Data
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private Integer limit = 1;
@PostConstruct
void postConstructor() {
limit = limit * 1024;
limit = limit * 1024;
}
}

View File

@@ -0,0 +1,2 @@
app:
limit: 1

View File

@@ -0,0 +1,47 @@
package com.memlastic.controller;
import com.memlastic.exception.DataLimitExceededException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class TestControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testUploadTextPlainSuccess() throws Exception {
mockMvc.perform(post("/upload").content("This text message is designed specifically for the test"))
.andExpect(status().isOk())
.andExpect(content().string("Ok"));
}
@Test
void testUploadFileSuccess() throws Exception {
ClassLoader classLoader = TestControllerTest.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("smallFile.json");
mockMvc.perform(post("/upload").content(inputStream.readAllBytes()))
.andExpect(status().isOk())
.andExpect(content().string("Ok"));
}
@Test
void testUploadFileFailed() throws Exception {
ClassLoader classLoader = TestControllerTest.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("bigFile.json");
mockMvc.perform(post("/upload").content(inputStream.readAllBytes()))
.andExpect(result -> assertTrue(result.getResolvedException().fillInStackTrace().getCause() instanceof DataLimitExceededException));
}
}

View File

@@ -0,0 +1,2 @@
app:
limit: 1

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff