Skip to content

Instantly share code, notes, and snippets.

@sloanlance
Last active December 5, 2016 23:12
Show Gist options
  • Save sloanlance/95cd68f57fdacc8f2dd159a72b259086 to your computer and use it in GitHub Desktop.
Save sloanlance/95cd68f57fdacc8f2dd159a72b259086 to your computer and use it in GitHub Desktop.
JSONView-for-Chrome/pull/76
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";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment