feat: add path transversal security
This commit is contained in:
@@ -71,10 +71,18 @@ public class PhotoServiceImpl implements PhotoService {
|
|||||||
|
|
||||||
// ========= SAUVEGARDE ORIGINAL =========
|
// ========= SAUVEGARDE ORIGINAL =========
|
||||||
Path originalPath = uploadPath.resolve(uuid);
|
Path originalPath = uploadPath.resolve(uuid);
|
||||||
|
|
||||||
|
// ========= PROTECTION PATH TRAVERSAL =========
|
||||||
|
// Empêche un pirate d'envoyer ../../windows/system32 par exemple
|
||||||
|
FileValidator.validatePathTraversal(originalPath, uploadPath);
|
||||||
|
|
||||||
Files.write(originalPath, bytes);
|
Files.write(originalPath, bytes);
|
||||||
// ========= MINIATURE =========
|
// ========= MINIATURE =========
|
||||||
String thumbUuid = "thumb-" + uuid + ".jpg";
|
String thumbUuid = "thumb-" + uuid + ".jpg";
|
||||||
Path thumbPath = uploadPath.resolve(thumbUuid);
|
Path thumbPath = uploadPath.resolve(thumbUuid);
|
||||||
|
|
||||||
|
// ========= PROTECTION PATH TRAVERSAL POUR MINIATURE =========
|
||||||
|
FileValidator.validatePathTraversal(thumbPath, uploadPath);
|
||||||
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes)) {
|
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes)) {
|
||||||
net.coobird.thumbnailator.Thumbnails.of(bis)
|
net.coobird.thumbnailator.Thumbnails.of(bis)
|
||||||
.size(300, 300)
|
.size(300, 300)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ import java.util.List;
|
|||||||
* Utilitaire pour valider les fichiers uploadés
|
* Utilitaire pour valider les fichiers uploadés
|
||||||
* - Vérification du type MIME réel via Magic Numbers (Apache Tika)
|
* - Vérification du type MIME réel via Magic Numbers (Apache Tika)
|
||||||
* - Limitation de taille
|
* - Limitation de taille
|
||||||
|
* - Protection contre Path Traversal
|
||||||
*/
|
*/
|
||||||
public class FileValidator {
|
public class FileValidator {
|
||||||
|
|
||||||
@@ -85,6 +87,25 @@ public class FileValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valide que le chemin de destination est bien dans le répertoire autorisé
|
||||||
|
* Protection contre les attaques Path Traversal (ex: ../../windows/system32)
|
||||||
|
*
|
||||||
|
* @param destinationFile Le chemin du fichier de destination
|
||||||
|
* @param rootDirectory Le répertoire racine autorisé
|
||||||
|
* @throws InvalidFileException Si le chemin tente de sortir du répertoire autorisé
|
||||||
|
*/
|
||||||
|
public static void validatePathTraversal(Path destinationFile, Path rootDirectory) throws InvalidFileException {
|
||||||
|
Path normalizedDestination = destinationFile.normalize().toAbsolutePath();
|
||||||
|
Path normalizedRoot = rootDirectory.normalize().toAbsolutePath();
|
||||||
|
|
||||||
|
if (!normalizedDestination.startsWith(normalizedRoot)) {
|
||||||
|
throw new InvalidFileException(
|
||||||
|
"Chemin de fichier invalide : tentative de Path Traversal détectée"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception personnalisée pour les fichiers invalides
|
* Exception personnalisée pour les fichiers invalides
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package local.epul4a.fotosharing.util;
|
package local.epul4a.fotosharing.util;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class FileValidatorTest {
|
class FileValidatorTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testValidJpegFile() {
|
void testValidJpegFile() {
|
||||||
// JPEG Magic Number: FF D8 FF
|
// JPEG Magic Number: FF D8 FF
|
||||||
@@ -80,5 +86,39 @@ class FileValidatorTest {
|
|||||||
|
|
||||||
assertTrue(exception.getMessage().contains("vide"));
|
assertTrue(exception.getMessage().contains("vide"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPathTraversalAttackDetected() {
|
||||||
|
// Tentative de Path Traversal avec ../../
|
||||||
|
Path maliciousPath = tempDir.resolve("../../etc/passwd");
|
||||||
|
|
||||||
|
FileValidator.InvalidFileException exception = assertThrows(
|
||||||
|
FileValidator.InvalidFileException.class,
|
||||||
|
() -> FileValidator.validatePathTraversal(maliciousPath, tempDir)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("Path Traversal"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValidPathInsideRoot() {
|
||||||
|
// Chemin valide à l'intérieur du répertoire racine
|
||||||
|
Path validPath = tempDir.resolve("uploads/photo.jpg");
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> FileValidator.validatePathTraversal(validPath, tempDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPathTraversalWithMultipleLevels() {
|
||||||
|
// Tentative de Path Traversal avec plusieurs niveaux
|
||||||
|
Path maliciousPath = tempDir.resolve("subdir/../../../etc/passwd");
|
||||||
|
|
||||||
|
FileValidator.InvalidFileException exception = assertThrows(
|
||||||
|
FileValidator.InvalidFileException.class,
|
||||||
|
() -> FileValidator.validatePathTraversal(maliciousPath, tempDir)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("Path Traversal"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user