CVE-2024-32463: Insufficient Information (NVD-CWE-noinfo)
value: phlex is an open source framework for building object-oriented views in Ruby. There is a potential cross-site scripting (XSS) vulnerability that can be exploited via maliciously crafted user data. The filter to detect and prevent the use of the `javascript:` URL scheme in the `href` attribute of an `<a>` tag could be bypassed with tab `\t` or newline `\n` characters between the characters of the protocol, e.g. `java\tscript:`. This vulnerability is fixed in 1.10.1, 1.9.2, 1.8.3, 1.7.2, 1.6.3, 1.5.3, and 1.4.2. Configuring a Content Security Policy that does not allow `unsafe-inline` would effectively prevent this vulnerability from being exploited.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-17T16:15Z
Repository: https://github.com/phlex-ruby/phlex
Language: Ruby
Files changed: lib/phlex/sgml.rb, test/phlex/view/naughty_business.rb
Commit date: 2024-04-16 23:01:06 +0100
Commit message: Fix XSS

Diff: diff --git a/lib/phlex/sgml.rb b/lib/phlex/sgml.rb
index 161be94..994adc3 100644
--- a/lib/phlex/sgml.rb
+++ b/lib/phlex/sgml.rb
@@ -380,11 +380,11 @@ module Phlex
 					when Symbol then k.name.tr("_", "-")
 					else raise ArgumentError, "Attribute keys should be Strings or Symbols."
 				end
 
 				lower_name = name.downcase
