VYPR
Medium severity6.3NVD Advisory· Published Jun 8, 2026

CVE-2026-11470

CVE-2026-11470

Description

A vulnerability has been found in hs-web hsweb-framework up to 5.0.1. The affected element is the function denied of the file hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileUploadProperties.java of the component File Upload. The manipulation of the argument filename leads to path traversal. It is possible to initiate the attack remotely. The exploit has been disclosed to the public and may be used. The identifier of the patch is 8009845b577d8a2c4bbf4fdd8e8913799a714be6. It is suggested to install a patch to address this issue.

Affected products

2

Patches

1
8009845b577d

fix(file): 修复文件上传路径解析漏洞,增强对非法文件名的校验

https://github.com/hs-web/hsweb-frameworkzhouhaoJan 26, 2026via nvd-ref
2 files changed · +123 21
  • hsweb-system/hsweb-system-file/src/main/java/org/hswebframework/web/file/FileUploadProperties.java+32 8 modified
    @@ -4,18 +4,21 @@
     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 boolean denied(String name, MediaType mediaType) {
             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;
         }
     
    
  • hsweb-system/hsweb-system-file/src/test/java/org/hswebframework/web/file/FileUploadPropertiesTest.java+91 13 modified
    @@ -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,30 +33,30 @@ public void testDenyWithAllow(){
         }
     
         @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));
     
             assertTrue(uploadProperties.denied("test.exe", MediaType.ALL));
         }
     
     
    -
         @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));
     
             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 void testDenyWithDeny(){
         }
     
     
    +    @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"));
    +    }
    +
    +
     }
    \ No newline at end of file
    

Vulnerability mechanics

Root cause

"The file upload functionality does not properly sanitize filenames, allowing path traversal."

Attack vector

An attacker can remotely send a crafted filename containing path traversal sequences (e.g., `../` or `..\`) to the file upload functionality. This manipulation allows the attacker to write files to arbitrary locations on the server, potentially overwriting critical system files or injecting malicious content. The vulnerability is present in the `FileUploadProperties.java` file within the `hsweb-system-file` component.

Affected code

The vulnerability resides in the `createStaticSavePath` method of the `FileUploadProperties.java` file within the `hsweb-system-file` component. The `denied` method in the same file is also related to file handling and security checks.

What the fix does

The patch addresses the path traversal vulnerability by enhancing the validation of filenames in the `createStaticSavePath` method within `FileUploadProperties.java` [patch_id=5163052]. It normalizes the input path, replaces backslashes with forward slashes, and extracts only the filename component. Additionally, it checks if the filename matches a strict pattern of alphanumeric characters, dots, underscores, and hyphens before allowing it to be used with the original filename. This prevents malicious path sequences from being interpreted by the file system.

Preconditions

  • authThe attacker needs to have the necessary privileges to upload files.
  • networkThe attack can be initiated remotely.

Generated on Jun 8, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.