En général, il existe trois méthodes pour valider qu'un fichier est bien une image :
- Vérification de l'extension du fichier Cette méthode consiste à vérifier si l'extension du fichier correspond à celles autorisées pour les images. Bien que couramment utilisée, elle présente un risque majeur : un utilisateur malveillant peut simplement modifier l'extension d'un fichier non-image (comme un script PHP ou JSP) pour le faire passser pour une image. Cela pourrait compromettre la sécurité du système.
Cette approche reste néanmoins utile comme première étape préliminaire : si l'extension n'est pas valide, il est inutile de procéder aux vérifications plus complexes. Elle permet ainsi de réduire la charge sur le serveur en évitant de traiter tous les fichiers à posteriori.
- Analyse des octets de tête (magic numbers) Chaque type de fichier possède un en-tête spécifique. Par exemple, les fichiers PNG commencent par les octets "89 50". On peut utiliser cette technique pour identifier le type de fichier.
Voici un exemple de code Java qui lit les deux premiers octets d’un fichier :
import java.io.*;
import java.util.*;
public class FileTypeValidator {
public static String bytesToHex(byte[] src) {
StringBuilder sb = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
sb.append('0');
}
sb.append(hv);
}
return sb.toString();
}
public static void main(String[] args) throws IOException {
String filePath = "C:/favicon.png";
File file = new File(filePath);
try (InputStream is = new FileInputStream(file)) {
byte[] header = new byte[2];
is.read(header);
System.out.println(bytesToHex(header));
}
}
}
Cette méthode, tout comme la vérification d'extension, peut servir de filtre initial mais ne garantit pas la sécurité complète.
- Extraction des dimensions de l'image Si l'on peut lire les dimensions d'une image (largeur et hauteur), alors il est fort probable que le fichier soit bien une image. Les fichiers non-images ne permettent généralement pas cette lecture.
Voici un exemple de validation basée sur cette méthode :
/**
* Vérifie si un fichier est une image en tentant de lire ses dimensions.
*
* @param imageFile Le fichier à vérifier
* @return vrai si c'est une image valide
*/
public static boolean isValidImage(File imageFile) {
if (!imageFile.exists()) {
return false;
}
try {
BufferedImage img = ImageIO.read(imageFile);
return img != null && img.getWidth() > 0 && img.getHeight() > 0;
} catch (Exception e) {
return false;
}
}
Deuxièmement, sécurisation des images téléchargées Maintenant que nous savons identifier les fichiers images, nous devons aussi veiller à ce qu'ils ne contiennent pas de code malveillant. Même un fichierr image valide peut être corrompu avec du code exécutable inséré à la fin du fichier. Par exemple, des balises iframe peuvent être ajoutées sans altérer l’apparence visuelle de l’image, mais qui pourraient être exécutées dans certains contextes.
Pour éviter cela, nous pouvons recréer l'image en effectuant une opération simple comme l'ajout d'un filigrane ou un redmiensionnement. Cela élimine potentiellement tout contenu malveillant.
Voici un exemple d'ajout de filigrane :
/**
* Ajoute un filigrane à une image.
*
* @param sourceImagePath Chemin de l'image source
* @param watermarkPath Chemin de l'image filigrane
* @param x Position horizontale du filigrane
* @param y Position verticale du filigrane
* @param alpha Niveau de transparence (0.0 à 1.0)
* @throws IOException En cas d'erreur de lecture/écriture
*/
public static void addWatermark(String sourceImagePath, String watermarkPath, int x, int y, float alpha) throws IOException {
File sourceFile = new File(sourceImagePath);
String extension = sourceImagePath.substring(sourceImagePath.lastIndexOf(".") + 1);
BufferedImage sourceImage = ImageIO.read(sourceFile);
BufferedImage watermarkImage = ImageIO.read(new File(watermarkPath));
int sourceWidth = sourceImage.getWidth();
int sourceHeight = sourceImage.getHeight();
int watermarkWidth = watermarkImage.getWidth();
int watermarkHeight = watermarkImage.getHeight();
BufferedImage result = new BufferedImage(sourceWidth, sourceHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = result.createGraphics();
g2d.drawImage(sourceImage, 0, 0, null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
int posX = (x < 0) ? (sourceWidth - watermarkWidth) / 2 : Math.min(x, sourceWidth - watermarkWidth);
int posY = (y < 0) ? (sourceHeight - watermarkHeight) / 2 : Math.min(y, sourceHeight - watermarkHeight);
g2d.drawImage(watermarkImage, posX, posY, watermarkWidth, watermarkHeight, null);
g2d.dispose();
ImageIO.write(result, extension, sourceFile);
}
En combinant ces techniques, on peut minimiser les risques liés aux fichiers image malveillants.