Skip to content

Arbitrary File Upload Vulnerability in ShStaticFileAPI #1029

@intSheep

Description

@intSheep

Arbitrary File Upload Vulnerability in ShStaticFileAPI

Dear Shio Project Maintainers,

I hope this message finds you well. I am writing to report a security vulnerability that I have identified in the Shio Content Management System. This report is intended to help improve the security of your project and protect your users.

Summary

An arbitrary file upload vulnerability exists in the shStaticFileUpload method of ShStaticFileAPI.java in the Shio application. This vulnerability allows attackers to upload malicious files to arbitrary locations on the server by manipulating the filename parameter, potentially leading to remote code execution.

Affected System

  • System: Shio - Java-based Content Management System
  • Repository: https://github.com/openviglet/shio
  • Affected Versions: All versions prior to the current development version
  • Component: Static File API Module

Affected Component

  • File: shio-app/src/main/java/com/viglet/shio/api/staticfile/ShStaticFileAPI.java
  • Method: shStaticFileUpload
  • Line: 107-126

Vulnerability Details

Root Cause

The vulnerability stems from insufficient filename validation in the file upload process. While the application uses shUtils.sanitizedString() to process filenames, this function only replaces newline and tab characters, failing to prevent path traversal attacks.

Technical Analysis

  1. Input Source: The filename is derived from file.getOriginalFilename() from the uploaded file
  2. Insufficient Sanitization: shUtils.sanitizedString() only replaces [\n\r\t] with underscores
  3. Path Construction: The file path is constructed using string concatenation without path validation
  4. File Transfer: Files are transferred using file.transferTo() without proper path normalization

Vulnerable Code

@PostMapping("/upload")
@JsonView({ ShJsonView.ShJsonViewObject.class })
public ResponseEntity<Object> shStaticFileUpload(@RequestParam("file") MultipartFile file,
        @RequestParam("folderId") String folderId, @RequestParam("createPost") boolean createPost,
        Principal principal) {

    ShFolder shFolder = shFolderRepository.findById(folderId).orElse(null);
    if (!shStaticFileUtils.fileExists(shFolder, file.getOriginalFilename())) {
        return new ResponseEntity<>(
                shStaticFileUtils.createFilePost(file, shUtils.sanitizedString(file.getOriginalFilename()), shFolder, principal, createPost),
                HttpStatus.OK);
    }
}

Sanitization Function Analysis

public String sanitizedString(String string) {
    return (string != null) ? string.replaceAll("[\n\r\t]", "_") : null;
}

File Creation Process

In ShStaticFileUtils.createFilePost():

String destFile = directoryPath.getAbsolutePath().concat("/" + fileName);
file.transferTo(new File(destFile));

Attack Vector

An attacker can exploit this vulnerability by uploading a file with a malicious filename containing path traversal sequences:

POST /api/v2/staticfile/upload
Content-Type: multipart/form-data

file: [malicious file content]
folderId: [valid_folder_id]
createPost: false

Filename: ../../../webapps/ROOT/shell.jsp

Impact

  • Remote Code Execution: Upload of executable files (JSP, PHP, etc.) to web-accessible directories
  • System Compromise: Potential full system access through web shells
  • Data Breach: Unauthorized access to sensitive data
  • Service Disruption: Potential denial of service through disk space exhaustion

Proof of Concept

  1. Create a malicious JSP file with web shell functionality
  2. Upload the file with filename ../../../webapps/ROOT/shell.jsp
  3. Access the uploaded file via web browser to execute arbitrary code

Recommended Fix

Immediate Fix

Implement comprehensive filename validation:

@PostMapping("/upload")
@JsonView({ ShJsonView.ShJsonViewObject.class })
public ResponseEntity<Object> shStaticFileUpload(@RequestParam("file") MultipartFile file,
        @RequestParam("folderId") String folderId, @RequestParam("createPost") boolean createPost,
        Principal principal) {

    // Validate filename for path traversal attempts
    String originalFilename = file.getOriginalFilename();
    if (originalFilename == null || originalFilename.contains("..") || 
        originalFilename.contains("/") || originalFilename.contains("\\")) {
        return ResponseEntity.badRequest().build();
    }

    ShFolder shFolder = shFolderRepository.findById(folderId).orElse(null);
    if (!shStaticFileUtils.fileExists(shFolder, originalFilename)) {
        return new ResponseEntity<>(
                shStaticFileUtils.createFilePost(file, originalFilename, shFolder, principal, createPost),
                HttpStatus.OK);
    }
}

Comprehensive Fix

  1. Path Normalization: Use Path.normalize() to detect and prevent path traversal
  2. File Extension Whitelist: Implement strict file type validation
  3. Content Validation: Verify file content matches declared type
  4. Directory Validation: Ensure files are saved within intended directories
private boolean isValidUploadFile(MultipartFile file) {
    if (file == null || file.isEmpty()) {
        return false;
    }
    
    String filename = file.getOriginalFilename();
    if (filename == null || filename.isEmpty()) {
        return false;
    }
    
    // Check for path traversal sequences
    if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
        return false;
    }
    
    // Validate file extension against whitelist
    String extension = getFileExtension(filename);
    Set<String> allowedExtensions = Set.of("jpg", "jpeg", "png", "gif", "pdf", "txt", "doc", "docx");
    
    return allowedExtensions.contains(extension.toLowerCase());
}

private String getFileExtension(String filename) {
    int lastDotIndex = filename.lastIndexOf('.');
    return lastDotIndex > 0 ? filename.substring(lastDotIndex + 1) : "";
}

Enhanced File Creation

public ShPost createFilePost(MultipartFile file, String fileName, ShFolder shFolder, Principal principal,
        boolean createPost) {
    File directoryPath = shFolderUtils.dirPath(shFolder);
    ShPost shPost = new ShPost();
    if (directoryPath != null) {
        if (!directoryPath.exists())
            directoryPath.mkdirs();

        try {
            // Use Path API for secure path construction
            Path targetPath = directoryPath.toPath().resolve(fileName).normalize();
            
            // Ensure the final path is within the intended directory
            if (!targetPath.startsWith(directoryPath.toPath())) {
                throw new SecurityException("Path traversal attempt detected");
            }
            
            file.transferTo(targetPath.toFile());
            
            // ... rest of the method
        } catch (IOException e) {
            logger.error("shStaticFileUploadException", e);
        }
    }
    return shPost;
}

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions