From 08aaac33bccc1585deff59dc4fa268223e296a07 Mon Sep 17 00:00:00 2001
From: Brian Wolff <bawolff+wn@gmail.com>
Date: Fri, 6 Dec 2024 14:02:16 -0800
Subject: [PATCH] Ensure that <[a-z] cannot be in stylesheets, as it looks like
 html

In foreign content (svg) the rules are different and anything that
looks like a tag can make one, where in normal html it is just
</style. Block both just in case. This also goes along with a
change to the sanitizer to ensure that suspicious tokens are
separated.

Bug: T381617
Change-Id: I2cb4d046774dd8c8a446f1d072c970c3bc05f95d
---
 i18n/en.json                              | 2 +-
 i18n/qqq.json                             | 2 +-
 includes/TemplateStylesContentHandler.php | 9 ++++++---
 3 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/i18n/en.json b/i18n/en.json
index c95306d..4d379eb 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -14,7 +14,7 @@
 	"templatestyles-bad-src": "Page [[:$1|$2]] must have content model \"{{int:content-model-sanitized-css}}\" for TemplateStyles (current model is \"$3\").",
 	"templatestyles-errorcomment": "Errors processing stylesheet [[:$1]] (rev $2):\n$3",
 	"templatestyles-size-exceeded": "The stylesheet is larger than the maximum size of $2.",
-	"templatestyles-end-tag-injection": "The supplied stylesheet contains <code>&lt;/style</code>, which is not allowed.",
+	"templatestyles-tag-injection": "The supplied stylesheet contains something that looks like an html tag, which is not allowed.",
 	"content-model-sanitized-css": "Sanitized CSS",
 	"templatestyles-stylesheet-error-category": "TemplateStyles stylesheets with errors",
 	"templatestyles-stylesheet-error-category-desc": "The TemplateStyles stylesheet has an error.",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index fcc4c61..ecf5624 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -17,7 +17,7 @@
 	"templatestyles-bad-src": "Error message displayed when the title specified is not a usable stylesheet. Parameters:\n* $1 - The title specified.\n* $2 - The title with wikitext escaped.\n* $3 - Current content model of the page in question.",
 	"templatestyles-errorcomment": "Formatting for the comment used to display TemplateStyles errors encountered during the parse. Parameters:\n* $1 - Source stylesheet.\n* $2 - Revision of the stylesheet.\n* $3 - Errors.",
 	"templatestyles-size-exceeded": "Error returned when the stylesheet is more than $wgTemplateStylesMaxStylesheetSize bytes. Parameters:\n* $1 - Maximum size in bytes\n* $2 - Maximum size in \"human units\" (i.e. KB, MB, GB, etc).",
-	"templatestyles-end-tag-injection": "Error returned when the stylesheet contains <code>&lt;/style</code>, which is likely an attempt at HTML injection.",
+	"templatestyles-tag-injection": "Error returned when the stylesheet contains html, which might be malicious",
 	"content-model-sanitized-css": "Name for TemplateStyles sanitized-css content model.",
 	"templatestyles-stylesheet-error-category": "{{tracking category name}}\nTracking category for TemplateStyles stylesheets with errors.",
 	"templatestyles-stylesheet-error-category-desc": "Description on [[Special:TrackingCategories]] for the {{msg-mw|templatestyles-stylesheet-error-category}} tracking category.",
diff --git a/includes/TemplateStylesContentHandler.php b/includes/TemplateStylesContentHandler.php
index ae5146e..abb5bb0 100644
--- a/includes/TemplateStylesContentHandler.php
+++ b/includes/TemplateStylesContentHandler.php
@@ -167,10 +167,13 @@ class TemplateStylesContentHandler extends CodeContentHandler {
 		// Stringify it while minifying
 		$value = CSSUtil::stringify( $stylesheet, [ 'minify' => $options['minify'] ] );
 
-		// Sanity check, don't allow "</style" if one somehow sneaks through the sanitizer
-		if ( preg_match( '!</style!i', $value ) ) {
+		// Sanity check, don't allow "</style" if one somehow sneaks through the sanitizer.
+		// Also, don't allow "<ABC" if one somehow sneaks through the sanitizer
+		// This matters if the style tag is in a foreign namespace (e.g. <svg>) which
+		// has different rules from normal HTML.
+		if ( preg_match( '!</?[a-z]!i', $value ) ) {
 			$value = '';
-			$status->fatal( 'templatestyles-end-tag-injection' );
+			$status->fatal( 'templatestyles-tag-injection' );
 		}
 
 		if ( !$options['novalue'] ) {
-- 
2.39.2

