From 5e461ad43aeb0b98686ac6b4962c4329765f1241 Mon Sep 17 00:00:00 2001 From: Shloka Mahesheka Date: Tue, 18 Oct 2022 15:37:50 +0530 Subject: [PATCH] Added test cases for Preflight Controller. --- .../internal/utility/FileReader.java | 29 +++++++ .../fileupload/PreflightController.java | 26 ++++-- .../fileupload/UnrestrictedFileUpload.java | 82 ++++++++--------- .../fileUpload/PreflightControllerTest.java | 87 +++++++++++++++++++ 4 files changed, 175 insertions(+), 49 deletions(-) create mode 100644 src/main/java/org/sasanlabs/internal/utility/FileReader.java create mode 100644 src/test/java/org/sasanlabs/service/vulnerability/fileUpload/PreflightControllerTest.java diff --git a/src/main/java/org/sasanlabs/internal/utility/FileReader.java b/src/main/java/org/sasanlabs/internal/utility/FileReader.java new file mode 100644 index 00000000..90ceb8cd --- /dev/null +++ b/src/main/java/org/sasanlabs/internal/utility/FileReader.java @@ -0,0 +1,29 @@ +package org.sasanlabs.internal.utility; + +import org.sasanlabs.service.vulnerability.fileupload.UnrestrictedFileUpload; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + + +public class FileReader { + + private UnrestrictedFileUpload unrestrictedFileUpload; + + public FileReader(UnrestrictedFileUpload unrestrictedFileUpload) { + this.unrestrictedFileUpload = unrestrictedFileUpload; + } + + + public InputStream readAsStream(String fileName) throws FileNotFoundException { + return ( + new FileInputStream( + unrestrictedFileUpload.getContentDispositionRoot().toFile() + + FrameworkConstants.SLASH + + fileName)); + } + +} + diff --git a/src/main/java/org/sasanlabs/service/vulnerability/fileupload/PreflightController.java b/src/main/java/org/sasanlabs/service/vulnerability/fileupload/PreflightController.java index 95fae699..031c7d5c 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/fileupload/PreflightController.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/fileupload/PreflightController.java @@ -3,10 +3,13 @@ import static org.sasanlabs.service.vulnerability.fileupload.UnrestrictedFileUpload.CONTENT_DISPOSITION_STATIC_FILE_LOCATION; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.IOUtils; +import org.sasanlabs.internal.utility.FileReader; import org.sasanlabs.internal.utility.FrameworkConstants; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -24,24 +27,31 @@ */ @RestController public class PreflightController { - private UnrestrictedFileUpload unrestrictedFileUpload; + //private UnrestrictedFileUpload unrestrictedFileUpload; - public PreflightController(UnrestrictedFileUpload unrestrictedFileUpload) { - this.unrestrictedFileUpload = unrestrictedFileUpload; + private FileReader fileReader; + + + + public PreflightController(FileReader fileReader) { + this.fileReader=fileReader; +// this.unrestrictedFileUpload = unrestrictedFileUpload; } @RequestMapping( CONTENT_DISPOSITION_STATIC_FILE_LOCATION + FrameworkConstants.SLASH + "{fileName}") public ResponseEntity fetchFile(@PathVariable("fileName") String fileName) throws IOException { - InputStream inputStream = - new FileInputStream( - unrestrictedFileUpload.getContentDispositionRoot().toFile() - + FrameworkConstants.SLASH - + fileName); + InputStream inputStream = fileReader.readAsStream(fileName); +// new FileInputStream( +// unrestrictedFileUpload.getContentDispositionRoot().toFile() +// + FrameworkConstants.SLASH +// + fileName); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION, "attachment"); return new ResponseEntity( IOUtils.toByteArray(inputStream), httpHeaders, HttpStatus.OK); } + + } diff --git a/src/main/java/org/sasanlabs/service/vulnerability/fileupload/UnrestrictedFileUpload.java b/src/main/java/org/sasanlabs/service/vulnerability/fileupload/UnrestrictedFileUpload.java index 0e05276f..9542036d 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/fileupload/UnrestrictedFileUpload.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/fileupload/UnrestrictedFileUpload.java @@ -47,7 +47,7 @@ public class UnrestrictedFileUpload { private Path contentDispositionRoot; private static final String STATIC_FILE_LOCATION = "upload"; - static final String CONTENT_DISPOSITION_STATIC_FILE_LOCATION = "contentDispositionUpload"; + public static final String CONTENT_DISPOSITION_STATIC_FILE_LOCATION = "contentDispositionUpload"; private static final String BASE_PATH = "static"; private static final String REQUEST_PARAMETER = "file"; private static final Random RANDOM = new Random(new Date().getTime()); @@ -69,9 +69,9 @@ public UnrestrictedFileUpload() throws IOException, URISyntaxException { uploadDirectoryURI = new URI( Thread.currentThread() - .getContextClassLoader() - .getResource(BASE_PATH) - .toURI() + .getContextClassLoader() + .getResource(BASE_PATH) + .toURI() + FrameworkConstants.SLASH + STATIC_FILE_LOCATION); root = Paths.get(uploadDirectoryURI); @@ -102,14 +102,14 @@ public UnrestrictedFileUpload() throws IOException, URISyntaxException { } private static final ResponseEntity> - genericFileUploadUtility( - Path root, - String fileName, - Supplier validator, - MultipartFile file, - boolean htmlEncode, - boolean isContentDisposition) - throws IOException { + genericFileUploadUtility( + Path root, + String fileName, + Supplier validator, + MultipartFile file, + boolean htmlEncode, + boolean isContentDisposition) + throws IOException { if (validator.get()) { Files.copy( file.getInputStream(), @@ -122,8 +122,8 @@ public UnrestrictedFileUpload() throws IOException, URISyntaxException { FrameworkConstants.VULNERABLE_APP + FrameworkConstants.SLASH + (isContentDisposition - ? CONTENT_DISPOSITION_STATIC_FILE_LOCATION - : STATIC_FILE_LOCATION) + ? CONTENT_DISPOSITION_STATIC_FILE_LOCATION + : STATIC_FILE_LOCATION) + FrameworkConstants.SLASH + fileName); } else { @@ -131,8 +131,8 @@ public UnrestrictedFileUpload() throws IOException, URISyntaxException { FrameworkConstants.VULNERABLE_APP + FrameworkConstants.SLASH + (isContentDisposition - ? CONTENT_DISPOSITION_STATIC_FILE_LOCATION - : STATIC_FILE_LOCATION) + ? CONTENT_DISPOSITION_STATIC_FILE_LOCATION + : STATIC_FILE_LOCATION) + FrameworkConstants.SLASH + fileName; } @@ -145,17 +145,17 @@ public UnrestrictedFileUpload() throws IOException, URISyntaxException { HttpStatus.OK); } - Path getContentDispositionRoot() { + public Path getContentDispositionRoot() { return contentDispositionRoot; } // file name reflected and stored is there. @AttackVector( vulnerabilityExposed = { - VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, - VulnerabilityType.PERSISTENT_XSS, - VulnerabilityType.REFLECTED_XSS, - VulnerabilityType.PATH_TRAVERSAL + VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, + VulnerabilityType.PERSISTENT_XSS, + VulnerabilityType.REFLECTED_XSS, + VulnerabilityType.PATH_TRAVERSAL }, description = "UNRESTRICTED_FILE_UPLOAD_NO_VALIDATION_FILE_NAME", payload = "UNRESTRICTED_FILE_UPLOAD_PAYLOAD_LEVEL_1") @@ -173,9 +173,9 @@ public ResponseEntity> getVulnerablePay // file name reflected and stored is there. @AttackVector( vulnerabilityExposed = { - VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, - VulnerabilityType.PERSISTENT_XSS, - VulnerabilityType.REFLECTED_XSS + VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, + VulnerabilityType.PERSISTENT_XSS, + VulnerabilityType.REFLECTED_XSS }, description = "UNRESTRICTED_FILE_UPLOAD_NO_VALIDATION_FILE_NAME", payload = "UNRESTRICTED_FILE_UPLOAD_PAYLOAD_LEVEL_2") @@ -193,9 +193,9 @@ public ResponseEntity> getVulnerablePay // .htm extension breaks the file upload vulnerability @AttackVector( vulnerabilityExposed = { - VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, - VulnerabilityType.PERSISTENT_XSS, - VulnerabilityType.REFLECTED_XSS + VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, + VulnerabilityType.PERSISTENT_XSS, + VulnerabilityType.REFLECTED_XSS }, description = "UNRESTRICTED_FILE_UPLOAD_IF_NOT_HTML_FILE_EXTENSION", payload = "UNRESTRICTED_FILE_UPLOAD_PAYLOAD_LEVEL_3") @@ -219,9 +219,9 @@ public ResponseEntity> getVulnerablePay @AttackVector( vulnerabilityExposed = { - VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, - VulnerabilityType.PERSISTENT_XSS, - VulnerabilityType.REFLECTED_XSS + VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, + VulnerabilityType.PERSISTENT_XSS, + VulnerabilityType.REFLECTED_XSS }, description = "UNRESTRICTED_FILE_UPLOAD_IF_NOT_HTML_NOT_HTM_FILE_EXTENSION", payload = "UNRESTRICTED_FILE_UPLOAD_PAYLOAD_LEVEL_4") @@ -245,9 +245,9 @@ public ResponseEntity> getVulnerablePay @AttackVector( vulnerabilityExposed = { - VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, - VulnerabilityType.PERSISTENT_XSS, - VulnerabilityType.REFLECTED_XSS + VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, + VulnerabilityType.PERSISTENT_XSS, + VulnerabilityType.REFLECTED_XSS }, description = "UNRESTRICTED_FILE_UPLOAD_IF_NOT_HTML_NOT_HTM_FILE_EXTENSION_CASE_INSENSITIVE", @@ -276,9 +276,9 @@ public ResponseEntity> getVulnerablePay // WhiteList approach @AttackVector( vulnerabilityExposed = { - VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, - VulnerabilityType.PERSISTENT_XSS, - VulnerabilityType.REFLECTED_XSS + VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, + VulnerabilityType.PERSISTENT_XSS, + VulnerabilityType.REFLECTED_XSS }, description = "UNRESTRICTED_FILE_UPLOAD_IF_FILE_NAME_NOT_CONTAINS_.PNG_OR_.JPEG_CASE_INSENSITIVE", @@ -305,9 +305,9 @@ public ResponseEntity> getVulnerablePay // Null Byte Attack @AttackVector( vulnerabilityExposed = { - VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, - VulnerabilityType.PERSISTENT_XSS, - VulnerabilityType.REFLECTED_XSS + VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, + VulnerabilityType.PERSISTENT_XSS, + VulnerabilityType.REFLECTED_XSS }, description = "UNRESTRICTED_FILE_UPLOAD_IF_FILE_NAME_NOT_ENDS_WITH_.PNG_OR_.JPEG_CASE_INSENSITIVE_AND_FILE_NAMES_CONSIDERED_BEFORE_NULL_BYTE", @@ -355,8 +355,8 @@ public ResponseEntity> getVulnerablePay // ZAP FileUpload Addon. @AttackVector( vulnerabilityExposed = { - VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, - VulnerabilityType.PERSISTENT_XSS + VulnerabilityType.UNRESTRICTED_FILE_UPLOAD, + VulnerabilityType.PERSISTENT_XSS }, description = "UNRESTRICTED_FILE_UPLOAD_IF_FILE_NAME_NOT_ENDS_WITH_.PNG_OR_.JPEG_CASE_INSENSITIVE") diff --git a/src/test/java/org/sasanlabs/service/vulnerability/fileUpload/PreflightControllerTest.java b/src/test/java/org/sasanlabs/service/vulnerability/fileUpload/PreflightControllerTest.java new file mode 100644 index 00000000..b0a4fc7a --- /dev/null +++ b/src/test/java/org/sasanlabs/service/vulnerability/fileUpload/PreflightControllerTest.java @@ -0,0 +1,87 @@ +package org.sasanlabs.service.vulnerability.commandInjection.fileUpload; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sasanlabs.internal.utility.FileReader; +import org.sasanlabs.internal.utility.FrameworkConstants; +import org.sasanlabs.service.vulnerability.fileupload.PreflightController; +import org.sasanlabs.service.vulnerability.fileupload.UnrestrictedFileUpload; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sasanlabs.service.vulnerability.fileupload.UnrestrictedFileUpload.CONTENT_DISPOSITION_STATIC_FILE_LOCATION; + +public class PreflightControllerTest { + + @Mock + UnrestrictedFileUpload unrestrictedFileUpload; + @Mock + FileReader fileReader; + String fileName = "test.txt"; + private PreflightController preflightController; + private Path contentDispositionRoot; + + private Path textFilePath = Paths.get(fileName); + + + @BeforeEach + void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + Files.createFile(textFilePath); + } + + @AfterEach + void destroy() throws IOException { + Files.delete(textFilePath); + } + + + @Test + void shouldBeAbleToFetchFile() throws IOException { + + //Arrange + preflightController = new PreflightController(fileReader); + contentDispositionRoot = + Paths.get( + FrameworkConstants.SLASH + + CONTENT_DISPOSITION_STATIC_FILE_LOCATION + + FrameworkConstants.SLASH); + + when(unrestrictedFileUpload.getContentDispositionRoot()).thenReturn(contentDispositionRoot); + when(fileReader.readAsStream(fileName)).thenReturn(new ByteArrayInputStream("test".getBytes())); + + //Act + ResponseEntity responseEntity = preflightController.fetchFile(fileName); + + //Assert + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + verify(fileReader).readAsStream(fileName); + } + + @Test + void shouldThrowException() throws IOException, URISyntaxException { + + //Arrange + FileReader fileReader1 = new FileReader(new UnrestrictedFileUpload()); + preflightController = new PreflightController(fileReader1); + String anotherFileName = "test"; + + //Act and Assert + assertThrows(IOException.class, () -> preflightController.fetchFile(anotherFileName)); + } + +}