CVE-2024-27103: Insufficient Information (NVD-CWE-noinfo)
value: Querybook is a Big Data Querying UI. When a user searches for their queries, datadocs, tables and lists, the search result is marked and highlighted, and this feature uses dangerouslySetInnerHTML which means that if the highlighted result has an XSS payload it will trigger. While the input to dangerouslySetInnerHTML is not sanitized for the data inside of queries which leads to an XSS vulnerability. During the "query auto-suggestion" the name of the suggested tables are set with innerHTML which leads to the XSS vulnerability. A patch to rectify this issue has been introduced in Querybook version 3.31.2.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-02-28T18:15Z
Repository: https://github.com/pinterest/querybook
Language: Other
Files changed: package.json, querybook/webapp/components/DataDocStatementExecution/StatementLog.tsx, querybook/webapp/components/Search/SearchResultItem.tsx, querybook/webapp/lib/sql-helper/sql-autocompleter.ts
Commit date: 2024-02-27 20:05:37 -0800
Commit message: Merge pull request from GHSA-3hjm-9277-5c88

* Fix XSS vulnerabilities

* remove console.log
Diff: diff --git a/package.json b/package.json
index 634963fd..b36beb41 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
     "name": "querybook",
-    "version": "3.31.1",
+    "version": "3.31.2",
     "description": "A Big Data Webapp",
     "private": true,
     "scripts": {
         "test": "jest",
         "build": "npx webpack",
@@ -48,10 +48,11 @@
         "codemirror": "^5.64.0",
         "core-decorators": "0.20.0",
         "core-js": "^3.19.1",
         "cron-parser": "^4.7.0",
         "dagre": "^0.8.5",
+        "dompurify": "^3.0.9",
         "draft-js": "0.11.7",
         "draft-js-export-html": "^1.4.1",
         "draft-js-import-html": "^1.4.1",
         "fast-json-stable-stringify": "^2.1.0",
         "formik": "^2.2.9",
diff --git a/querybook/webapp/components/DataDocStatementExecution/StatementLog.tsx b/querybook/webapp/components/DataDocStatementExecution/StatementLog.tsx
index af35cf30..14f989c3 100644
--- a/querybook/webapp/components/DataDocStatementExecution/StatementLog.tsx
+++ b/querybook/webapp/components/DataDocStatementExecution/StatementLog.tsx
@@ -1,6 +1,7 @@
 import clsx from 'clsx';
+import DOMPurify from 'dompurify';
 import { debounce } from 'lodash';
 import React from 'react';
 import { useDispatch, useSelector } from 'react-redux';
 
 import { IStatementLog } from 'const/queryExecution';
@@ -101,11 +102,13 @@ export const StatementLog: React.FunctionComponent<IStatementLogProps> = ({
                     updateScrollPosition(logRef.current.scrollTop);
                 }
             }}
             className="statement-execution-log-container"
             dangerouslySetInnerHTML={{
-                __html: logText,
+                __html: DOMPurify.sanitize(logText, {
+                    USE_PROFILES: { html: true },
+                }),
             }}
         />
     );
 
     return (
diff --git a/querybook/webapp/components/Search/SearchResultItem.tsx b/querybook/webapp/components/Search/SearchResultItem.tsx
index 8266c94a..5a5b4256 100644
--- a/querybook/webapp/components/Search/SearchResultItem.tsx
+++ b/querybook/webapp/components/Search/SearchResultItem.tsx
@@ -1,5 +1,6 @@
+import DOMPurify from 'dompurify';
 import { escape, escapeRegExp } from 'lodash';
 import React, { useMemo, useRef, useState } from 'react';
 import { useSelector } from 'react-redux';
 
 import { BoardItemAddButton } from 'components/BoardItemAddButton/BoardItemAddButton';
@@ -53,11 +54,13 @@ const HighlightTitle: React.FunctionComponent<{
     return highlightedTitle && highlightedTitle !== 'Untitled' ? (
         <AccentText size="smedium" weight="bold" color="text" hover>
             <div
                 className="result-item-title"
                 dangerouslySetInnerHTML={{
-                    __html: highlightedTitle,
+                    __html: DOMPurify.sanitize(highlightedTitle, {
+                        USE_PROFILES: { html: true },
+                    }),
                 }}
             />
         </AccentText>
     ) : (
         <UntitledText size="smedium" />
@@ -164,12 +167,13 @@ export const QueryItem: React.FunctionComponent<IQueryItemProps> = ({
                 }
             />
             {!isQueryTextExpanded ? (
                 <span
                     dangerouslySetInnerHTML={{
-                        __html: formatHighlightStrings(
-                            queryTextHighlightedContent
+                        __html: DOMPurify.sanitize(
+                            formatHighlightStrings(queryTextHighlightedContent),
+                            { USE_PROFILES: { html: true } }
                         ),
                     }}
                 />
             ) : (
                 getSyntaxHighlightedQueryDOM()
@@ -279,11 +283,14 @@ export const DataDocItem: React.FunctionComponent<IDataDocItemProps> = ({
     const dataDocContent = (preview.highlight || {}).cells;
     const descriptionDOM = dataDocContent && (
         <span
             className="result-item-description"
             dangerouslySetInnerHTML={{
-                __html: formatHighlightStrings(dataDocContent),
+                __html: DOMPurify.sanitize(
+                    formatHighlightStrings(dataDocContent),
+                    { USE_PROFILES: { html: true } }
+                ),
             }}
         />
     );
 
     return (
@@ -379,11 +386,14 @@ export const DataTableItem: React.FunctionComponent<IDataTableItemProps> = ({
 
     const highlightedDescription = (preview.highlight || {}).description;
     const descriptionDOM = highlightedDescription ? (
         <span
             dangerouslySetInnerHTML={{
-                __html: formatHighlightStrings(highlightedDescription),
+                __html: DOMPurify.sanitize(
+                    formatHighlightStrings(highlightedDescription),
+                    { USE_PROFILES: { html: true } }
+                ),
             }}
         />
     ) : (
         description || 'no description'
     );
@@ -482,11 +492,14 @@ export const BoardItem: React.FunctionComponent<{
 
     const highlightedDescription = preview.highlight?.description;
     const descriptionDOM = highlightedDescription ? (
         <span
             dangerouslySetInnerHTML={{
-                __html: formatHighlightStrings(highlightedDescription),
+                __html: DOMPurify.sanitize(
+                    formatHighlightStrings(highlightedDescription),
+                    { USE_PROFILES: { html: true } }
+                ),
             }}
         />
     ) : (
         description || 'No list description'
     );
diff --git a/querybook/webapp/lib/sql-helper/sql-autocompleter.ts b/querybook/webapp/lib/sql-helper/sql-autocompleter.ts
index dae50a09..2e294c20 100644
--- a/querybook/webapp/lib/sql-helper/sql-autocompleter.ts
+++ b/querybook/webapp/lib/sql-helper/sql-autocompleter.ts
@@ -1,12 +1,13 @@
 import { getLanguageSetting, ILanguageSetting } from './sql-setting';
+import DOMPurify from 'dompurify';
+import { bind } from 'lodash-decorators';
 
 import CodeMirror from 'lib/codemirror';
 import { ICodeAnalysis, TableToken } from 'lib/sql-helper/sql-lexer';
 import { reduxStore } from 'redux/store';
 import { SearchTableResource } from 'resource/search';
-import { bind } from 'lodash-decorators';
 
 interface ILineAnalysis {
     statementNum: number;
     context: string;
     reference: TableToken[];
@@ -406,18 +407,21 @@ export class SqlAutoCompleter {
                       `<b style='font-weight: bold'>${data.originalText}</b>`
                   );
 
         const div = document.createElement('div');
         div.className = 'code-editor-autocomplete-wrapper';
-        div.innerHTML = `
+        div.innerHTML = DOMPurify.sanitize(
+            `
             <span class="code-editor-autocomplete-span code-editor-text-span">
                 ${text}
             </span>
             <span class="code-editor-autocomplete-span code-editor-tooltip-span">
                 ${tooltip}
             </span>
-        `;
+        `,
+            { USE_PROFILES: { html: true } }
+        );
 
         element.appendChild(div);
     }
 
     private getAutoSuggestionFormatters(type: string, text: string): Formatter {
diff --git a/yarn.lock b/yarn.lock
index de1d62c5..0d108bbc 100644
Binary files a/yarn.lock and b/yarn.lock differ

Vulnerable code:     "version": "3.31.1",
                __html: logText,
                    __html: highlightedTitle,
                        __html: formatHighlightStrings(
                            queryTextHighlightedContent
                __html: formatHighlightStrings(dataDocContent),
                __html: formatHighlightStrings(highlightedDescription),
                __html: formatHighlightStrings(highlightedDescription),
import { bind } from 'lodash-decorators';
        div.innerHTML = `
        `;
Fixed code:     "version": "3.31.2",
        "dompurify": "^3.0.9",
import DOMPurify from 'dompurify';
                __html: DOMPurify.sanitize(logText, {
                    USE_PROFILES: { html: true },
                }),
import DOMPurify from 'dompurify';
                    __html: DOMPurify.sanitize(highlightedTitle, {
                        USE_PROFILES: { html: true },
                    }),
                        __html: DOMPurify.sanitize(
                            formatHighlightStrings(queryTextHighlightedContent),
                            { USE_PROFILES: { html: true } }
                __html: DOMPurify.sanitize(
                    formatHighlightStrings(dataDocContent),
                    { USE_PROFILES: { html: true } }
                ),
                __html: DOMPurify.sanitize(
                    formatHighlightStrings(highlightedDescription),
                    { USE_PROFILES: { html: true } }
                ),
                __html: DOMPurify.sanitize(
                    formatHighlightStrings(highlightedDescription),
                    { USE_PROFILES: { html: true } }
                ),
import DOMPurify from 'dompurify';
import { bind } from 'lodash-decorators';
        div.innerHTML = DOMPurify.sanitize(
            `
        `,
            { USE_PROFILES: { html: true } }
        );