Reflected cross-site scripting (XSS) In postcss
Description
PostCSS has XSS via Unescaped in its CSS Stringify Output
PostCSS: XSS via Unescaped </style> in CSS Stringify Output
Summary
PostCSS v8.5.5 (latest) does not escape </style> sequences when stringifying CSS ASTs. When user-submitted CSS is parsed and re-stringified for embedding in HTML <style> tags, </style> in CSS values breaks out of the style context, enabling XSS.
Proof of Concept
const postcss = require('postcss'); // Parse user CSS and re-stringify for page embedding const userCSS = 'body { content: "</style><script>alert(1)</script><style>"; }'; const ast = postcss.parse(userCSS); const output = ast.toResult().css; const html = `<style>${output}</style>`; ...
Tested output (Node.js v22, postcss v8.5.5):
Input: body { content: "</style><script>alert(1)</script><style>"; } Output: body { content: "</style><script>alert(1)</script><style>"; } Contains </style>: true
Impact
Impact non-bundler use cases since bundlers for XSS on their own. Requires some PostCSS plugin to have malware code, which can inject XSS to website.
Suggested Fix
Escape </style in all stringified output values:
output = output.replace(/<\/(style)/gi, '<\\/$1');
Credits
Discovered and reported by Sunil Kumar (@TharVid)
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Package | Affected version | Patched versions |
|---|---|---|---|
npm | 8.5.10 | ||
debian 11 | - | ||
debian 12 | - | ||
debian 13 | - | ||
debian 14 | 8.5.12+~cs9.3.32-1 | ||
rpm rhel10 | - | - | |
rpm rhel7 | - | - | |
rpm rhel8 | - | - | |
rpm rhel9 | - | - | |
rpm rhel9 | - | - |
1-10 of 19
10
Aliases
References