mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-06-02 19:02:44 +08:00
@@ -4,18 +4,21 @@ import lombok.Getter;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.hswebframework.utils.time.DateFormatter;
|
import org.hswebframework.utils.time.DateFormatter;
|
||||||
|
import org.hswebframework.web.authorization.exception.AccessDenyException;
|
||||||
import org.hswebframework.web.id.IDGenerator;
|
import org.hswebframework.web.id.IDGenerator;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
import java.io.File;
|
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.Files;
|
||||||
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.attribute.PosixFileAttributeView;
|
import java.nio.file.attribute.PosixFileAttributeView;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
import java.util.Collections;
|
import java.text.Normalizer;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -92,28 +95,49 @@ public class FileUploadProperties {
|
|||||||
return defaultDeny;
|
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) {
|
public StaticFileInfo createStaticSavePath(String name) {
|
||||||
String fileName = IDGenerator.SNOW_FLAKE_STRING.generate();
|
String fileName = IDGenerator.SNOW_FLAKE_STRING.generate();
|
||||||
String filePath = DateFormatter.toString(new Date(), "yyyyMMdd");
|
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(".") ?
|
String suffix = resolveExtension(name);
|
||||||
name.substring(name.lastIndexOf(".")) : "";
|
|
||||||
|
|
||||||
StaticFileInfo info = new StaticFileInfo();
|
StaticFileInfo info = new StaticFileInfo();
|
||||||
|
|
||||||
if (useOriginalFileName) {
|
// 仅支持 字母数字组成的文件名
|
||||||
|
if (useOriginalFileName && name.matches("^[a-zA-Z0-9._-]+$")) {
|
||||||
filePath = filePath + "/" + fileName;
|
filePath = filePath + "/" + fileName;
|
||||||
fileName = name;
|
fileName = name;
|
||||||
} else {
|
} else {
|
||||||
fileName = fileName + suffix;
|
fileName = fileName + suffix;
|
||||||
}
|
}
|
||||||
String absPath = staticFilePath.concat("/").concat(filePath);
|
String absPath = staticFilePath.concat("/").concat(filePath);
|
||||||
new File(absPath).mkdirs();
|
|
||||||
|
|
||||||
info.location = staticLocation + "/" + filePath + "/" + fileName;
|
boolean ignore = new File(absPath).mkdirs();
|
||||||
info.savePath = absPath + "/" + fileName;
|
|
||||||
|
Path fullPath = Paths.get(absPath, fileName);
|
||||||
|
info.savePath = fullPath.normalize().toString();
|
||||||
|
|
||||||
info.relativeLocation = filePath + "/" + fileName;
|
info.relativeLocation = filePath + "/" + fileName;
|
||||||
|
info.location = staticLocation + "/" + filePath + "/" + fileName;
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.hswebframework.web.file;
|
package org.hswebframework.web.file;
|
||||||
|
|
||||||
|
import org.hswebframework.web.authorization.exception.AccessDenyException;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
|
import java.text.Normalizer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
@@ -12,17 +14,17 @@ public class FileUploadPropertiesTest {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoSet(){
|
public void testNoSet() {
|
||||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||||
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
||||||
|
|
||||||
assertFalse(uploadProperties.denied("test.exe", MediaType.ALL));
|
assertFalse(uploadProperties.denied("test.exe", MediaType.ALL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDenyWithAllow(){
|
public void testDenyWithAllow() {
|
||||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||||
uploadProperties.setAllowFiles(new HashSet<>(Arrays.asList("xls","json")));
|
uploadProperties.setAllowFiles(new HashSet<>(Arrays.asList("xls", "json")));
|
||||||
|
|
||||||
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
||||||
assertFalse(uploadProperties.denied("test.XLS", MediaType.ALL));
|
assertFalse(uploadProperties.denied("test.XLS", MediaType.ALL));
|
||||||
@@ -31,9 +33,9 @@ public class FileUploadPropertiesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDenyWithAllowMediaType(){
|
public void testDenyWithAllowMediaType() {
|
||||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||||
uploadProperties.setAllowMediaType(new HashSet<>(Arrays.asList("application/xls","application/json")));
|
uploadProperties.setAllowMediaType(new HashSet<>(Arrays.asList("application/xls", "application/json")));
|
||||||
|
|
||||||
assertFalse(uploadProperties.denied("test.json", MediaType.APPLICATION_JSON));
|
assertFalse(uploadProperties.denied("test.json", MediaType.APPLICATION_JSON));
|
||||||
|
|
||||||
@@ -41,10 +43,9 @@ public class FileUploadPropertiesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDenyWithDenyMediaType(){
|
public void testDenyWithDenyMediaType() {
|
||||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||||
uploadProperties.setDenyMediaType(new HashSet<>(Arrays.asList("application/json")));
|
uploadProperties.setDenyMediaType(new HashSet<>(Arrays.asList("application/json")));
|
||||||
|
|
||||||
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
||||||
@@ -52,9 +53,10 @@ public class FileUploadPropertiesTest {
|
|||||||
assertTrue(uploadProperties.denied("test.exe", MediaType.APPLICATION_JSON));
|
assertTrue(uploadProperties.denied("test.exe", MediaType.APPLICATION_JSON));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDenyWithDeny(){
|
public void testDenyWithDeny() {
|
||||||
FileUploadProperties uploadProperties=new FileUploadProperties();
|
FileUploadProperties uploadProperties = new FileUploadProperties();
|
||||||
uploadProperties.setDenyFiles(new HashSet<>(Arrays.asList("exe")));
|
uploadProperties.setDenyFiles(new HashSet<>(Arrays.asList("exe")));
|
||||||
|
|
||||||
assertFalse(uploadProperties.denied("test.xls", MediaType.ALL));
|
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