The Problem
The filesystem store constructs file paths by joining a base directory with user-controlled hash values. `filepath.Join` resolves `..` components, allowing a crafted hash to write outside the store directory.
```go
// drs-verify/pkg/store/filesystem.go:93-101
func (f *FilesystemStore) hashPath(hash string) string {
name := strings.TrimPrefix(hash, "sha256:")
if len(name) < 4 {
name = fmt.Sprintf("%064s", name)
}
prefix := name[:4]
return filepath.Join(f.baseDir, prefix, name+".jwt")
}
```
A hash like `sha256:../../../../etc/passwd` produces:
```
filepath.Join("/var/drs/store", "../", "../../../../etc/passwd.jwt")
→ /etc/passwd.jwt
```
The `Put()` method writes attacker-controlled content (a JWT string) to this path. The `Delete()` method removes it. This is arbitrary file write and delete with the permissions of the drs-verify process.
Proof
```go
hash := "sha256:../../../../etc/passwd"
name := strings.TrimPrefix(hash, "sha256:") // "../../../../etc/passwd"
prefix := name[:4] // "../.."
filepath.Join("/tmp/store", prefix, name+".jwt") // "/etc/passwd.jwt"
```
What Must Change
Validate that the resolved path stays within the base directory:
```go
func (f *FilesystemStore) hashPath(hash string) (string, error) {
name := strings.TrimPrefix(hash, "sha256:")
// Reject any path-separator characters
if strings.ContainsAny(name, "/\\..") {
return "", fmt.Errorf("invalid hash: contains path separators")
}
// ... rest of path construction
}
```
Or more robustly: after constructing the path, check that `filepath.Rel(f.baseDir, path)` does not start with `..`.
The hash should also be validated as hex-only (`[0-9a-f]+`) since it comes from a SHA-256 digest. Anything outside that character set is not a real hash.
Severity
HIGH. Arbitrary file write. In a container deployment with a writable filesystem, this can overwrite configuration files, inject content into mounted volumes, or destroy data. The severity depends on what the process has permission to write, but the vulnerability itself is unambiguous.
The Problem
The filesystem store constructs file paths by joining a base directory with user-controlled hash values. `filepath.Join` resolves `..` components, allowing a crafted hash to write outside the store directory.
```go
// drs-verify/pkg/store/filesystem.go:93-101
func (f *FilesystemStore) hashPath(hash string) string {
name := strings.TrimPrefix(hash, "sha256:")
if len(name) < 4 {
name = fmt.Sprintf("%064s", name)
}
prefix := name[:4]
return filepath.Join(f.baseDir, prefix, name+".jwt")
}
```
A hash like `sha256:../../../../etc/passwd` produces:
```
filepath.Join("/var/drs/store", "../", "../../../../etc/passwd.jwt")
→ /etc/passwd.jwt
```
The `Put()` method writes attacker-controlled content (a JWT string) to this path. The `Delete()` method removes it. This is arbitrary file write and delete with the permissions of the drs-verify process.
Proof
```go
hash := "sha256:../../../../etc/passwd"
name := strings.TrimPrefix(hash, "sha256:") // "../../../../etc/passwd"
prefix := name[:4] // "../.."
filepath.Join("/tmp/store", prefix, name+".jwt") // "/etc/passwd.jwt"
```
What Must Change
Validate that the resolved path stays within the base directory:
```go
func (f *FilesystemStore) hashPath(hash string) (string, error) {
name := strings.TrimPrefix(hash, "sha256:")
// Reject any path-separator characters
if strings.ContainsAny(name, "/\\..") {
return "", fmt.Errorf("invalid hash: contains path separators")
}
// ... rest of path construction
}
```
Or more robustly: after constructing the path, check that `filepath.Rel(f.baseDir, path)` does not start with `..`.
The hash should also be validated as hex-only (`[0-9a-f]+`) since it comes from a SHA-256 digest. Anything outside that character set is not a real hash.
Severity
HIGH. Arbitrary file write. In a container deployment with a writable filesystem, this can overwrite configuration files, inject content into mounted volumes, or destroy data. The severity depends on what the process has permission to write, but the vulnerability itself is unambiguous.