Original repo pull requests:
My forked repo: https://github.com/sloanlance/JSONView-for-Chrome
Original issues:
My issue: sloanlance/JSONView-for-Chrome#1
| JSONView-for-Chrome security vulnerability |
| diff --git a/WebContent/content.js b/WebContent/content.js | |
| index a6bb58c..e45278b 100644 | |
| --- a/WebContent/content.js | |
| +++ b/WebContent/content.js | |
| @@ -45,6 +45,9 @@ function displayUI(theme, html) { | |
| content += "<style>" + theme + "</style>"; | |
| content += html; | |
| document.body.innerHTML = content; | |
| + document.body.querySelectorAll("a").forEach(function(a) { | |
| + a.setAttribute('href', atob(a.getAttribute('href'))); | |
| + }); | |
| collapsers = document.querySelectorAll("#json .collapsible .collapsible"); | |
| statusElement = document.createElement("div"); | |
| statusElement.className = "status"; | |
| @@ -114,10 +117,10 @@ function extractData(rawText) { | |
| function processData(data) { | |
| var xhr, jsonText; | |
| - | |
| + | |
| function formatToHTML(fnName, offset) { | |
| if (!jsonText) | |
| - return; | |
| + return; | |
| port.postMessage({ | |
| jsonToHTML : true, | |
| json : jsonText, | |
| diff --git a/WebContent/workerFormatter.js b/WebContent/workerFormatter.js | |
| index 1850bdd..9559efb 100644 | |
| --- a/WebContent/workerFormatter.js | |
| +++ b/WebContent/workerFormatter.js | |
| @@ -1,6 +1,6 @@ | |
| /** | |
| - * Adapted the code in to order to run in a web worker. | |
| - * | |
| + * Adapted the code in to order to run in a web worker. | |
| + * | |
| * Original author: Benjamin Hollis | |
| */ | |
| @@ -24,7 +24,7 @@ function valueToHTML(value) { | |
| output += decorateWithSpan(value, "type-number"); | |
| else if (valueType == "string") | |
| if (/^(http|https):\/\/[^\s]+$/.test(value)) | |
| - output += decorateWithSpan('"', "type-string") + '<a href="' + value + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', "type-string"); | |
| + output += decorateWithSpan('"', "type-string") + '<a href="' + btoa(value) + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', "type-string"); | |
| else | |
| output += decorateWithSpan('"' + value + '"', "type-string"); | |
| else if (valueType == "boolean") |
| From 4b5db0439bd312d388f5ef14fc13f508132a291d Mon Sep 17 00:00:00 2001 | |
| From: joev <[email protected]> | |
| Date: Thu, 26 Feb 2015 10:57:43 -0600 | |
| Subject: [PATCH] Properly sanitize links to prevent HTML injection. | |
| --- | |
| WebContent/content.js | 7 +++++-- | |
| WebContent/workerFormatter.js | 6 +++--- | |
| 2 files changed, 8 insertions(+), 5 deletions(-) | |
| diff --git a/WebContent/content.js b/WebContent/content.js | |
| index a6bb58c..e45278b 100644 | |
| --- a/WebContent/content.js | |
| +++ b/WebContent/content.js | |
| @@ -45,6 +45,9 @@ function displayUI(theme, html) { | |
| content += "<style>" + theme + "</style>"; | |
| content += html; | |
| document.body.innerHTML = content; | |
| + document.body.querySelectorAll("a").forEach(function(a) { | |
| + a.setAttribute('href', atob(a.getAttribute('href'))); | |
| + }); | |
| collapsers = document.querySelectorAll("#json .collapsible .collapsible"); | |
| statusElement = document.createElement("div"); | |
| statusElement.className = "status"; | |
| @@ -114,10 +117,10 @@ function extractData(rawText) { | |
| function processData(data) { | |
| var xhr, jsonText; | |
| - | |
| + | |
| function formatToHTML(fnName, offset) { | |
| if (!jsonText) | |
| - return; | |
| + return; | |
| port.postMessage({ | |
| jsonToHTML : true, | |
| json : jsonText, | |
| diff --git a/WebContent/workerFormatter.js b/WebContent/workerFormatter.js | |
| index 1850bdd..9559efb 100644 | |
| --- a/WebContent/workerFormatter.js | |
| +++ b/WebContent/workerFormatter.js | |
| @@ -1,6 +1,6 @@ | |
| /** | |
| - * Adapted the code in to order to run in a web worker. | |
| - * | |
| + * Adapted the code in to order to run in a web worker. | |
| + * | |
| * Original author: Benjamin Hollis | |
| */ | |
| @@ -24,7 +24,7 @@ function valueToHTML(value) { | |
| output += decorateWithSpan(value, "type-number"); | |
| else if (valueType == "string") | |
| if (/^(http|https):\/\/[^\s]+$/.test(value)) | |
| - output += decorateWithSpan('"', "type-string") + '<a href="' + value + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', "type-string"); | |
| + output += decorateWithSpan('"', "type-string") + '<a href="' + btoa(value) + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', "type-string"); | |
| else | |
| output += decorateWithSpan('"' + value + '"', "type-string"); | |
| else if (valueType == "boolean") |
| diff --git a/WebContent/content.js b/WebContent/content.js | |
| index a6bb58c..c51fa62 100644 | |
| --- a/WebContent/content.js | |
| +++ b/WebContent/content.js | |
| @@ -1,4 +1,4 @@ | |
| -var port = chrome.runtime.connect(), collapsers, options, jsonObject; | |
| +var port = chrome.runtime.connect(), collapsers, options, jsonObject, jsonSelector; | |
| function displayError(error, loc, offset) { | |
| var link = document.createElement("link"), pre = document.body.firstChild.firstChild, text = pre.textContent.substring(offset), start = 0, ranges = [], idx = 0, end, range = document | |
| @@ -40,11 +40,17 @@ function displayError(error, loc, offset) { | |
| } | |
| function displayUI(theme, html) { | |
| - var statusElement, toolboxElement, expandElement, reduceElement, viewSourceElement, optionsElement, content = ""; | |
| - content += '<link rel="stylesheet" type="text/css" href="' + chrome.runtime.getURL("jsonview-core.css") + '">'; | |
| - content += "<style>" + theme + "</style>"; | |
| - content += html; | |
| - document.body.innerHTML = content; | |
| + var statusElement, toolboxElement, expandElement, reduceElement, viewSourceElement, optionsElement, userStyleElement, baseStyleElement; | |
| + baseStyleElement = document.createElement("link"); | |
| + baseStyleElement.rel = "stylesheet"; | |
| + baseStyleElement.type = "text/css"; | |
| + baseStyleElement.href = chrome.runtime.getURL("jsonview-core.css"); | |
| + document.head.appendChild(baseStyleElement); | |
| + userStyleElement = document.createElement("style"); | |
| + userStyleElement.appendChild(document.createTextNode(theme)); | |
| + document.head.appendChild(userStyleElement); | |
| + | |
| + document.body.innerHTML = html; | |
| collapsers = document.querySelectorAll("#json .collapsible .collapsible"); | |
| statusElement = document.createElement("div"); | |
| statusElement.className = "status"; | |
| @@ -78,10 +84,14 @@ function displayUI(theme, html) { | |
| document.body.addEventListener('contextmenu', onContextMenu, false); | |
| expandElement.addEventListener('click', onexpand, false); | |
| reduceElement.addEventListener('click', onreduce, false); | |
| - optionsElement.addEventListener("click", function() { | |
| + optionsElement.addEventListener("click", function(ev) { | |
| + if (ev.isTrusted === false) | |
| + return; | |
| window.open(chrome.runtime.getURL("options.html")); | |
| }, false); | |
| - copyPathElement.addEventListener("click", function() { | |
| + copyPathElement.addEventListener("click", function(ev) { | |
| + if (ev.isTrusted === false) | |
| + return; | |
| port.postMessage({ | |
| copyPropertyPath : true, | |
| path : statusElement.innerText | |
| @@ -192,13 +202,17 @@ var onmouseMove = (function() { | |
| hoveredLI.firstChild.classList.remove("hovered"); | |
| hoveredLI = null; | |
| statusElement.innerText = ""; | |
| + jsonSelector = []; | |
| } | |
| } | |
| return function(event) { | |
| + if (event.isTrusted === false) | |
| + return; | |
| var str = "", statusElement = document.querySelector(".status"); | |
| element = getParentLI(event.target); | |
| if (element) { | |
| + jsonSelector = []; | |
| if (hoveredLI) | |
| hoveredLI.firstChild.classList.remove("hovered"); | |
| hoveredLI = element; | |
| @@ -207,9 +221,12 @@ var onmouseMove = (function() { | |
| if (element.parentNode.classList.contains("array")) { | |
| var index = [].indexOf.call(element.parentNode.children, element); | |
| str = "[" + index + "]" + str; | |
| + jsonSelector.unshift(index); | |
| } | |
| if (element.parentNode.classList.contains("obj")) { | |
| - str = "." + element.firstChild.firstChild.innerText + str; | |
| + var key = element.firstChild.firstChild.innerText; | |
| + str = "." + key + str; | |
| + jsonSelector.unshift(key); | |
| } | |
| element = element.parentNode.parentNode.parentNode; | |
| } while (element.tagName == "LI"); | |
| @@ -233,15 +250,17 @@ function onmouseClick() { | |
| } | |
| } | |
| -function onContextMenu() { | |
| +function onContextMenu(ev) { | |
| + if (ev.isTrusted === false) | |
| + return; | |
| var currentLI, statusElement, selection = "", i, value; | |
| currentLI = getParentLI(event.target); | |
| statusElement = document.querySelector(".status"); | |
| if (currentLI) { | |
| - if (Array.isArray(jsonObject)) | |
| - value = eval("(jsonObject" + statusElement.innerText + ")"); | |
| - else | |
| - value = eval("(jsonObject." + statusElement.innerText + ")"); | |
| + var value = jsonObject; | |
| + jsonSelector.forEach(function(idx) { | |
| + value = value[idx]; | |
| + }); | |
| port.postMessage({ | |
| copyPropertyPath : true, | |
| path : statusElement.innerText, | |
| diff --git a/WebContent/workerFormatter.js b/WebContent/workerFormatter.js | |
| index 1850bdd..0d4c3bb 100644 | |
| --- a/WebContent/workerFormatter.js | |
| +++ b/WebContent/workerFormatter.js | |
| @@ -9,7 +9,7 @@ function htmlEncode(t) { | |
| } | |
| function decorateWithSpan(value, className) { | |
| - return '<span class="' + className + '">' + htmlEncode(value) + '</span>'; | |
| + return '<span class="' + htmlEncode(className) + '">' + htmlEncode(value) + '</span>'; | |
| } | |
| function valueToHTML(value) { | |
| @@ -23,8 +23,8 @@ function valueToHTML(value) { | |
| else if (valueType == "number") | |
| output += decorateWithSpan(value, "type-number"); | |
| else if (valueType == "string") | |
| - if (/^(http|https):\/\/[^\s]+$/.test(value)) | |
| - output += decorateWithSpan('"', "type-string") + '<a href="' + value + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', "type-string"); | |
| + if (/^https?:\/\/[^\s]+$/.test(value)) | |
| + output += decorateWithSpan('"', "type-string") + '<a href="' + htmlEncode(value) + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', "type-string"); | |
| else | |
| output += decorateWithSpan('"' + value + '"', "type-string"); | |
| else if (valueType == "boolean") | |
| @@ -70,7 +70,7 @@ function objectToHTML(json) { | |
| function jsonToHTML(json, fnName) { | |
| var output = ''; | |
| if (fnName) | |
| - output += '<div class="callback-function">' + fnName + '(</div>'; | |
| + output += '<div class="callback-function">' + htmlEncode(fnName) + '(</div>'; | |
| output += '<div id="json">'; | |
| output += valueToHTML(json); | |
| output += '</div>'; |
| From c0c9e1822eba0009fb0755e6a45fdac5a384dda5 Mon Sep 17 00:00:00 2001 | |
| From: Jordan Milne <[email protected]> | |
| Date: Thu, 20 Oct 2016 17:03:13 -0700 | |
| Subject: [PATCH 1/3] Ensure handled events were caused by user interaction | |
| --- | |
| WebContent/content.js | 14 +++++++++++--- | |
| 1 file changed, 11 insertions(+), 3 deletions(-) | |
| diff --git a/WebContent/content.js b/WebContent/content.js | |
| index a6bb58c..098c7a5 100644 | |
| --- a/WebContent/content.js | |
| +++ b/WebContent/content.js | |
| @@ -78,10 +78,14 @@ function displayUI(theme, html) { | |
| document.body.addEventListener('contextmenu', onContextMenu, false); | |
| expandElement.addEventListener('click', onexpand, false); | |
| reduceElement.addEventListener('click', onreduce, false); | |
| - optionsElement.addEventListener("click", function() { | |
| + optionsElement.addEventListener("click", function(ev) { | |
| + if (ev.isTrusted === false) | |
| + return; | |
| window.open(chrome.runtime.getURL("options.html")); | |
| }, false); | |
| - copyPathElement.addEventListener("click", function() { | |
| + copyPathElement.addEventListener("click", function(ev) { | |
| + if (ev.isTrusted === false) | |
| + return; | |
| port.postMessage({ | |
| copyPropertyPath : true, | |
| path : statusElement.innerText | |
| @@ -196,6 +200,8 @@ var onmouseMove = (function() { | |
| } | |
| return function(event) { | |
| + if (event.isTrusted === false) | |
| + return; | |
| var str = "", statusElement = document.querySelector(".status"); | |
| element = getParentLI(event.target); | |
| if (element) { | |
| @@ -233,7 +239,9 @@ function onmouseClick() { | |
| } | |
| } | |
| -function onContextMenu() { | |
| +function onContextMenu(ev) { | |
| + if (ev.isTrusted === false) | |
| + return; | |
| var currentLI, statusElement, selection = "", i, value; | |
| currentLI = getParentLI(event.target); | |
| statusElement = document.querySelector(".status"); | |
| From 9d296f0dd042d5291ac15bfb721f3db0b7be6f61 Mon Sep 17 00:00:00 2001 | |
| From: Jordan Milne <[email protected]> | |
| Date: Fri, 21 Oct 2016 20:02:56 -0700 | |
| Subject: [PATCH 2/3] Fix a few UXSS vulnerabilities | |
| --- | |
| WebContent/content.js | 19 ++++++++++++------- | |
| WebContent/workerFormatter.js | 8 ++++---- | |
| 2 files changed, 16 insertions(+), 11 deletions(-) | |
| diff --git a/WebContent/content.js b/WebContent/content.js | |
| index 098c7a5..597c214 100644 | |
| --- a/WebContent/content.js | |
| +++ b/WebContent/content.js | |
| @@ -1,4 +1,4 @@ | |
| -var port = chrome.runtime.connect(), collapsers, options, jsonObject; | |
| +var port = chrome.runtime.connect(), collapsers, options, jsonObject, jsonSelector; | |
| function displayError(error, loc, offset) { | |
| var link = document.createElement("link"), pre = document.body.firstChild.firstChild, text = pre.textContent.substring(offset), start = 0, ranges = [], idx = 0, end, range = document | |
| @@ -42,7 +42,7 @@ function displayError(error, loc, offset) { | |
| function displayUI(theme, html) { | |
| var statusElement, toolboxElement, expandElement, reduceElement, viewSourceElement, optionsElement, content = ""; | |
| content += '<link rel="stylesheet" type="text/css" href="' + chrome.runtime.getURL("jsonview-core.css") + '">'; | |
| - content += "<style>" + theme + "</style>"; | |
| + content += "<style>" + theme.replace(/<\/\s*style/g, '') + "</style>"; | |
| content += html; | |
| document.body.innerHTML = content; | |
| collapsers = document.querySelectorAll("#json .collapsible .collapsible"); | |
| @@ -196,6 +196,7 @@ var onmouseMove = (function() { | |
| hoveredLI.firstChild.classList.remove("hovered"); | |
| hoveredLI = null; | |
| statusElement.innerText = ""; | |
| + jsonSelector = []; | |
| } | |
| } | |
| @@ -205,6 +206,7 @@ var onmouseMove = (function() { | |
| var str = "", statusElement = document.querySelector(".status"); | |
| element = getParentLI(event.target); | |
| if (element) { | |
| + jsonSelector = []; | |
| if (hoveredLI) | |
| hoveredLI.firstChild.classList.remove("hovered"); | |
| hoveredLI = element; | |
| @@ -213,9 +215,12 @@ var onmouseMove = (function() { | |
| if (element.parentNode.classList.contains("array")) { | |
| var index = [].indexOf.call(element.parentNode.children, element); | |
| str = "[" + index + "]" + str; | |
| + jsonSelector.unshift(index); | |
| } | |
| if (element.parentNode.classList.contains("obj")) { | |
| - str = "." + element.firstChild.firstChild.innerText + str; | |
| + var key = element.firstChild.firstChild.innerText; | |
| + str = "." + key + str; | |
| + jsonSelector.unshift(key); | |
| } | |
| element = element.parentNode.parentNode.parentNode; | |
| } while (element.tagName == "LI"); | |
| @@ -246,10 +251,10 @@ function onContextMenu(ev) { | |
| currentLI = getParentLI(event.target); | |
| statusElement = document.querySelector(".status"); | |
| if (currentLI) { | |
| - if (Array.isArray(jsonObject)) | |
| - value = eval("(jsonObject" + statusElement.innerText + ")"); | |
| - else | |
| - value = eval("(jsonObject." + statusElement.innerText + ")"); | |
| + var value = jsonObject; | |
| + jsonSelector.forEach(function(idx) { | |
| + value = value[idx]; | |
| + }); | |
| port.postMessage({ | |
| copyPropertyPath : true, | |
| path : statusElement.innerText, | |
| diff --git a/WebContent/workerFormatter.js b/WebContent/workerFormatter.js | |
| index 1850bdd..0d4c3bb 100644 | |
| --- a/WebContent/workerFormatter.js | |
| +++ b/WebContent/workerFormatter.js | |
| @@ -9,7 +9,7 @@ function htmlEncode(t) { | |
| } | |
| function decorateWithSpan(value, className) { | |
| - return '<span class="' + className + '">' + htmlEncode(value) + '</span>'; | |
| + return '<span class="' + htmlEncode(className) + '">' + htmlEncode(value) + '</span>'; | |
| } | |
| function valueToHTML(value) { | |
| @@ -23,8 +23,8 @@ function valueToHTML(value) { | |
| else if (valueType == "number") | |
| output += decorateWithSpan(value, "type-number"); | |
| else if (valueType == "string") | |
| - if (/^(http|https):\/\/[^\s]+$/.test(value)) | |
| - output += decorateWithSpan('"', "type-string") + '<a href="' + value + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', "type-string"); | |
| + if (/^https?:\/\/[^\s]+$/.test(value)) | |
| + output += decorateWithSpan('"', "type-string") + '<a href="' + htmlEncode(value) + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', "type-string"); | |
| else | |
| output += decorateWithSpan('"' + value + '"', "type-string"); | |
| else if (valueType == "boolean") | |
| @@ -70,7 +70,7 @@ function objectToHTML(json) { | |
| function jsonToHTML(json, fnName) { | |
| var output = ''; | |
| if (fnName) | |
| - output += '<div class="callback-function">' + fnName + '(</div>'; | |
| + output += '<div class="callback-function">' + htmlEncode(fnName) + '(</div>'; | |
| output += '<div id="json">'; | |
| output += valueToHTML(json); | |
| output += '</div>'; | |
| From 9725589004f3e1c3a20c626c16c36baf35f92ac4 Mon Sep 17 00:00:00 2001 | |
| From: Jordan Milne <[email protected]> | |
| Date: Thu, 24 Nov 2016 16:21:10 -0800 | |
| Subject: [PATCH 3/3] Replace more HTML string templating with DOM operations | |
| --- | |
| WebContent/content.js | 16 +++++++++++----- | |
| 1 file changed, 11 insertions(+), 5 deletions(-) | |
| diff --git a/WebContent/content.js b/WebContent/content.js | |
| index 597c214..c51fa62 100644 | |
| --- a/WebContent/content.js | |
| +++ b/WebContent/content.js | |
| @@ -40,11 +40,17 @@ function displayError(error, loc, offset) { | |
| } | |
| function displayUI(theme, html) { | |
| - var statusElement, toolboxElement, expandElement, reduceElement, viewSourceElement, optionsElement, content = ""; | |
| - content += '<link rel="stylesheet" type="text/css" href="' + chrome.runtime.getURL("jsonview-core.css") + '">'; | |
| - content += "<style>" + theme.replace(/<\/\s*style/g, '') + "</style>"; | |
| - content += html; | |
| - document.body.innerHTML = content; | |
| + var statusElement, toolboxElement, expandElement, reduceElement, viewSourceElement, optionsElement, userStyleElement, baseStyleElement; | |
| + baseStyleElement = document.createElement("link"); | |
| + baseStyleElement.rel = "stylesheet"; | |
| + baseStyleElement.type = "text/css"; | |
| + baseStyleElement.href = chrome.runtime.getURL("jsonview-core.css"); | |
| + document.head.appendChild(baseStyleElement); | |
| + userStyleElement = document.createElement("style"); | |
| + userStyleElement.appendChild(document.createTextNode(theme)); | |
| + document.head.appendChild(userStyleElement); | |
| + | |
| + document.body.innerHTML = html; | |
| collapsers = document.querySelectorAll("#json .collapsible .collapsible"); | |
| statusElement = document.createElement("div"); | |
| statusElement.className = "status"; |
Original repo pull requests:
My forked repo: https://github.com/sloanlance/JSONView-for-Chrome
Original issues:
My issue: sloanlance/JSONView-for-Chrome#1