-
-
Notifications
You must be signed in to change notification settings - Fork 32
Description
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
- Input Source: The filename is derived from
file.getOriginalFilename()from the uploaded file - Insufficient Sanitization:
shUtils.sanitizedString()only replaces[\n\r\t]with underscores - Path Construction: The file path is constructed using string concatenation without path validation
- 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
- Create a malicious JSP file with web shell functionality
- Upload the file with filename
../../../webapps/ROOT/shell.jsp - 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
- Path Normalization: Use
Path.normalize()to detect and prevent path traversal - File Extension Whitelist: Implement strict file type validation
- Content Validation: Verify file content matches declared type
- 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
- OWASP File Upload: https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload
- CWE-434: Unrestricted Upload of File with Dangerous Type
- CWE-22: Path Traversal
- CWE-73: External Control of File Name or Path