CVE-2024-27103 (published 2024-02-28T18:15Z)

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.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/pinterest/querybook
commit_details: repo_url: https://github.com/pinterest/querybook
author: J.C. Zhong
author_date: 2024-02-27 20:05:37-08:00
msg: Merge pull request from GHSA-3hjm-9277-5c88

* Fix XSS vulnerabilities

* remove console.log
repository: repo_url: https://github.com/pinterest/querybook
repo_name: pinterest/querybook
description: Querybook is a Big Data Querying UI, combining collocated table metadata and a simple notebook interface.
repo_language: TypeScript
owner: pinterest
file_changes: filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,6 @@
 {
     "name": "querybook",
-    "version": "3.31.1",
+    "version": "3.31.2",
     "description": "A Big Data Webapp",
     "private": true,
     "scripts": {
@@ -50,6 +50,7 @@
         "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",

programming_language: JavaScript
filename: StatementLog.tsx
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,5 @@
 import clsx from 'clsx';
+import DOMPurify from 'dompurify';
 import { debounce } from 'lodash';
 import React from 'react';
 import { useDispatch, useSelector } from 'react-redux';
@@ -103,7 +104,9 @@ export const StatementLog: React.FunctionComponent<IStatementLogProps> = ({
             }}
             className="statement-execution-log-container"
             dangerouslySetInnerHTML={{
-                __html: logText,
+                __html: DOMPurify.sanitize(logText, {
+                    USE_PROFILES: { html: true },
+                }),
             }}
         />
     );

programming_language: TypeScript
filename: SearchResultItem.tsx
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,4 @@
+import DOMPurify from 'dompurify';
 import { escape, escapeRegExp } from 'lodash';
 import React, { useMemo, useRef, useState } from 'react';
 import { useSelector } from 'react-redux';
@@ -55,7 +56,9 @@ const HighlightTitle: React.FunctionComponent<{
             <div
                 className="result-item-title"
                 dangerouslySetInnerHTML={{
-                    __html: highlightedTitle,
+                    __html: DOMPurify.sanitize(highlightedTitle, {
+                        USE_PROFILES: { html: true },
+                    }),
                 }}
             />
         </AccentText>
@@ -166,8 +169,9 @@ export const QueryItem: React.FunctionComponent<IQueryItemProps> = ({
             {!isQueryTextExpanded ? (
                 <span
                     dangerouslySetInnerHTML={{
-                        __html: formatHighlightStrings(
-                            queryTextHighlightedContent
+                        __html: DOMPurify.sanitize(
+                            formatHighlightStrings(queryTextHighlightedContent),
+                            { USE_PROFILES: { html: true } }
                         ),
                     }}
                 />
@@ -281,7 +285,10 @@ export const DataDocItem: React.FunctionComponent<IDataDocItemProps> = ({
         <span
             className="result-item-description"
             dangerouslySetInnerHTML={{
-                __html: formatHighlightStrings(dataDocContent),
+                __html: DOMPurify.sanitize(
+                    formatHighlightStrings(dataDocContent),
+                    { USE_PROFILES: { html: true } }
+                ),
             }}
         />
     );
@@ -381,7 +388,10 @@ export const DataTableItem: React.FunctionComponent<IDataTableItemProps> = ({
     const descriptionDOM = highlightedDescription ? (
         <span
             dangerouslySetInnerHTML={{
-                __html: formatHighlightStrings(highlightedDescription),
+                __html: DOMPurify.sanitize(
+                    formatHighlightStrings(highlightedDescription),
+                    { USE_PROFILES: { html: true } }
+                ),
             }}
         />
     ) : (
@@ -484,7 +494,10 @@ export const BoardItem: React.FunctionComponent<{
     const descriptionDOM = highlightedDescription ? (
         <span
             dangerouslySetInnerHTML={{
-                __html: formatHighlightStrings(highlightedDescription),
+                __html: DOMPurify.sanitize(
+                    formatHighlightStrings(highlightedDescription),
+                    { USE_PROFILES: { html: true } }
+                ),
             }}
         />
     ) : (

programming_language: TypeScript
filename: sql-autocompleter.ts
change_type: ModificationType.MODIFY
diff: @@ -1,10 +1,11 @@
 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;
@@ -408,14 +409,17 @@ export class SqlAutoCompleter {
 
         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);
     }

programming_language: TypeScript