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