mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-05-07 05:36:21 +08:00
@@ -4,18 +4,21 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.hswebframework.utils.time.DateFormatter;
|
||||
import org.hswebframework.web.authorization.exception.AccessDenyException;
|
||||
import org.hswebframework.web.id.IDGenerator;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.PosixFileAttributeView;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.Collections;
|
||||
import java.text.Normalizer;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
@@ -92,28 +95,49 @@ public class FileUploadProperties {
|
||||
return defaultDeny;
|
||||
}
|
||||
|
||||
public static String resolveExtension(String name) {
|
||||
int lastIndex = name.lastIndexOf(".");
|
||||
if (lastIndex < 0) {
|
||||
return "";
|
||||
}
|
||||
return name.substring(lastIndex).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public StaticFileInfo createStaticSavePath(String name) {
|
||||
String fileName = IDGenerator.SNOW_FLAKE_STRING.generate();
|
||||
String filePath = DateFormatter.toString(new Date(), "yyyyMMdd");
|
||||
try {
|
||||
name = Paths
|
||||
.get(Normalizer
|
||||
.normalize(name, Normalizer.Form.NFKC)
|
||||
.replace("\\", "/"))
|
||||
.toFile()
|
||||
.getName();
|
||||
} catch (InvalidPathException e) {
|
||||
throw new AccessDenyException.NoStackTrace();
|
||||
}
|
||||
|
||||
//文件后缀
|
||||
String suffix = name.contains(".") ?
|
||||
name.substring(name.lastIndexOf(".")) : "";
|
||||
String suffix = resolveExtension(name);
|
||||
|
||||
StaticFileInfo info = new StaticFileInfo();
|
||||
|
||||
if (useOriginalFileName) {
|
||||
// 仅支持 字母数字组成的文件名
|
||||
if (useOriginalFileName && name.matches("^[a-zA-Z0-9._-]+$")) {
|
||||
filePath = filePath + "/" + fileName;
|
||||
fileName = name;
|
||||
} else {
|
||||
fileName = fileName + suffix;
|
||||
}
|
||||
String absPath = staticFilePath.concat("/").concat(filePath);
|
||||
new File(absPath).mkdirs();
|
||||
|
||||
info.location = staticLocation + "/" + filePath + "/" + fileName;
|
||||
info.savePath = absPath + "/" + fileName;
|
||||
boolean ignore = new File(absPath).mkdirs();
|
||||
|
||||
Path fullPath = Paths.get(absPath, fileName);
|
||||
info.savePath = fullPath.normalize().toString();
|
||||
|
||||
info.relativeLocation = filePath + "/" + fileName;
|
||||
info.location = staticLocation + "/" + filePath + "/" + fileName;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.hswebframework.web.file;
|
||||
|
||||
import org.hswebframework.web.authorization.exception.AccessDenyException;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.text.Normalizer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
@@ -12,17 +14,17 @@ public class FileUploadPropertiesTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoSet(){
|
||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
||||
public void testNoSet() {
|
||||
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
||||
|
||||
assertFalse(uploadProperties.denied("test.exe", MediaType.ALL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDenyWithAllow(){
|
||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
||||
uploadProperties.setAllowFiles(new HashSet<>(Arrays.asList("xls","json")));
|
||||
public void testDenyWithAllow() {
|
||||
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||
uploadProperties.setAllowFiles(new HashSet<>(Arrays.asList("xls", "json")));
|
||||
|
||||
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
||||
assertFalse(uploadProperties.denied("test.XLS", MediaType.ALL));
|
||||
@@ -31,9 +33,9 @@ public class FileUploadPropertiesTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDenyWithAllowMediaType(){
|
||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
||||
uploadProperties.setAllowMediaType(new HashSet<>(Arrays.asList("application/xls","application/json")));
|
||||
public void testDenyWithAllowMediaType() {
|
||||
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||
uploadProperties.setAllowMediaType(new HashSet<>(Arrays.asList("application/xls", "application/json")));
|
||||
|
||||
assertFalse(uploadProperties.denied("test.json", MediaType.APPLICATION_JSON));
|
||||
|
||||
@@ -41,10 +43,9 @@ public class FileUploadPropertiesTest {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testDenyWithDenyMediaType(){
|
||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
||||
public void testDenyWithDenyMediaType() {
|
||||
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||
uploadProperties.setDenyMediaType(new HashSet<>(Arrays.asList("application/json")));
|
||||
|
||||
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
||||
@@ -52,9 +53,10 @@ public class FileUploadPropertiesTest {
|
||||
assertTrue(uploadProperties.denied("test.exe", MediaType.APPLICATION_JSON));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDenyWithDeny(){
|
||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
||||
public void testDenyWithDeny() {
|
||||
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||
uploadProperties.setDenyFiles(new HashSet<>(Arrays.asList("exe")));
|
||||
|
||||
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
||||
@@ -64,4 +66,80 @@ public class FileUploadPropertiesTest {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
// https://github.com/hs-web/hsweb-framework/issues/344
|
||||
public void testIllegalFileName() {
|
||||
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||
uploadProperties.setUseOriginalFileName(true);
|
||||
|
||||
// 基本的路径遍历攻击
|
||||
FileUploadProperties.StaticFileInfo fileInfo = uploadProperties
|
||||
.createStaticSavePath("../../../../pom.xml");
|
||||
assertFalse(fileInfo.getSavePath().contains("../"));
|
||||
assertFalse(fileInfo.getRelativeLocation().contains("../"));
|
||||
assertFalse(fileInfo.getLocation().contains("../"));
|
||||
|
||||
// Windows风格的路径遍历攻击
|
||||
fileInfo = uploadProperties.createStaticSavePath("..\\..\\..\\..\\pom.xml");
|
||||
assertFalse(fileInfo.getSavePath().contains("..\\"));
|
||||
assertFalse(fileInfo.getRelativeLocation().contains("..\\"));
|
||||
assertFalse(fileInfo.getLocation().contains("..\\"));
|
||||
|
||||
// URL编码的路径遍历
|
||||
fileInfo = uploadProperties.createStaticSavePath("..%2F..%2F..%2F..%2Fpom.xml");
|
||||
assertFalse(fileInfo.getSavePath().contains("../"));
|
||||
assertFalse(fileInfo.getSavePath().contains("..%2F"));
|
||||
assertFalse(fileInfo.getRelativeLocation().contains("../"));
|
||||
assertFalse(fileInfo.getLocation().contains("../"));
|
||||
|
||||
// 双重URL编码
|
||||
fileInfo = uploadProperties.createStaticSavePath("..%252F..%252F..%252Fpom.xml");
|
||||
assertFalse(fileInfo.getSavePath().contains("../"));
|
||||
assertFalse(fileInfo.getSavePath().contains("..%2F"));
|
||||
assertFalse(fileInfo.getSavePath().contains("..%252F"));
|
||||
|
||||
// Unicode编码的路径遍历
|
||||
fileInfo = uploadProperties.createStaticSavePath("..%c0%af..%c0%afpom.xml");
|
||||
assertFalse(fileInfo.getSavePath().contains("../"));
|
||||
assertFalse(fileInfo.getRelativeLocation().contains("../"));
|
||||
|
||||
// 绝对路径攻击 - Linux
|
||||
fileInfo = uploadProperties.createStaticSavePath("/etc/passwd");
|
||||
assertFalse(fileInfo.getSavePath().startsWith("/etc/"));
|
||||
assertFalse(fileInfo.getLocation().contains("/etc/passwd"));
|
||||
|
||||
// 绝对路径攻击 - Windows
|
||||
fileInfo = uploadProperties.createStaticSavePath("C:\\Windows\\System32\\config\\sam");
|
||||
assertFalse(fileInfo.getSavePath().contains("C:\\"));
|
||||
assertFalse(fileInfo.getSavePath().contains("System32"));
|
||||
|
||||
// 混合斜杠
|
||||
fileInfo = uploadProperties.createStaticSavePath("..\\../..\\../pom.xml");
|
||||
assertFalse(fileInfo.getSavePath().contains("../"));
|
||||
assertFalse(fileInfo.getSavePath().contains("..\\"));
|
||||
|
||||
// 过度的路径遍历
|
||||
fileInfo = uploadProperties.createStaticSavePath("../../../../../../../../../../../../etc/passwd");
|
||||
assertFalse(fileInfo.getSavePath().contains("../"));
|
||||
assertFalse(fileInfo.getLocation().contains("/etc/"));
|
||||
|
||||
|
||||
// // 带有空字节注入
|
||||
assertThrows(AccessDenyException.class,
|
||||
()->{
|
||||
uploadProperties.createStaticSavePath("../../pom.xml\0.jpg");
|
||||
});
|
||||
|
||||
// 点和斜杠的各种组合
|
||||
fileInfo = uploadProperties.createStaticSavePath("....//....//pom.xml");
|
||||
assertFalse(fileInfo.getSavePath().contains(".."));
|
||||
assertFalse(fileInfo.getSavePath().contains("//"));
|
||||
|
||||
// 反斜杠编码
|
||||
fileInfo = uploadProperties.createStaticSavePath("..%5c..%5cpom.xml");
|
||||
assertFalse(fileInfo.getSavePath().contains("..\\"));
|
||||
assertFalse(fileInfo.getSavePath().contains("..%5c"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user