https://is-xss-possible.pages.dev/
(Please note that this problem might be unresolvable, as it's a real-world one.)
I encountered the following JavaScript in the real world (this is the simplified version):
const obj = {};
function run(user_input) {
const data = JSON.parse(user_input.data);
return obj[data.one][data.two].apply(null, [data.three, data.four, data.two]);
}
window.addEventListener("message", run);
I've been struggling with this JavaScript for over 48 hours, but I haven't been able to execute arbitrary JavaScript yet.
Since we can access nested properties twice, we can use various functions.
For example, if we supply the {"one":"constructor","two":"constructor"}
, we can access the constructor of the constructor (i.e., Function()
).
While we can construct a new function using obj["constructor"]["constructor"]
, we have two problems:
- The constructed function won't be executed. (No
Function("...")()
call) - We can only control the
arg1
andarg2
, since thefunctionBody
will be aconstructor
: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function
For problem 2, we can execute arbitrary JavaScript via the default argument parameter. For example, the following function will execute alert(1)
when called:
function test(a = alert(1)) {}
test();
Function()
will construct the function like above with the following snippet:
Function("a = alert(1)", "")();
For problem 1, I'm currently thinking of breaking the generated function and causing the immediate execution. For example, the following Function()
call will generate the function without arguments:
Function("/*test1", "test2*/", "").toString();
> 'function anonymous(/*test1,test2*/\n) {\n\n}'
However, ECMAScript forbids parameters to break the function:
https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-createdynamicfunction
NOTE: The parameters and body are parsed separately to ensure that each is valid alone. For example, new Function("/*", "*/ ) {") does not evaluate a function.
Browsers are following this specification and throw the following errors when trying to break the function from the parameter:
- Chromium:
Arg string terminates parameters early
- Firefox:
unexpected end of function parameter list
You can test this with the following link: https://is-xss-possible.pages.dev/#{"one":"constructor","two":"constructor","three":"/*","four":"*/){},function a("}
I've read the source code of the browsers, but couldn't find ways to bypass the validation. The following are the links to the validation of each browser:
- Chromium: https://source.chromium.org/chromium/chromium/src/+/main:v8/src/parsing/parser.cc;l=3115-3129;drc=8f477f936c9b9e6b4c9f35a8ccc5e65bd4cb7f4e
- Firefox: https://searchfox.org/mozilla-central/rev/f9157a03835653cd3ece8d2dc713a782b7e4374e/js/src/frontend/Parser.cpp#3522-3527
Is there anything that I missed? I'm happy to split the bounty if anyone finds the solution!
Edit 1: The run
function is called as a listener of the message
event, so this function can be reached multiple times and the return value will be discarded.
Edit 2: obj
will always be empty ({}
), I checked this multiple times.
Edit 3: obj
is defined outside of the function, I've updated the snippet to reflect this.