Log forging via unneutralized CRLF in raw.go getFiles/rawDirHandler error logging
low Fully Valid high confidence
Status
Fully Valid
Canonical authored version of the incomplete-log-forging finding: the raw.go:181 sink processes attacker CRLF unneutralized and is reachable unauthenticated, a complete and proven weakness at a sink the prior %q fix did not cover. Supersedes the older duplicate DOK-100100 and complements (does not duplicate) the published DOK-100023.
Repository / Component
Plain-English Description
When a file request fails, the server writes the requested file name straight into its log. An attacker can put line breaks in that name, letting them inject fake lines into the log file. This works even with no login when a public folder share is enabled.
Description of the Underlying Issue
getFiles (raw.go:151-154) and rawDirHandler (raw.go:178-181) log an attacker-supplied file path with a plain %s format verb and no CR/LF neutralization. The file names originate from the files= query parameter, which parseQueryFiles (raw.go:29-42) only slash-cleans and filepath.Joins — it does not strip control characters. A newline embedded in the name therefore reaches log.Printf verbatim, breaking the one-line-per-event invariant of the log. This is a distinct sink from the already-remediated r.URL.Path forging (DOK-100023 fixed at data.go:71 with %q); the prior fix's scope did not cover raw.go:154 or raw.go:181.
Potential Attack
An attacker crafts a request such as GET /api/public/dl/{dir-hash}?files=x%0A<forged-log-line>. The named entry fails to Stat (ENOENT), driving execution into the error branch that logs the file name with %s. The embedded %0A (newline) is written directly, appending a fabricated log record. Because unprotected public directory shares are reachable with zero credentials (public.go:120-131), the attacker needs no account; any authenticated Download user can also reach the same sink through rawHandler -> getFiles.
Outcomes of Potential Attack
The attacker can inject forged log lines, potentially fabricating events, corrupting audit trails, masking real activity, or (depending on the downstream log consumer) injecting content that misleads or is mis-parsed by log-processing tooling. Impact is confined to log integrity; there is no code execution or data exposure from this sink.
Affected Scope
log.Printf sinks in getFiles/rawDirHandler (raw.go:154, raw.go:181); unauthenticated via unprotected public directory shares, plus /api/raw for authenticated Download users.
Suggested Fix (plain english)
Escape or strip carriage-return and line-feed characters before writing user-controlled file names to the log.
Suggested Fix (detailed)
Change raw.go:154 and raw.go:181 to log the file name with %q instead of %s, or route it through a shared CRLF-neutralizing logging helper used across the http package so future sinks are covered by construction. Verify by confirming no user-controlled operand reaches a log call with a bare %s; add a regression check that a files= value containing %0A/%0D produces a single escaped log line. Escape/strip CR/LF at raw.go:154 and raw.go:181 (e.g., %q); introduce a CRLF-neutralizing logging helper used across the http package.
Validation
Canonical authored version of the incomplete-log-forging finding: the raw.go:181 sink processes attacker CRLF unneutralized and is reachable unauthenticated, a complete and proven weakness at a sink the prior %q fix did not cover. Supersedes the older duplicate DOK-100100 and complements (does not duplicate) the published DOK-100023.
Full Evidence
http/raw.go:178-181 for _, fname := range filenames { ...; log.Printf("Failed to get files from %s: %v", fname, err) }http/raw.go:151-154 recursion sink logging fPath with %s
http/raw.go:29-42 parseQueryFiles double-decodes files= and applies only slashClean/filepath.Join
http/data.go:71 %q fix scope limited to r.URL.Path
http/public.go:130-131,120-127 unauthenticated reach for unprotected public dir shares
Proven fact: Attacker-controlled control characters (CRLF) from the files= query reach a %s log write at raw.go:181 with no neutralization (verified through the full decode/clean/join chain).
Proven fact: Reachable with zero credentials on an unprotected public directory share; also reachable by any authenticated Download user via rawHandler -> getFiles.
Proven fact: This is a distinct sink from the already-fixed r.URL.Path sink (DOK-100023 @ data.go:71); no state record previously covered raw.go:154/181.
Unvalidated fact: Downstream log-consumer sensitivity to injected newlines (impact amplifier only).
Unvalidated fact: Whether the residual %v err operand at data.go:71 should be tracked as a separate UPDATE to DOK-100023 (out of scope for this raw.go sink).
http/raw.go:178-181 — rawDirHandler logs fname with %s: log.Printf("Failed to get files from %s: %v", fname, err)http/raw.go:151-154 — getFiles recursion sink logging fPath with %s
http/raw.go:29-42 — parseQueryFiles double-decodes files= and applies only slashClean/filepath.Join, no control-character stripping
http/data.go:71 — the prior %q fix (DOK-100023) is scoped to r.URL.Path only, not the raw.go sinks
http/public.go:120-131 — unauthenticated reachability for unprotected public directory shares
Proven fact: Attacker-controlled CRLF from the files= query reaches a %s log write at raw.go:181 with no neutralization, verified through the full decode/clean/join chain.
Proven fact: The sink is reachable with zero credentials on an unprotected public directory share, and by any authenticated Download user via rawHandler -> getFiles.
Proven fact: This sink is distinct from the already-fixed r.URL.Path sink (DOK-100023 @ data.go:71); no prior state record covered raw.go:154/181.
Unvalidated fact: Downstream log-consumer sensitivity to injected newlines (an impact amplifier, not a precondition for the injection itself).
Unvalidated fact: Whether the residual %v err operand at data.go:71 warrants a separate UPDATE to DOK-100023 — out of scope for this raw.go sink.
http/raw.go:29-42 parseQueryFiles: names := Split(Query().Get("files"),","), url.QueryUnescape, slashClean, filepath.Join (no control-char stripping)http/raw.go:178-181 for _, fname := range filenames { ... log.Printf("Failed to get files from %s: %v", fname, err)http/raw.go:151-154 sibling sink logging fPath with %s
http/data.go:71 the %q fix covers only r.URL.Path, not these raw.go sinks
http/public.go:120-127 publicDlHandler -> rawDirHandler reaches this path unauthenticated for public dir shares