feat: add path transversal security

This commit is contained in:
2025-12-05 08:39:08 +01:00
parent 471595060a
commit 7b94b5b82c
3 changed files with 69 additions and 0 deletions

View File

@@ -71,10 +71,18 @@ public class PhotoServiceImpl implements PhotoService {
// ========= SAUVEGARDE ORIGINAL =========
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);
// ========= MINIATURE =========
String thumbUuid = "thumb-" + uuid + ".jpg";
Path thumbPath = uploadPath.resolve(thumbUuid);
// ========= PROTECTION PATH TRAVERSAL POUR MINIATURE =========
FileValidator.validatePathTraversal(thumbPath, uploadPath);
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes)) {
net.coobird.thumbnailator.Thumbnails.of(bis)
.size(300, 300)

View File

@@ -5,6 +5,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
@@ -12,6 +13,7 @@ import java.util.List;
* Utilitaire pour valider les fichiers uploadés
* - Vérification du type MIME réel via Magic Numbers (Apache Tika)
* - Limitation de taille
* - Protection contre Path Traversal
*/
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
*/

View File

@@ -1,12 +1,18 @@
package local.epul4a.fotosharing.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.mock.web.MockMultipartFile;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
class FileValidatorTest {
@TempDir
Path tempDir;
@Test
void testValidJpegFile() {
// JPEG Magic Number: FF D8 FF
@@ -80,5 +86,39 @@ class FileValidatorTest {
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"));
}
}