Closed Bug 2006500 (CVE-2026-0887) Opened 5 months ago Closed 5 months ago

PDF.js vulnerable to CSS injection, leads to exfiltrating saved signatures

Categories

(Firefox :: PDF Viewer, defect, P1)

defect

Tracking

()

VERIFIED FIXED
148 Branch
Tracking Status
firefox-esr115 --- unaffected
firefox-esr140 147+ verified
firefox146 --- wontfix
firefox147 + verified
firefox148 + verified

People

(Reporter: rebane2001, Assigned: calixte)

Details

(4 keywords, Whiteboard: [client-bounty-form] [adv-main147+] [adv-esr140.7+])

Attachments

(7 files, 1 obsolete file)

Attached image demo_minimal.png

Description

Firefox's built-in PDF viewer allows users to create and save signatures. By abusing a CSS injection, it is possible for any website to exfiltrate said signatures. This works even in private browsing mode, as saved signatures appear there too.

The CSS injection can be performed by serving a PDF file from an attacker-controlled website with a Link header to load arbitrary CSS into the PDF viewer. If the user can be convinced to open up the signatures menu (e.g. hold down the Enter key or make a single click on the attacker's website), the CSS attribute selectors can be used to exfiltrate the SVG path of a signature.

This CSS injection can be used for other attacks as well, but I found the signature exfiltration to be the most impactful one.

Repro

There are two repros, one very minimal one for testing, and a more advanced one for exfiltrating a signature.

You will need python3 and Flask installed for both repros.

Minimal

The minimal repro just serves a small PDF file with the following header: Link: <data:text/css,*{rotate:180deg;background:%23F002}>; rel=stylesheet.

To use it, run pdf_css_injection_minimal.py and visit http://127.0.0.1:8000/. You should see all of the UI elements upside down and red.

See demo_minimal.png for an example of what it should look like.

Signature exfil

While this repro works cross-platform, the timings are configured to work best on Windows 10.

Make sure you have at least one signature saved - if not, open any PDF file and create one. Then, run pdf_css_injection_demo.py, visit http://127.0.0.1:8000/, and hold Enter for a couple seconds. The exfiltration will begin and you should see the saved signature slowly appear on the page.

The signature exfiltration repro does a lot behind the scenes:

  1. Loads a PDF into an iframe with a lot of Link headers set.
    • The first Link header loads CSS to help with opening the signatures menu.
    • The rest of the Link headers are behind a media query to load them later on.
  2. Waits for the iframe to load, focuses it, and uses a hash fragment to force select the signatures menu.
    • Since the user is holding Enter, the signatures menu will pop open. It cannot close due to the CSS loaded earlier.
  3. Shrinks the iframe to force the rest of the Link headers to load.
    • The reason for this is to prevent the Link headers from blocking the page from loading.
  4. Repeatedly exfiltrates the d attribute on the SVG path of the signature by 4 digit groups.
  5. Displays the exfiltrated data and SVG path on the page.

I am 100% sure this attack could be made significantly more reliable and way faster, this here is just a proof of concept I quickly threw together.

See demo_exfil.mp4 for an example of what this repro should look like.

Fix

My suggested fix would be ignoring the Link header for the built-in PDF viewer. CSP might also help, though I'm not sure if that'd be possible to implement.

Flags: sec-bounty?
Attached video demo_exfil.mp4 (obsolete) —
Component: Security → PDF Viewer
Attached video demo_exfil.webm

replacing with a better quality version of the demo video

Attachment #9533506 - Attachment is obsolete: true

Also good to probably note that this CSS injection applies to all content types, not just PDFs, but the PDF viewer is the only component I've found an attack for so far.

Assignee: nobody → cdenizet
Severity: -- → S3
Status: UNCONFIRMED → NEW
Ever confirmed: true
Priority: -- → P1
Status: NEW → ASSIGNED

For sure, we mustn't inject in the viewer whatever css not under our control, so I think the fix is just a matter of removing the Link entry from the response header.
Anyway, in the current state of the POC, it's pretty hard to be able to exfiltrate a drawn signature but it's possible.

Attached file (secure)

(In reply to Lyra Rebane from comment #5)

Also good to probably note that this CSS injection applies to all content types, not just PDFs,

This does not apply to media documents. I tested more examples here:

  • example: works for plain text (red document)
  • example: does not work for png image viewer (normal background)
  • example: json viewer (desktop only, not Android, on Android it renders as plain text). Does not work, background remains the default color; the stylesheet load is blocked by CSP via default-src 'none';
  • example: pdf viewer (red background, also on Android)

So in practice the only vulnerable component is the PDF viewer. The special thing about the PDF viewer is that it has access to user-stored data (signatures), otherwise the impact would be minimal since the server could also have responded with a custom HTML if it wanted to spoof the UI.

Very clever, taking advantage of standard HTTP/HTML mechanisms to attack our "non-web" features that we have implemented using web technologies. The #hash focusing shouldn't be allowed either, and as Tom alluded to in the patch there are other potentially troublesome headers like CSP, maybe referrer-policy, permission-policy and others that shouldn't be allowed for this kind of feature. permission-policy wouldn't be an issue for PDFs, but might for other features.

The "holding enter down" trick verges on "clickjacking". Do we need to protect PDFs from framing attacks, too?

https://hg-edge.mozilla.org/mozilla-central/rev/22ec6f7c326f

Calixte, there's still time to get this uplifted to Beta and ESR140 this cycle if desired, but we'll need uplift requests for both ASAP if so.

Group: firefox-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 5 months ago
Flags: needinfo?(cdenizet)
Flags: in-testsuite+
Resolution: --- → FIXED
Target Milestone: --- → 148 Branch

firefox-beta Uplift Approval Request

  • User impact if declined: It's fixing a sec-moderate issue.
  • Code covered by automated testing: yes
  • Fix verified in Nightly: no
  • Needs manual QE test: yes
  • Steps to reproduce for manual QE testing: Check the POC in comment #0.
  • Risk associated with taking this patch: low
  • Explanation of risk level: The patch is basic and limited to the pdf viewer.
  • String changes made/needed: No
  • Is Android affected?: no
Attachment #9535466 - Flags: approval-mozilla-beta?
Flags: qe-verify+
Attachment #9535468 - Flags: approval-mozilla-esr140?

firefox-esr140 Uplift Approval Request

  • User impact if declined: It's fixing a sec-moderate issue.
  • Code covered by automated testing: yes
  • Fix verified in Nightly: no
  • Needs manual QE test: yes
  • Steps to reproduce for manual QE testing: Check the POC in comment #0.
  • Risk associated with taking this patch: low
  • Explanation of risk level: The patch is basic and limited to the pdf viewer.
  • String changes made/needed: No
  • Is Android affected?: no
Attachment #9535466 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
Attachment #9535468 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+
QA Whiteboard: [sec] [uplift] [qa-ver-needed-c148/b147]

I reproduced this issue on an affected Nightly build (2025-12-16) using the minimal test case from comment 0 on Windows 11.

The issue is verified as fixed on the latest builds: Nightly 148.0a1, Beta 147.0b10 (downloaded from Treeherder), and ESR 140.7.0esr (downloaded from Treeherder), on Windows 11.

Status: RESOLVED → VERIFIED
QA Whiteboard: [sec] [uplift] [qa-ver-needed-c148/b147] → [sec] [uplift] [qa-ver-done-c148/b147]
Flags: qe-verify+
QA Contact: cgeorgiu

(clearing obsolete needinfo; the uplift request has since been submitted and processed)

Flags: needinfo?(cdenizet)

Note: changing ESR115 from wontfix to unaffected. While the UI is still vulnerable to CSS injection, there is no sensitive information to extract, because the signature feature only landed in 138 (bug 1946738).

Whiteboard: [client-bounty-form] → [client-bounty-form] [adv-main147+] [adv-esr140.7+]
Alias: CVE-2026-0887
Flags: sec-bounty? → sec-bounty+

Thank you for the clear write-up and analysis, which figured into our bounty calculation.

Thank you! Would it be possible to re-send the sec-bounty email? I didn't have PGP set up so I didn't get the e-mail.

Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: