-
-
Save danneu/425f41997d871d4f3a31596393a14656 to your computer and use it in GitHub Desktop.
Breaking CloudFlare's "I'm Under Attack" challenge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const parseExpression = require("./parse-expression"); | |
function findAll(regex, target) { | |
let results = [], match; | |
while (match = regex.exec(target)) { | |
results.push(match); | |
} | |
return results; | |
} | |
module.exports = function(testcase) { | |
let result; | |
let [_, parent, child, initialExpression] = /var s,t,o,p,b,r,e,a,k,i,n,g,f,\s*([a-zA-Z]+)={"([a-zA-Z]+)":([^}]+)};/.exec(testcase); | |
let modifyingExpressions = findAll(new RegExp(`${parent}\.${child}\s*([*+-])=\s*([^;]+)`, "g"), testcase).map((match) => { | |
return { | |
operation: match[1], | |
expression: match[2] | |
} | |
}).map(({operation, expression}) => { | |
return { | |
operation, | |
expression: parseExpression(expression) | |
} | |
}); | |
initialExpression = parseExpression(initialExpression); | |
return {parent, child, initialExpression, modifyingExpressions}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const arrayEqual = require("array-equal"); | |
module.exports = function(string) { | |
return evaluate(parse(string)); | |
} | |
function evaluateBranch(tree, modifiers) { | |
let result = tree.map((expression) => { | |
if (expression.type === "group") { | |
return evaluateBranch(expression.values, expression.modifiers); | |
} else if (expression.modifiers.length === 0) { | |
return ""; // This is a trigger to stringify the previous values | |
} else if (arrayEqual(["plus"], expression.modifiers)) { | |
return 0; | |
} else if (arrayEqual(["negate", "negate"], expression.modifiers)) { | |
return true; | |
} else if (arrayEqual(["negate", "plus"], expression.modifiers)) { | |
return true; | |
} else if (arrayEqual(["plus", "plus", "negate"], expression.modifiers)) { | |
return true; | |
} else if (arrayEqual(["plus", "negate", "negate"], expression.modifiers)) { | |
return 1; | |
} else { | |
throw new Error(`Found unrecognized modifier pattern: ${expression.modifiers}`) | |
} | |
}).reduce((combined, value) => { | |
if (value === "") { | |
return combined.toString(); | |
} else { | |
if (value === true) { | |
value = 1; | |
} | |
if (typeof combined === "string") { | |
return combined + value.toString(); | |
} else { | |
return combined + value; | |
} | |
} | |
}, 0); | |
if (modifiers == null) { | |
return result; | |
} else { | |
if (arrayEqual(["plus"], modifiers)) { | |
return parseInt(result); | |
} else { | |
return result; | |
} | |
} | |
} | |
function evaluate(tree) { | |
return evaluateBranch(tree); | |
} | |
function parse(string) { | |
let length = string.length; | |
let byte; | |
let stateFinishedItem = false; | |
let modifierStack = [[]]; | |
let itemStack = [[]]; | |
let currentStack = itemStack[0]; | |
let currentModifiers = modifierStack[0]; | |
let stackLevel = 0; | |
for (let pos = 0; pos < length; pos++) { | |
byte = string[pos]; | |
switch (byte) { | |
case "+": | |
if (pos === 0 || stateFinishedItem === false) { | |
// Modifier / number-cast | |
currentModifiers.push("plus"); | |
stateFinishedItem = false; | |
} else { | |
// Addition, we don't need to do anything here | |
} | |
break; | |
case "!": | |
stateFinishedItem = false; | |
currentModifiers.push("negate"); | |
break; | |
case "(": | |
stateFinishedItem = false; | |
stackLevel++; | |
itemStack[stackLevel] = currentStack = []; | |
modifierStack[stackLevel] = currentModifiers = []; | |
break; | |
case ")": | |
if (stackLevel === 0) { | |
throw new Error(`Encountered ) without matching (`); | |
} | |
stackLevel--; | |
stateFinishedItem = true; | |
currentStack = itemStack[stackLevel]; | |
currentStack.push({ | |
type: "group", | |
values: itemStack[stackLevel + 1], | |
modifiers: modifierStack[stackLevel] | |
}); | |
currentModifiers = modifierStack[stackLevel] = []; | |
break; | |
case "[": | |
if (string[pos + 1] === "]") { | |
// Reached the brackets; end of the modifier sequence | |
currentStack.push({ | |
type: "brackets", | |
modifiers: currentModifiers | |
}); | |
currentModifiers = []; | |
pos += 1; // Skip over the closing bracket | |
stateFinishedItem = true; | |
} else { | |
throw new Error(`Invalid byte found; expected ] but got ${string[pos + 1]}`); | |
} | |
} | |
} | |
return itemStack[0]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment