Chart operation prototype protection (#2569)
This commit is contained in:
parent
50a7319b69
commit
85db3be5d0
@ -153,7 +153,7 @@ export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHe
|
|||||||
);
|
);
|
||||||
|
|
||||||
let xValues = new Set();
|
let xValues = new Set();
|
||||||
const series = {};
|
const series = Object.create(null);
|
||||||
|
|
||||||
values.forEach(row => {
|
values.forEach(row => {
|
||||||
const serie = row[0],
|
const serie = row[0],
|
||||||
@ -163,14 +163,14 @@ export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHe
|
|||||||
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
||||||
|
|
||||||
xValues.add(xVal);
|
xValues.add(xVal);
|
||||||
if (typeof series[serie] === "undefined") series[serie] = {};
|
if (typeof series[serie] === "undefined") series[serie] = Object.create(null);
|
||||||
series[serie][xVal] = val;
|
series[serie][xVal] = val;
|
||||||
});
|
});
|
||||||
|
|
||||||
xValues = new Array(...xValues);
|
xValues = new Array(...xValues);
|
||||||
|
|
||||||
const seriesList = [];
|
const seriesList = [];
|
||||||
for (const seriesName in series) {
|
for (const seriesName of Object.keys(series)) {
|
||||||
const serie = series[seriesName];
|
const serie = series[seriesName];
|
||||||
seriesList.push({name: seriesName, data: serie});
|
seriesList.push({name: seriesName, data: serie});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import BigNumber from "bignumber.js";
|
import BigNumber from "bignumber.js";
|
||||||
import {toHexFast} from "../lib/Hex.mjs";
|
import {toHexFast} from "../lib/Hex.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively displays a JSON object as an HTML table
|
* Recursively displays a JSON object as an HTML table
|
||||||
@ -25,15 +26,16 @@ export function objToTable(obj, nested=false) {
|
|||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|
||||||
for (const key in obj) {
|
for (const key of Object.keys(obj)) {
|
||||||
if (typeof obj[key] === "function")
|
const value = obj[key];
|
||||||
|
if (typeof value === "function")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
html += `<tr><td style='word-wrap: break-word'>${Utils.escapeHtml(String(key))}</td>`;
|
||||||
if (typeof obj[key] === "object")
|
if (value !== null && typeof value === "object")
|
||||||
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
html += `<td style='padding: 0'>${objToTable(value, true)}</td>`;
|
||||||
else
|
else
|
||||||
html += `<td>${obj[key]}</td>`;
|
html += `<td>${Utils.escapeHtml(String(value))}</td>`;
|
||||||
html += "</tr>";
|
html += "</tr>";
|
||||||
}
|
}
|
||||||
html += "</table>";
|
html += "</table>";
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import "./tests/NodeDish.mjs";
|
|||||||
import "./tests/Utils.mjs";
|
import "./tests/Utils.mjs";
|
||||||
import "./tests/Categories.mjs";
|
import "./tests/Categories.mjs";
|
||||||
import "./tests/lib/BigIntUtils.mjs";
|
import "./tests/lib/BigIntUtils.mjs";
|
||||||
|
import "./tests/lib/ChartsProtocolPrototypePollution.mjs";
|
||||||
|
|
||||||
const testStatus = {
|
const testStatus = {
|
||||||
allTestsPassing: true,
|
allTestsPassing: true,
|
||||||
|
|||||||
90
tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs
Normal file
90
tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import TestRegister from "../../../lib/TestRegister.mjs";
|
||||||
|
import {getSeriesValues} from "../../../../src/core/lib/Charts.mjs";
|
||||||
|
import {objToTable} from "../../../../src/core/lib/Protocol.mjs";
|
||||||
|
import SeriesChart from "../../../../src/core/operations/SeriesChart.mjs";
|
||||||
|
import ParseUDP from "../../../../src/core/operations/ParseUDP.mjs";
|
||||||
|
import it from "../../assertionHandler.mjs";
|
||||||
|
import assert from "assert";
|
||||||
|
|
||||||
|
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
||||||
|
|
||||||
|
TestRegister.addApiTests([
|
||||||
|
it("Charts: should not pollute Object.prototype from a __proto__ series name", () => {
|
||||||
|
const xVal = "<img src=x onerror=alert(1)>";
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = getSeriesValues(`__proto__,${xVal},1`, "\n", ",", false);
|
||||||
|
|
||||||
|
assert.equal(Object.prototype[xVal], undefined);
|
||||||
|
assert.deepEqual(result.xValues, [xVal]);
|
||||||
|
assert.equal(result.series.length, 1);
|
||||||
|
assert.equal(result.series[0].name, "__proto__");
|
||||||
|
assert.equal(Object.getPrototypeOf(result.series[0].data), null);
|
||||||
|
assert(hasOwn(result.series[0].data, xVal));
|
||||||
|
assert.equal(result.series[0].data[xVal], 1);
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Charts: should keep __proto__ x-axis names as own data keys", () => {
|
||||||
|
const result = getSeriesValues("safe,__proto__,1", "\n", ",", false);
|
||||||
|
|
||||||
|
assert.equal(result.series.length, 1);
|
||||||
|
assert.equal(Object.getPrototypeOf(result.series[0].data), null);
|
||||||
|
assert(hasOwn(result.series[0].data, "__proto__"));
|
||||||
|
assert.equal(result.series[0].data.__proto__, 1);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Protocol: should ignore inherited properties when rendering tables", () => {
|
||||||
|
const inheritedKey = "<img src=x onerror=alert(1)>";
|
||||||
|
delete Object.prototype[inheritedKey];
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.prototype[inheritedKey] = "polluted";
|
||||||
|
|
||||||
|
const html = objToTable({safe: "value"});
|
||||||
|
|
||||||
|
assert(!html.includes(inheritedKey));
|
||||||
|
assert(!html.includes("polluted"));
|
||||||
|
assert(html.includes("safe"));
|
||||||
|
assert(html.includes("value"));
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[inheritedKey];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Protocol: should escape table keys and scalar values", () => {
|
||||||
|
const obj = {
|
||||||
|
"<b>field</b>": "<img src=x onerror=alert(1)>",
|
||||||
|
};
|
||||||
|
|
||||||
|
const html = objToTable(obj);
|
||||||
|
|
||||||
|
assert(!html.includes("<b>field</b>"));
|
||||||
|
assert(!html.includes("<img src=x onerror=alert(1)>"));
|
||||||
|
assert(html.includes("<b>field</b>"));
|
||||||
|
assert(html.includes("<img src=x onerror=alert(1)>"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Series chart and Parse UDP: should not expose polluted prototype data as HTML", () => {
|
||||||
|
const xVal = "<img src=x onerror=alert(document.domain)>";
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const chartHtml = new SeriesChart().run(
|
||||||
|
`__proto__,${xVal},1`,
|
||||||
|
["Line feed", "Comma", "", 1, "red"]
|
||||||
|
);
|
||||||
|
assert.equal(Object.prototype[xVal], undefined);
|
||||||
|
|
||||||
|
const parseUDP = new ParseUDP();
|
||||||
|
const tableHtml = parseUDP.present(parseUDP.run(chartHtml, ["Raw"]));
|
||||||
|
|
||||||
|
assert(!/<img|onerror|alert\(/.test(tableHtml));
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]);
|
||||||
Loading…
x
Reference in New Issue
Block a user