Skip to content

Arbitrary file write via unsanitized upload filename (path traversal) #1202

Description

@Ku4D3

Severity: High · CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
Affected versions:v1.4.0

Summary

Several component-upload endpoints under /upload/component/* save an uploaded multipart file to a path built directly from the caller-supplied filename with no sanitization. Because the destination is computed as uploadsDir + separator + originalFilename, a filename containing ../ escapes the intended upload directory, letting an authenticated operator write or overwrite arbitrary files writable by the JVM user.

Vulnerability chain

Stage Component Location
Source Multipart filename of uploaded component resources UploadController.java:146
Sink transferTo(uploadsDir + sep + originalFilename) (no sanitize) UploadController.java:147,153

A shared helper, getResourcesFromFiles, is invoked by the component-upload endpoints (/upload/component/config, /upload/component/addOrUpdateComponent, /upload/component/parseKerberos, /upload/component/uploadKerberos). It reads MultipartFile.getOriginalFilename() verbatim, concatenates it onto the upload base directory, creates any missing parent directories, and writes the bytes with transferTo. No /, \, or .. sequence is rejected, and no canonical containment check is performed, so a traversal payload in the multipart filename resolves outside the base directory.

Key code

taier-data-develop/.../controller/console/UploadController.java:143

for (MultipartFile file : files) {
    String fileOriginalName = file.getOriginalFilename();
    String path =  uploadsDir + File.separator + fileOriginalName;   // :147 — no sanitize
    File saveFile = new File(path);
    if (!saveFile.getParentFile().exists()) {
        saveFile.getParentFile().mkdirs();                            // :150 — also creates escaped parent dirs
    }
    try {
        file.transferTo(saveFile);                                    // :153
    } catch (Exception e) { ... }

The upload base directory is the working directory plus file-uploadsUploadController.java:58:

private static String uploadsDir = System.getProperty("user.dir") + File.separator + "file-uploads";

Proof of Concept

Authenticated console operator. The request only triggers the write; the target is a benign /tmp path.

POST /upload/component/config HTTP/1.1
Host: <taier host>
Content-Type: multipart/form-data; boundary=----b
Cookie: <taier session>

------b
Content-Disposition: form-data; name="fileName"; filename="../../../../../../tmp/x.txt"
Content-Type: application/octet-stream

x
------b
Content-Disposition: form-data; name="componentType"

0
------b--

The same sink applies to /upload/component/parseKerberos, /upload/component/addOrUpdateComponent, and /upload/component/uploadKerberos, which all pass uploaded files through getResourcesFromFiles.

Impact

An authenticated operator with component-management access can write or overwrite any file reachable and writable by the taier JVM user. Depending on the deployment, this can lead to remote code execution (e.g., dropping classes, scripts, or JARs onto a writable classpath or writable configuration consumed by the application). Precondition: a valid authenticated session.

Remediation

Do not trust MultipartFile.getOriginalFilename() as a filesystem component. Generate a safe server-side name (e.g., a UUID or content hash) for storage and keep the original name only as metadata; reject any /, \, or .. sequence in the original filename. Additionally, canonicalize the resolved destination and verify it remains inside the upload base directory before writing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions