Fix pretty recipe parser ReDoS (#2687)
Co-authored-by: zainnadeem(RedOpsCell) <zainnadeemzainnadeem80@gmail.com> (main author)
This commit is contained in:
parent
1343a107cb
commit
f77ddf4890
@ -1015,6 +1015,7 @@ class Utils {
|
|||||||
|
|
||||||
// Parse bespoke recipe format
|
// Parse bespoke recipe format
|
||||||
recipe = recipe.replace(/\n/g, "");
|
recipe = recipe.replace(/\n/g, "");
|
||||||
|
Utils._validatePrettyRecipe(recipe);
|
||||||
let m, args;
|
let m, args;
|
||||||
const recipeRegex = /([^(]+)\(((?:'[^'\\]*(?:\\.[^'\\]*)*'|[^)/'])*)(\/[^)]+)?\)/g,
|
const recipeRegex = /([^(]+)\(((?:'[^'\\]*(?:\\.[^'\\]*)*'|[^)/'])*)(\/[^)]+)?\)/g,
|
||||||
recipeConfig = [];
|
recipeConfig = [];
|
||||||
@ -1040,6 +1041,53 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a linear structural validation pass over pretty recipe syntax.
|
||||||
|
*
|
||||||
|
* @param {string} recipe
|
||||||
|
* @throws {Error} if the recipe is structurally invalid
|
||||||
|
*/
|
||||||
|
static _validatePrettyRecipe(recipe) {
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < recipe.length) {
|
||||||
|
const openParen = recipe.indexOf("(", i);
|
||||||
|
if (openParen === -1 || openParen === i) {
|
||||||
|
throw new Error("Invalid recipe");
|
||||||
|
}
|
||||||
|
|
||||||
|
i = openParen + 1;
|
||||||
|
let inString = false,
|
||||||
|
escaped = false,
|
||||||
|
foundCloseParen = false;
|
||||||
|
|
||||||
|
for (; i < recipe.length; i++) {
|
||||||
|
const c = recipe[i];
|
||||||
|
|
||||||
|
if (inString) {
|
||||||
|
if (escaped) {
|
||||||
|
escaped = false;
|
||||||
|
} else if (c === "\\") {
|
||||||
|
escaped = true;
|
||||||
|
} else if (c === "'") {
|
||||||
|
inString = false;
|
||||||
|
}
|
||||||
|
} else if (c === "'") {
|
||||||
|
inString = true;
|
||||||
|
} else if (c === ")") {
|
||||||
|
foundCloseParen = true;
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundCloseParen || inString || escaped) {
|
||||||
|
throw new Error("Invalid recipe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a list of files or directories.
|
* Formats a list of files or directories.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -26,4 +26,81 @@ TestRegister.addApiTests([
|
|||||||
"\x7e...",
|
"\x7e...",
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
it("Utils: should parse normal pretty recipes", () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
Utils.parseRecipeConfig("From_Base64('A-Za-z0-9+/=',true)To_Hex('Space')"),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
op: "From Base64",
|
||||||
|
args: ["A-Za-z0-9+/=", true],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "To Hex",
|
||||||
|
args: ["Space"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Utils: should parse pretty recipe options", () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
Utils.parseRecipeConfig("A(/disabled/breakpoint)"),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
op: "A",
|
||||||
|
args: [],
|
||||||
|
disabled: true,
|
||||||
|
breakpoint: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Utils: should parse escaped quotes and backslashes in pretty recipes", () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
Utils.parseRecipeConfig("A('\\'\\\\')"),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
op: "A",
|
||||||
|
args: ["'\\"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Utils: should parse large valid quoted pretty recipe arguments", () => {
|
||||||
|
const value = "x".repeat(10000);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
Utils.parseRecipeConfig(`A('${value}')`),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
op: "A",
|
||||||
|
args: [value],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Utils: should reject malformed pretty recipes with unmatched quotes", () => {
|
||||||
|
assert.throws(
|
||||||
|
() => Utils.parseRecipeConfig("A(" + "'".repeat(10000)),
|
||||||
|
/Invalid recipe/,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Utils: should reject malformed pretty recipes with malformed parentheses", () => {
|
||||||
|
assert.throws(
|
||||||
|
() => Utils.parseRecipeConfig("A("),
|
||||||
|
/Invalid recipe/,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Utils: should reject malformed pretty recipes with malformed escapes", () => {
|
||||||
|
assert.throws(
|
||||||
|
() => Utils.parseRecipeConfig("A('" + "\\".repeat(10000)),
|
||||||
|
/Invalid recipe/,
|
||||||
|
);
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user