CVE-2021-23495 (published 2022-02-25T20:15Z)

The package karma before 6.3.16 are vulnerable to Open Redirect due to missing validation of the return_url query parameter.

Severity: MEDIUM
CVSS v2 base score: 5.8 (AV:N/AC:M/Au:N/C:P/I:P/A:N)
CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-601
cwe_name: URL Redirection to Untrusted Site ('Open Redirect')
description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
extended_description: An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance. Whether this issue poses a vulnerability will be subject to the intended behavior of the application. For example, a search engine might intentionally provide redirects to arbitrary URLs.
         

Fix information:
repo_url: https://github.com/karma-runner/karma
commit_details: repo_url: https://github.com/karma-runner/karma
author: Jonathan Ginsburg
author_date: 2022-02-10 12:23:41-06:00
msg: fix(security): mitigate the "Open Redirect Vulnerability"
repository: repo_url: https://github.com/karma-runner/karma
repo_name: karma-runner/karma
description: Spectacular Test Runner for JavaScript
repo_language: JavaScript
owner: karma-runner
file_changes: filename: karma.js
change_type: ModificationType.MODIFY
diff: @@ -239,7 +239,15 @@ function Karma (updater, socket, iframe, opener, navigator, location, document)
       self.updater.updateTestStatus('complete')
     }
     if (returnUrl) {
-      if (!/^https?:\/\//.test(returnUrl)) {
+      var isReturnUrlAllowed = false
+      for (var i = 0; i < this.config.allowedReturnUrlPatterns.length; i++) {
+        var allowedReturnUrlPattern = new RegExp(this.config.allowedReturnUrlPatterns[i])
+        if (allowedReturnUrlPattern.test(returnUrl)) {
+          isReturnUrlAllowed = true
+          break
+        }
+      }
+      if (!isReturnUrlAllowed) {
         throw new Error(
           'Security: Navigation to '.concat(
             returnUrl,

programming_language: JavaScript
filename: 01-configuration-file.md
change_type: ModificationType.MODIFY
diff: @@ -277,6 +277,15 @@ upon the completion of running the tests. Setting this to false is useful when e
 
 If true, Karma does not display the banner and browser list. Useful when using karma on component tests with screenshots.
 
+## client.allowedReturnUrlPatterns
+**Type:** Array
+
+**Default:** `['^https?://']`
+
+**Description:** Define the string representations of the regular expressions that will be allowed for the `return_url` query parameter.
+
+If the value of the `return_url` query parameter does not match any regular expression derived from the string representation of each of the elements of this array, navigation to it will be blocked.
+
 ## colors
 **Type:** Boolean
 

programming_language: Markdown
filename: config.js
change_type: ModificationType.MODIFY
diff: @@ -17,19 +17,19 @@ let TYPE_SCRIPT_AVAILABLE = false
 try {
   require('coffeescript').register()
   COFFEE_SCRIPT_AVAILABLE = true
-} catch (e) {}
+} catch {}
 
 // LiveScript is required here to enable config files written in LiveScript.
 // It's not directly used in this file.
 try {
   require('LiveScript')
   LIVE_SCRIPT_AVAILABLE = true
-} catch (e) {}
+} catch {}
 
 try {
   require('ts-node')
   TYPE_SCRIPT_AVAILABLE = true
-} catch (e) {}
+} catch {}
 
 class Pattern {
   constructor (pattern, served, included, watched, nocache, type, isBinary) {
@@ -324,7 +324,8 @@ class Config {
       useIframe: true,
       runInParent: false,
       captureConsole: true,
-      clearContext: true
+      clearContext: true,
+      allowedReturnUrlPatterns: ['^https?://']
     }
     this.browserDisconnectTimeout = 2000
     this.browserDisconnectTolerance = 0

programming_language: JavaScript
filename: karma.js
change_type: ModificationType.MODIFY
diff: @@ -249,7 +249,15 @@ function Karma (updater, socket, iframe, opener, navigator, location, document)
       self.updater.updateTestStatus('complete')
     }
     if (returnUrl) {
-      if (!/^https?:\/\//.test(returnUrl)) {
+      var isReturnUrlAllowed = false
+      for (var i = 0; i < this.config.allowedReturnUrlPatterns.length; i++) {
+        var allowedReturnUrlPattern = new RegExp(this.config.allowedReturnUrlPatterns[i])
+        if (allowedReturnUrlPattern.test(returnUrl)) {
+          isReturnUrlAllowed = true
+          break
+        }
+      }
+      if (!isReturnUrlAllowed) {
         throw new Error(
           'Security: Navigation to '.concat(
             returnUrl,

programming_language: JavaScript
filename: karma.spec.js
change_type: ModificationType.MODIFY
diff: @@ -442,15 +442,18 @@ describe('Karma', function () {
       assert(spyResult.called)
     })
 
-    it('should navigate the client to return_url if specified', function (done) {
+    it('should navigate the client to return_url if specified and allowed', function (done) {
+      var config = {
+        // The default value.
+        allowedReturnUrlPatterns: ['^https?://']
+      }
       windowLocation.search = '?id=567&return_url=http://return.com'
       socket = new MockSocket()
       k = new ClientKarma(updater, socket, iframe, windowStub, windowNavigator, windowLocation)
       clientWindow = { karma: k }
       ck = new ContextKarma(ContextKarma.getDirectCallParentKarmaMethod(clientWindow))
-      ck.config = {}
+      socket.emit('execute', config)
 
-      sinon.spy(socket, 'disconnect')
       clock.tick(500)
 
       ck.complete()
@@ -462,6 +465,26 @@ describe('Karma', function () {
       clock.tick(10)
     })
 
+    it('should not navigate the client to return_url if not allowed', function () {
+      var config = {
+        allowedReturnUrlPatterns: []
+      }
+
+      windowLocation.search = '?id=567&return_url=javascript:alert(document.domain)'
+      socket = new MockSocket()
+      k = new ClientKarma(updater, socket, iframe, windowStub, windowNavigator, windowLocation)
+      clientWindow = { karma: k }
+      ck = new ContextKarma(ContextKarma.getDirectCallParentKarmaMethod(clientWindow))
+      socket.emit('execute', config)
+
+      try {
+        ck.complete()
+        throw new Error('An error should have been caught.')
+      } catch (error) {
+        assert(/Error: Security: Navigation to .* was blocked to prevent malicious exploits./.test(error))
+      }
+    })
+
     it('should clear context window upon complete when clearContext config is true', function () {
       var config = ck.config = {
         clearContext: true

programming_language: JavaScript