# REPRO-2026-00158: goshs: PUT upload accepts cross-origin requests without CSRF token ## Summary Status: published Severity: medium Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00158 GHSA: GHSA-rhf7-wvw3-vjvm CVE: CVE-2026-42091 ## Package Name: github.com/patrickhener/goshs Ecosystem: go Affected: <= v2.0.1 (commit f5671ea) Fixed: v2.0.2 ## Root Cause # RCA Report: CVE-2026-42091 — goshs PUT Upload CSRF ## Summary goshs (a Go-based HTTP file server CLI) versions ≤ v2.0.1 are vulnerable to Cross-Site Request Forgery (CSRF) on the PUT upload endpoint. While the POST upload handler enforced CSRF token validation via `checkCSRF()`, the `put()` handler in `httpserver/updown.go` omitted this check entirely. Combined with permissive CORS headers (`Access-Control-Allow-Origin: *`), a malicious cross-origin webpage can silently issue an HTTP PUT and write arbitrary files to any reachable goshs instance. ## Impact - **Package**: `github.com/patrickhener/goshs` - **Affected versions**: `≤ v2.0.1` - **Fixed version**: `v2.0.2` - **CWE**: CWE-352 (Cross-Site Request Forgery) - **Severity**: Medium (CVSS 3.1 base 6.5) - **Risk**: Any website visited by a user whose browser can reach a goshs upload server (loopback, LAN, VPN, etc.) can upload arbitrary files without user consent. ## Root Cause The `put()` handler in `httpserver/updown.go` did not call `fs.checkCSRF(w, req)` before processing the request. The `checkCSRF()` function (present in `httpserver/handler.go`) validates the `X-CSRF-Token` header, absence of `Origin`/`Referer` (non-browser clients), or same-origin requests. Because `put()` skipped this validation, a browser-initiated cross-origin PUT request—bearing an arbitrary `Origin` header but no valid token—was accepted and the file was written to disk. The fix commit [`0e715b94e10c3d1aa552276000f15f104dee2f32`](https://github.com/patrickhener/goshs/commit/0e715b94e10c3d1aa552276000f15f104dee2f32) adds the missing `checkCSRF()` call at the top of `put()`. ## Reproduction Steps The reproduction is fully automated by `repro/reproduction_steps.sh`. It performs the following: 1. Clones the `goshs` repository (or reuses an existing clone). 2. Builds the vulnerable binary at tag `v2.0.1` (`goshs_v2.0.1`). 3. Builds the fixed binary at tag `v2.0.2` (`goshs_v2.0.2`). 4. Starts each binary in a separate scratch upload directory with upload enabled. 5. Sends a cross-origin-style PUT request (`curl -X PUT -H "Origin: http://evil.com" --data-binary 'pwned' http://localhost:/pwned.txt`). 6. Captures the HTTP response status and checks whether `pwned.txt` was created. **Expected evidence of reproduction:** - **v2.0.1 (vulnerable)**: HTTP `200 OK` and `pwned.txt` is created in the upload directory. - **v2.0.2 (fixed)**: HTTP `403 Forbidden` and `pwned.txt` is **not** created. ## Evidence All logs are written to `logs/`: - `logs/vuln.log` — goshs v2.0.1 server output. - `logs/vuln_result.json` — captured HTTP status (`200`), response body, and `file_created: true`. - `logs/fixed.log` — goshs v2.0.2 server output. - `logs/fixed_result.json` — captured HTTP status (`403`), response body (`Forbidden`), and `file_created: false`. Excerpt from `logs/vuln_result.json`: ```json { "version": "vuln", "http_status": "200", "response_body": "", "file_created": true, "upload_dir": ".../logs/upload_vuln" } ``` Excerpt from `logs/fixed_result.json`: ```json { "version": "fixed", "http_status": "403", "response_body": "Forbidden", "file_created": false, "upload_dir": ".../logs/upload_fixed" } ``` Environment: - Go version used for build: `go1.24.7` (v2.0.1) / `go1.25.0` (v2.0.2) - OS: Linux (containerized) - Network: localhost (`127.0.0.1`) ## Recommendations / Next Steps 1. **Immediate fix**: Upgrade to `goshs v2.0.2` or later, which enforces `checkCSRF()` on every mutating handler (PUT, POST, DELETE). 2. **Defense in depth**: If running goshs on a publicly reachable interface, pair it with `--basic-auth` (`-b user:pass`). When basic auth is configured, the CORS preflight already fails in browsers, providing an additional layer of protection. 3. **Regression testing**: Any future change to upload handlers should be accompanied by a test that sends a cross-origin request with a mismatched `Origin` header and asserts `403 Forbidden`. ## Additional Notes - **Idempotency**: The reproduction script was executed twice consecutively and produced identical results both times. - **Edge cases**: The script simulates a browser cross-origin request by explicitly sending an `Origin` header. Without this header, `curl` requests are treated as non-browser clients and are allowed by design (see `checkCSRF` logic). The vulnerability specifically affects browser-based CSRF attacks. - **Limitations**: The reproduction does not require a real browser or a second origin; the malicious cross-origin behavior is accurately modeled with `curl` and the `Origin` header. ## Reproduction Details Reproduced: 2026-05-23T06:58:26.415Z Duration: 2117 seconds Tool calls: 171 Turns: 152 Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00158 pruva-verify GHSA-rhf7-wvw3-vjvm pruva-verify CVE-2026-42091 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00158&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00158/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/advisories/GHSA-rhf7-wvw3-vjvm - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-42091 - Source: https://github.com/patrickhener/goshs ## Artifacts - repro/rca_report.md (analysis, 4614 bytes) - repro/reproduction_steps.sh (reproduction_script, 3555 bytes) - vuln_variant/rca_report.md (analysis, 8327 bytes) - vuln_variant/reproduction_steps.sh (reproduction_script, 6356 bytes) - bundle/context.json (other, 3141 bytes) - bundle/metadata.json (other, 647 bytes) - bundle/ticket.md (ticket, 3153 bytes) - repro/validation_verdict.json (other, 1026 bytes) - vuln_variant/root_cause_equivalence.json (other, 1143 bytes) - vuln_variant/patch_analysis.md (documentation, 5301 bytes) - vuln_variant/variant_manifest.json (other, 2548 bytes) - vuln_variant/validation_verdict.json (other, 1420 bytes) - vuln_variant/source_identity.json (other, 736 bytes) - logs/variant_results.json (other, 701 bytes) - logs/latest.pid (other, 5 bytes) - logs/vuln_result.json (other, 180 bytes) - logs/latest.log (log, 1353 bytes) - logs/vuln.pid (other, 5 bytes) - logs/upload_bypass/pwned_bypass.txt (other, 12 bytes) - logs/bypass.log (log, 1101 bytes) - logs/fixed_result.json (other, 192 bytes) - logs/upload_latest/pwned_latest.txt (other, 12 bytes) - logs/upload_vuln/pwned_vuln.txt (other, 5 bytes) - logs/upload_vuln/pwned.txt (other, 5 bytes) - logs/bypass.pid (other, 5 bytes) - logs/vuln.log (log, 1096 bytes) - logs/fixed.log (log, 991 bytes) - logs/fixed.pid (other, 5 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00158 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00158/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00158 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev