CVE-2022-48345: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: sanitize-url (aka @braintree/sanitize-url) before 6.0.2 allows XSS via HTML entities.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-02-24T06:15Z
Repository: https://github.com/braintree/sanitize-url
Language: TypeScript
Files changed: CHANGELOG.md, src/__tests__/test.ts, src/index.ts
Commit date: 2022-10-20 15:12:27 -0700
Commit message: Fix html entity tab (#45)

* fix: correct urls that did not sanitize html encoded colons

* fix: replace html encoded tabs

* update CHANGELOG.md

Co-authored-by: Blade Barringer <blade.barringer@paypal.com>
Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e5d9fa..e40cc26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,12 @@
 # CHANGELOG
 
+## unreleased
+
+- Fix issue where urls in the form `javascript&colon;alert('xss');` were not properly sanitized
+- Fix issue where urls in the form `javasc&Tab;ript:alert('XSS');` were not properly sanitized
+
 ## 6.0.0
 
 **Breaking Changes**
 
 - Decode HTML characters automatically that would result in an XSS vulnerability when rendering links via a server rendered HTML file
diff --git a/src/__tests__/test.ts b/src/__tests__/test.ts
index 51cb224..365dd17 100644
--- a/src/__tests__/test.ts
+++ b/src/__tests__/test.ts
@@ -98,10 +98,11 @@ describe("sanitizeUrl", () => {
       "&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041",
       "&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;",
       "&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29",
       "jav&#x09;ascript:alert('XSS');",
       " &#14; javascript:alert('XSS');",
+      "javasc&Tab;ript: alert('XSS');",
     ];
 
     attackVectors.forEach((vector) => {
       expect(sanitizeUrl(vector)).toBe("about:blank");
     });
@@ -134,10 +135,19 @@ describe("sanitizeUrl", () => {
         expect(sanitizeUrl(`&!*${protocol}:alert(document.domain)`)).toBe(
           "about:blank"
         );
       });
 
+      it(`disallows ${protocol} urls that use &colon; for the colon portion of the url`, () => {
+        expect(sanitizeUrl(`${protocol}&colon;alert(document.domain)`)).toBe(
+          "about:blank"
+        );
+        expect(sanitizeUrl(`${protocol}&COLON;alert(document.domain)`)).toBe(
+          "about:blank"
+        );
+      });
+
       it(`disregards capitalization for ${protocol} urls`, () => {
         // upper case every other letter in protocol name
         const mixedCapitalizationProtocol = protocol
           .split("")
           .map((character, index) => {
diff --git a/src/index.ts b/src/index.ts
index 801dfae..3852569 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,18 +1,20 @@
 const invalidProtocolRegex = /^([^\w]*)(javascript|data|vbscript)/im;
 const htmlEntitiesRegex = /&#(\w+)(^\w|;)?/g;
+const htmlTabEntityRegex = /&tab;/gi;
 const ctrlCharactersRegex =
   /[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim;
-const urlSchemeRegex = /^([^:]+):/gm;
+const urlSchemeRegex = /^.+(:|&colon;)/gim;
 const relativeFirstCharacters = [".", "/"];
 
 function isRelativeUrlWithoutProtocol(url: string): boolean {
   return relativeFirstCharacters.indexOf(url[0]) > -1;
 }
 
 // adapted from https://stackoverflow.com/a/29824550/2601552
 function decodeHtmlCharacters(str: string) {
+  str = str.replace(htmlTabEntityRegex, "&#9;");
   return str.replace(htmlEntitiesRegex, (match, dec) => {
     return String.fromCharCode(dec);
   });
 }
 

Vulnerable code: const urlSchemeRegex = /^([^:]+):/gm;
Fixed code: ## unreleased

- Fix issue where urls in the form `javascript&colon;alert('xss');` were not properly sanitized
- Fix issue where urls in the form `javasc&Tab;ript:alert('XSS');` were not properly sanitized

      "javasc&Tab;ript: alert('XSS');",
      it(`disallows ${protocol} urls that use &colon; for the colon portion of the url`, () => {
        expect(sanitizeUrl(`${protocol}&colon;alert(document.domain)`)).toBe(
          "about:blank"
        );
        expect(sanitizeUrl(`${protocol}&COLON;alert(document.domain)`)).toBe(
          "about:blank"
        );
      });

const htmlTabEntityRegex = /&tab;/gi;
const urlSchemeRegex = /^.+(:|&colon;)/gim;
  str = str.replace(htmlTabEntityRegex, "&#9;");