From 54eb534bf480fb526d05cc74d2f5f865e93cfd1c Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Sat, 19 Dec 2020 00:22:48 +0100 Subject: [PATCH] fix: xss security --- lib/shared/Configurator.js | 8 ++++---- lib/shared/Popup.js | 3 ++- lib/timeline/Timeline.js | 2 +- lib/timeline/component/CustomTime.js | 2 +- lib/timeline/component/DataAxis.js | 7 ++++--- lib/timeline/component/Group.js | 4 ++-- lib/timeline/component/Legend.js | 2 +- lib/timeline/component/TimeAxis.js | 4 ++-- lib/timeline/component/item/Item.js | 8 ++++---- lib/util.js | 4 +++- package-lock.json | 16 ++++++++++++++++ package.json | 6 ++++-- 12 files changed, 44 insertions(+), 22 deletions(-) diff --git a/lib/shared/Configurator.js b/lib/shared/Configurator.js index d72f94d503..3a8cac7149 100644 --- a/lib/shared/Configurator.js +++ b/lib/shared/Configurator.js @@ -245,7 +245,7 @@ class Configurator { _makeHeader(name) { let div = document.createElement('div'); div.className = 'vis-configuration vis-config-header'; - div.innerHTML = name; + div.innerHTML = util.xss(name); this._makeItem([],div); } @@ -262,10 +262,10 @@ class Configurator { let div = document.createElement('div'); div.className = 'vis-configuration vis-config-label vis-config-s' + path.length; if (objectLabel === true) { - div.innerHTML = '' + name + ':'; + div.innerHTML = util.xss('' + name + ':'); } else { - div.innerHTML = name + ':'; + div.innerHTML = util.xss(name + ':'); } return div; } @@ -407,7 +407,7 @@ class Configurator { let div = document.createElement("div"); div.id = "vis-configuration-popup"; div.className = "vis-configuration-popup"; - div.innerHTML = string; + div.innerHTML = util.xss(string); div.onclick = () => {this._removePopup()}; this.popupCounter += 1; this.popupDiv = {html:div, index:index}; diff --git a/lib/shared/Popup.js b/lib/shared/Popup.js index 8d269abfb6..8091a0e8b4 100644 --- a/lib/shared/Popup.js +++ b/lib/shared/Popup.js @@ -1,3 +1,4 @@ +import util from '../util'; import './tooltip.css'; /** @@ -42,7 +43,7 @@ class Popup { this.frame.appendChild(content); } else { - this.frame.innerHTML = content; // string containing text or HTML + this.frame.innerHTML = util.xss(content); // string containing text or HTML } } diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index e2212ee61a..2d9bbef607 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -96,7 +96,7 @@ export default class Timeline extends Core { loadingScreenFragment.appendChild(loadingScreen); } else if (loadingScreen != undefined) { - loadingScreenFragment.innerHTML = loadingScreen; + loadingScreenFragment.innerHTML = util.xss(loadingScreen); } } } diff --git a/lib/timeline/component/CustomTime.js b/lib/timeline/component/CustomTime.js index b896621cb4..82d74d0014 100644 --- a/lib/timeline/component/CustomTime.js +++ b/lib/timeline/component/CustomTime.js @@ -205,7 +205,7 @@ class CustomTime extends Component { setCustomMarker(title, editable) { const marker = document.createElement('div'); marker.className = `vis-custom-time-marker`; - marker.innerHTML = title; + marker.innerHTML = util.xss(title); marker.style.position = 'absolute'; if (editable) { diff --git a/lib/timeline/component/DataAxis.js b/lib/timeline/component/DataAxis.js index 03f30cd1db..0555d020e2 100644 --- a/lib/timeline/component/DataAxis.js +++ b/lib/timeline/component/DataAxis.js @@ -1,4 +1,5 @@ -import { v4 as randomUUID } from "uuid";import util from '../../util'; +import { v4 as randomUUID } from "uuid"; +import util from '../../util'; import * as DOMutil from '../../DOMutil'; import Component from './Component'; import DataScale from './DataScale'; @@ -504,7 +505,7 @@ class DataAxis extends Component { // reuse redundant label const label = DOMutil.getDOMElement('div', this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift(); label.className = className; - label.innerHTML = text; + label.innerHTML = util.xss(text); if (orientation === 'left') { label.style.left = `-${this.options.labelOffsetX}px`; label.style.textAlign = "right"; @@ -562,7 +563,7 @@ class DataAxis extends Component { if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) { const title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame); title.className = `vis-y-axis vis-title vis-${orientation}`; - title.innerHTML = this.options[orientation].title.text; + title.innerHTML = util.xss(this.options[orientation].title.text); // Add style - if provided if (this.options[orientation].title.style !== undefined) { diff --git a/lib/timeline/component/Group.js b/lib/timeline/component/Group.js index d2da5a4c37..d56f3cf7e9 100644 --- a/lib/timeline/component/Group.js +++ b/lib/timeline/component/Group.js @@ -178,9 +178,9 @@ class Group { } else if (content instanceof Object) { templateFunction(data, this.dom.inner); } else if (content !== undefined && content !== null) { - this.dom.inner.innerHTML = content; + this.dom.inner.innerHTML = util.xss(content); } else { - this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null + this.dom.inner.innerHTML = util.xss(this.groupId || ''); // groupId can be null } // update title diff --git a/lib/timeline/component/Legend.js b/lib/timeline/component/Legend.js index 3dae09bfe7..1734b9f2e5 100644 --- a/lib/timeline/component/Legend.js +++ b/lib/timeline/component/Legend.js @@ -185,7 +185,7 @@ Legend.prototype.redraw = function() { content += this.groups[groupId].content + '
'; } } - this.dom.textArea.innerHTML = content; + this.dom.textArea.innerHTML = util.xss(content); this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px'; } }; diff --git a/lib/timeline/component/TimeAxis.js b/lib/timeline/component/TimeAxis.js index de47fee353..4c41c92e11 100644 --- a/lib/timeline/component/TimeAxis.js +++ b/lib/timeline/component/TimeAxis.js @@ -339,7 +339,7 @@ class TimeAxis extends Component { this.dom.foreground.appendChild(label); } this.dom.minorTexts.push(label); - label.innerHTML = text; + label.innerHTML = util.xss(text); let y = (orientation == 'top') ? this.props.majorLabelHeight : 0; @@ -372,7 +372,7 @@ class TimeAxis extends Component { this.dom.foreground.appendChild(label); } - label.childNodes[0].innerHTML = text; + label.childNodes[0].innerHTML = util.xss(text); label.className = `vis-text vis-major ${className}`; //label.title = title; // TODO: this is a heavy operation diff --git a/lib/timeline/component/item/Item.js b/lib/timeline/component/item/Item.js index 5bf5ede0a4..0a24c2b141 100644 --- a/lib/timeline/component/item/Item.js +++ b/lib/timeline/component/item/Item.js @@ -366,7 +366,7 @@ class Item { content += `
end: ${moment(this.data.end).format('MM/DD/YYYY hh:mm')}`; } } - this.dom.onItemUpdateTimeTooltip.innerHTML = content; + this.dom.onItemUpdateTimeTooltip.innerHTML = util.xss(content); } } @@ -397,7 +397,7 @@ class Item { if (this.options.visibleFrameTemplate) { visibleFrameTemplateFunction = this.options.visibleFrameTemplate.bind(this); - itemVisibleFrameContent = visibleFrameTemplateFunction(itemData, itemVisibleFrameContentElement); + itemVisibleFrameContent = util.xss(visibleFrameTemplateFunction(itemData, itemVisibleFrameContentElement)); } else { itemVisibleFrameContent = ''; } @@ -414,7 +414,7 @@ class Item { itemVisibleFrameContentElement.appendChild(itemVisibleFrameContent); } else if (itemVisibleFrameContent != undefined) { - itemVisibleFrameContentElement.innerHTML = itemVisibleFrameContent; + itemVisibleFrameContentElement.innerHTML = util.xss(itemVisibleFrameContent); } else { if (!(this.data.type == 'background' && this.data.content === undefined)) { @@ -445,7 +445,7 @@ class Item { element.appendChild(content); } else if (content != undefined) { - element.innerHTML = content; + element.innerHTML = util.xss(content); } else { if (!(this.data.type == 'background' && this.data.content === undefined)) { diff --git a/lib/util.js b/lib/util.js index fc6f0d41e5..8c82352211 100644 --- a/lib/util.js +++ b/lib/util.js @@ -6,6 +6,7 @@ import { getType, isNumber, isString } from "vis-util/esnext"; import { DataSet, createNewDataPipeFrom } from "vis-data/esnext"; import moment from "moment"; +import xss from 'xss'; // parse ASP.Net Date pattern, // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/' @@ -226,5 +227,6 @@ export function typeCoerceDataSet( export default { ...util, - convert + convert, + xss }; diff --git a/package-lock.json b/package-lock.json index f96e3c2496..72364160c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7099,6 +7099,12 @@ "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", "dev": true }, + "cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=", + "dev": true + }, "cssnano": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", @@ -23171,6 +23177,16 @@ "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", "dev": true }, + "xss": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.8.tgz", + "integrity": "sha512-3MgPdaXV8rfQ/pNn16Eio6VXYPTkqwa0vc7GkiymmY/DqR1SE/7VPAAVZz1GJsJFrllMYO3RHfEaiUGjab6TNw==", + "dev": true, + "requires": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + } + }, "xtend": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", diff --git a/package.json b/package.json index 52abc72c81..0dbce261a8 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,8 @@ "uuid": "7.0.3", "vis-data": "7.1.1", "vis-dev-utils": "2.8.35", - "vis-util": "4.3.4" + "vis-util": "4.3.4", + "xss": "^1.0.8" }, "collective": { "type": "opencollective", @@ -259,5 +260,6 @@ "synthomat ", "thomasbarone ", "unknown " - ] + ], + "dependencies": {} }