-				next if lower_name == "href" && v.start_with?(/\s*javascript:/i)
+				next if lower_name == "href" && v.to_s.downcase.tr("\t \n", "").start_with?("javascript:")
 
 				# Detect unsafe attribute names. Attribute names are considered unsafe if they match an event attribute or include unsafe characters.
 				if HTML::EVENT_ATTRIBUTES.include?(lower_name) || name.match?(/[<>&"']/)
 					raise ArgumentError, "Unsafe attribute name detected: #{k}."
 				end
diff --git a/test/phlex/view/naughty_business.rb b/test/phlex/view/naughty_business.rb
index 6599191..ad5d692 100644
--- a/test/phlex/view/naughty_business.rb
+++ b/test/phlex/view/naughty_business.rb
@@ -83,10 +83,82 @@ describe Phlex::HTML do
 		it "strips the javascript protocol" do
 			expect(output).to be == %{<a>naughty link</a>}
 		end
 	end
 
+	with "naughty javascript link protocol with a hidden tab character" do
+		view do
+			def view_template
+				a(href: "\tjavascript:alert(1)") { "XSS" }
+				a(href: "j\tavascript:alert(1)") { "XSS" }
+				a(href: "ja\tvascript:alert(1)") { "XSS" }
+				a(href: "jav\tascript:alert(1)") { "XSS" }
+				a(href: "java\tscript:alert(1)") { "XSS" }
+				a(href: "javas\tcript:alert(1)") { "XSS" }
+				a(href: "javasc\tript:alert(1)") { "XSS" }
+				a(href: "javascr\tipt:alert(1)") { "XSS" }
+				a(href: "javascri\tpt:alert(1)") { "XSS" }
+				a(href: "javascrip\tt:alert(1)") { "XSS" }
+				a(href: "javascript\t:alert(1)") { "XSS" }
+				a(href: "javascript:\talert(1)") { "XSS" }
+			end
+		end
+
+		it "strips the javascript protocol" do
+			expect(output.scan("<a>").size).to be == 12
+			expect(output.scan("href").size).to be == 0
+		end
+	end
+
+	with "naughty javascript link protocol with a hidden newline character" do
+		view do
+			def view_template
+				a(href: "\njavascript:alert(1)") { "XSS" }
+				a(href: "j\navascript:alert(1)") { "XSS" }
+				a(href: "ja\nvascript:alert(1)") { "XSS" }
+				a(href: "jav\nascript:alert(1)") { "XSS" }
+				a(href: "java\nscript:alert(1)") { "XSS" }
+				a(href: "javas\ncript:alert(1)") { "XSS" }
+				a(href: "javasc\nript:alert(1)") { "XSS" }
+				a(href: "javascr\nipt:alert(1)") { "XSS" }
+				a(href: "javascri\npt:alert(1)") { "XSS" }
+				a(href: "javascrip\nt:alert(1)") { "XSS" }
+				a(href: "javascript\n:alert(1)") { "XSS" }
+				a(href: "javascript:\nalert(1)") { "XSS" }
+			end
+		end
+
+		it "strips the javascript protocol" do
+			expect(output.scan("<a>").size).to be == 12
+			expect(output.scan("href").size).to be == 0
+		end
+	end
+
+	with "naughty javascript link protocol with a hidden whitespace character" do
+		view do
+			def view_template
+				a(href: " javascript:alert(1)") { "XSS" }
+				a(href: "j avascript:alert(1)") { "XSS" }
+				a(href: "ja vascript:alert(1)") { "XSS" }
+				a(href: "jav ascript:alert(1)") { "XSS" }
+				a(href: "java script:alert(1)") { "XSS" }
+				a(href: "javas cript:alert(1)") { "XSS" }
+				a(href: "javasc ript:alert(1)") { "XSS" }
+				a(href: "javascr ipt:alert(1)") { "XSS" }
+				a(href: "javascri pt:alert(1)") { "XSS" }
+				a(href: "javascrip t:alert(1)") { "XSS" }
+				a(href: "javascript :alert(1)") { "XSS" }
+				a(href: "javascript: alert(1)") { "XSS" }
+			end
+		end
+
+		it "strips the javascript protocol" do
+			expect(output.scan("<a>").size).to be == 12
+			expect(output.scan("href").size).to be == 0
+		end
+	end
+
 	Phlex::HTML::EVENT_ATTRIBUTES.each do |event_attribute|
 		with "with naughty #{event_attribute} attribute" do
 			naughty_attributes = { event_attribute => "alert(1);" }
 
 			view do

Vulnerable code: 				next if lower_name == "href" && v.start_with?(/\s*javascript:/i)
Fixed code: 				next if lower_name == "href" && v.to_s.downcase.tr("\t \n", "").start_with?("javascript:")
	with "naughty javascript link protocol with a hidden tab character" do
		view do
			def view_template
				a(href: "\tjavascript:alert(1)") { "XSS" }
				a(href: "j\tavascript:alert(1)") { "XSS" }
				a(href: "ja\tvascript:alert(1)") { "XSS" }
				a(href: "jav\tascript:alert(1)") { "XSS" }
				a(href: "java\tscript:alert(1)") { "XSS" }
				a(href: "javas\tcript:alert(1)") { "XSS" }
				a(href: "javasc\tript:alert(1)") { "XSS" }
				a(href: "javascr\tipt:alert(1)") { "XSS" }
				a(href: "javascri\tpt:alert(1)") { "XSS" }
				a(href: "javascrip\tt:alert(1)") { "XSS" }
				a(href: "javascript\t:alert(1)") { "XSS" }
				a(href: "javascript:\talert(1)") { "XSS" }
			end
		end

		it "strips the javascript protocol" do
			expect(output.scan("<a>").size).to be == 12
			expect(output.scan("href").size).to be == 0
		end
	end

	with "naughty javascript link protocol with a hidden newline character" do
		view do
			def view_template
				a(href: "\njavascript:alert(1)") { "XSS" }
				a(href: "j\navascript:alert(1)") { "XSS" }
				a(href: "ja\nvascript:alert(1)") { "XSS" }
				a(href: "jav\nascript:alert(1)") { "XSS" }
				a(href: "java\nscript:alert(1)") { "XSS" }
				a(href: "javas\ncript:alert(1)") { "XSS" }
				a(href: "javasc\nript:alert(1)") { "XSS" }
				a(href: "javascr\nipt:alert(1)") { "XSS" }
				a(href: "javascri\npt:alert(1)") { "XSS" }
				a(href: "javascrip\nt:alert(1)") { "XSS" }
				a(href: "javascript\n:alert(1)") { "XSS" }
				a(href: "javascript:\nalert(1)") { "XSS" }
			end
		end

		it "strips the javascript protocol" do
			expect(output.scan("<a>").size).to be == 12
			expect(output.scan("href").size).to be == 0
		end
	end

	with "naughty javascript link protocol with a hidden whitespace character" do
		view do
			def view_template
				a(href: " javascript:alert(1)") { "XSS" }
				a(href: "j avascript:alert(1)") { "XSS" }
				a(href: "ja vascript:alert(1)") { "XSS" }
				a(href: "jav ascript:alert(1)") { "XSS" }
				a(href: "java script:alert(1)") { "XSS" }
				a(href: "javas cript:alert(1)") { "XSS" }
				a(href: "javasc ript:alert(1)") { "XSS" }
				a(href: "javascr ipt:alert(1)") { "XSS" }
				a(href: "javascri pt:alert(1)") { "XSS" }
				a(href: "javascrip t:alert(1)") { "XSS" }
				a(href: "javascript :alert(1)") { "XSS" }
				a(href: "javascript: alert(1)") { "XSS" }
			end
		end

		it "strips the javascript protocol" do
			expect(output.scan("<a>").size).to be == 12
			expect(output.scan("href").size).to be == 0
		end
	end