CVE-2022-29172: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Auth0 is an authentication broker that supports both social and enterprise identity providers, including Active Directory, LDAP, Google Apps, and Salesforce. In versions before `11.33.0`, when the “additional signup fields” feature [is configured](https://github.com/auth0/lock#additional-sign-up-fields), a malicious actor can inject invalidated HTML code into these additional fields, which is then stored in the service `user_metdata` payload (using the `name` property). Verification emails, when applicable, are generated using this metadata. It is therefor possible for an actor to craft a malicious link by injecting HTML, which is then rendered as the recipient's name within the delivered email template. You are impacted by this vulnerability if you are using `auth0-lock` version `11.32.2` or lower and are using the “additional signup fields” feature in your application. Upgrade to version `11.33.0`.
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: 2022-05-05T23:15Z
Repository: https://github.com/auth0/lock
Language: JavaScript
Files changed: src/__tests__/connection/database/actions.test.js, src/connection/database/actions.js
Commit date: 2022-05-05 11:29:00 +0100
Commit message: Merge pull request from GHSA-7ww6-75fj-jcj7

* Sanitize additional signup fields using DOMPurify

* Remove HTML entirely from additionalSignupFields
Diff: diff --git a/src/__tests__/connection/database/actions.test.js b/src/__tests__/connection/database/actions.test.js
index 6ff9b21c..03d6513f 100644
--- a/src/__tests__/connection/database/actions.test.js
+++ b/src/__tests__/connection/database/actions.test.js
@@ -135,6 +135,77 @@ describe('database/actions.js', () => {
     expect(hookRunner).toHaveBeenCalledTimes(1);
     expect(hookRunner).toHaveBeenCalledWith('signingUp', m, null, expect.any(Function));
     expect(signUpMock.calls.length).toBe(1);
     expect(signUpMock.calls[0][0]).toBe(id);
   });
+
+  it('sanitizes additionalSignUp fields using dompurify', () => {
+    const id = 1;
+    const hookRunner = jest.fn((str, m, context, fn) => fn());
+
+    require('connection/database/index').databaseConnectionName = () => 'test-connection';
+    require('connection/database/index').shouldAutoLogin = () => true;
+
+    // Test different fields using some examples from DOMPurify
+    // https://github.com/cure53/DOMPurify#some-purification-samples-please
+    const m = Immutable.fromJS({
+      field: {
+        email: {
+          value: 'test@email.com'
+        },
+        password: {
+          value: 'testpass'
+        },
+        family_name: {
+          value: 'Test <a href="https://www.google.co.uk">Fake link</a>' // HTML but not malicious
+        },
+        given_name: {
+          value: '<img src=x onerror=alert(1)//>'
+        },
+        name: {
+          value: '<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>'
+        },
+        other_name: {
+          value:
+            '<div onclick=alert(0)><form onsubmit=alert(1)><input onfocus=alert(2) name=parentNode>123</form></div>'
+        }
+      },
+      database: {
+        additionalSignUpFields: [
+          { name: 'family_name', storage: 'root' },
+          { name: 'given_name', storage: 'root' },
+          { name: 'name', storage: 'root' },
+          { name: 'other_name' }
+        ]
+      },
+      core: {
+        hookRunner
+      }
+    });
+
+    swap(setEntity, 'lock', id, m);
+    signUp(id);
+
+    const {
+      validateAndSubmit: { mock: validateAndSubmitMock }
+    } = coreActionsMock();
+
+    validateAndSubmitMock.calls[0][2](m);
+
+    const {
+      signUp: { mock: signUpMock }
+    } = webApiMock();
+
+    expect(signUpMock.calls[0][1]).toMatchObject({
+      connection: 'test-connection',
+      email: 'test@email.com',
+      password: 'testpass',
+      autoLogin: true,
+      family_name: 'Test Fake link',
+      given_name: '',
+      name: 'abc',
+      user_metadata: {
+        other_name: '123'
+      }
+    });
+  });
 });
diff --git a/src/connection/database/actions.js b/src/connection/database/actions.js
index 39713ff9..df96cd44 100644
--- a/src/connection/database/actions.js
+++ b/src/connection/database/actions.js
@@ -1,11 +1,12 @@
-import Immutable, { Map } from 'immutable';
 import { getEntity, read, swap, updateEntity } from '../../store/index';
 import webApi from '../../core/web_api';
 import { closeLock, logIn as coreLogIn, logInSuccess, validateAndSubmit } from '../../core/actions';
 import * as l from '../../core/index';
 import * as c from '../../field/index';
+import { sanitize } from 'dompurify';
+
 import {
   databaseConnection,
   databaseConnectionName,
   databaseConnectionRequiresUsername,
   databaseLogInWithEmail,
@@ -105,11 +106,12 @@ export function signUp(id) {
     if (!additionalSignUpFields(m).isEmpty()) {
       params.user_metadata = {};
       additionalSignUpFields(m).forEach(x => {
         const storage = x.get('storage');
         const fieldName = x.get('name');
-        const fieldValue = c.getFieldValue(m, x.get('name'));
+        const fieldValue = sanitize(c.getFieldValue(m, x.get('name')), { ALLOWED_TAGS: [] });
+
         switch (storage) {
           case 'root':
             params[fieldName] = fieldValue;
             break;
           default:

Vulnerable code: import Immutable, { Map } from 'immutable';
        const fieldValue = c.getFieldValue(m, x.get('name'));
Fixed code: 
  it('sanitizes additionalSignUp fields using dompurify', () => {
    const id = 1;
    const hookRunner = jest.fn((str, m, context, fn) => fn());

    require('connection/database/index').databaseConnectionName = () => 'test-connection';
    require('connection/database/index').shouldAutoLogin = () => true;

    // Test different fields using some examples from DOMPurify
    // https://github.com/cure53/DOMPurify#some-purification-samples-please
    const m = Immutable.fromJS({
      field: {
        email: {
          value: 'test@email.com'
        },
        password: {
          value: 'testpass'
        },
        family_name: {
          value: 'Test <a href="https://www.google.co.uk">Fake link</a>' // HTML but not malicious
        },
        given_name: {
          value: '<img src=x onerror=alert(1)//>'
        },
        name: {
          value: '<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>'
        },
        other_name: {
          value:
            '<div onclick=alert(0)><form onsubmit=alert(1)><input onfocus=alert(2) name=parentNode>123</form></div>'
        }
      },
      database: {
        additionalSignUpFields: [
          { name: 'family_name', storage: 'root' },
          { name: 'given_name', storage: 'root' },
          { name: 'name', storage: 'root' },
          { name: 'other_name' }
        ]
      },
      core: {
        hookRunner
      }
    });

    swap(setEntity, 'lock', id, m);
    signUp(id);

    const {
      validateAndSubmit: { mock: validateAndSubmitMock }
    } = coreActionsMock();

    validateAndSubmitMock.calls[0][2](m);

    const {
      signUp: { mock: signUpMock }
    } = webApiMock();

    expect(signUpMock.calls[0][1]).toMatchObject({
      connection: 'test-connection',
      email: 'test@email.com',
      password: 'testpass',
      autoLogin: true,
      family_name: 'Test Fake link',
      given_name: '',
      name: 'abc',
      user_metadata: {
        other_name: '123'
      }
    });
  });
import { sanitize } from 'dompurify';

        const fieldValue = sanitize(c.getFieldValue(m, x.get('name')), { ALLOWED_TAGS: [] });