CVE-2024-26148: Insufficient Information (NVD-CWE-noinfo)
value: Querybook is a user interface for querying big data. Prior to version 3.31.1, there is a vulnerability in Querybook's rich text editor that enables users to input arbitrary URLs without undergoing necessary validation. This particular security flaw allows the use of `javascript:` protocol which can potentially trigger arbitrary client-side execution. The most extreme exploit of this flaw could occur when an admin user unknowingly clicks on a cross-site scripting URL, thereby unintentionally compromising admin role access to the attacker. A patch to rectify this issue has been introduced in Querybook version `3.31.1`. The fix is backward compatible and automatically fixes existing DataDocs. There are no known workarounds for this issue, except for manually checking each URL prior to clicking on them.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-02-21T23:15Z
Repository: https://github.com/pinterest/querybook
Language: Other
Files changed: package.json, querybook/webapp/lib/richtext/index.tsx
Commit date: 2024-02-21 13:26:59 -0800
Commit message: fix: XSS injection with Querybook RichTextEditor (#1412)


Diff: diff --git a/package.json b/package.json
index 8e2ba874..634963fd 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
     "name": "querybook",
-    "version": "3.31.0",
+    "version": "3.31.1",
     "description": "A Big Data Webapp",
     "private": true,
     "scripts": {
         "test": "jest",
         "build": "npx webpack",
diff --git a/querybook/webapp/lib/richtext/index.tsx b/querybook/webapp/lib/richtext/index.tsx
index c3f34e14..45a62357 100644
--- a/querybook/webapp/lib/richtext/index.tsx
+++ b/querybook/webapp/lib/richtext/index.tsx
@@ -1,20 +1,36 @@
 import * as DraftJs from 'draft-js';
 import type { Stack } from 'immutable';
-import React from 'react';
+import React, { useMemo } from 'react';
 
 import { Link } from 'ui/Link/Link';
 
 interface IUrlLinkProps {
     contentState: DraftJs.ContentState;
     entityKey: string;
 }
 
 const UrlLink: React.FunctionComponent<IUrlLinkProps> = (props) => {
-    const { url } = props.contentState.getEntity(props.entityKey).getData();
+    const { url }: { url: string } = props.contentState
+        .getEntity(props.entityKey)
+        .getData();
+    const sanitizedUrl = useMemo(() => {
+        // sanitize URL to prevent XSS
+        try {
+            const urlObj = new URL(url);
+            if (['http:', 'https:'].includes(urlObj.protocol)) {
+                return urlObj.href;
+            } else {
+                return undefined;
+            }
+        } catch (error) {
+            return undefined;
+        }
+    }, [url]);
+
     return (
-        <Link to={url} newTab>
+        <Link to={sanitizedUrl ?? 'about:blank'} newTab>
             {props.children}
         </Link>
     );
 };
 

Vulnerable code:     "version": "3.31.0",
import React from 'react';
    const { url } = props.contentState.getEntity(props.entityKey).getData();
        <Link to={url} newTab>
Fixed code:     "version": "3.31.1",
import React, { useMemo } from 'react';
    const { url }: { url: string } = props.contentState
        .getEntity(props.entityKey)
        .getData();
    const sanitizedUrl = useMemo(() => {
        // sanitize URL to prevent XSS
        try {
            const urlObj = new URL(url);
            if (['http:', 'https:'].includes(urlObj.protocol)) {
                return urlObj.href;
            } else {
                return undefined;
            }
        } catch (error) {
            return undefined;
        }
    }, [url]);

        <Link to={sanitizedUrl ?? 'about:blank'} newTab>