Skip to content

Instantly share code, notes, and snippets.

@Ry0taK
Last active April 7, 2024 18:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ry0taK/5ce7be0db9ef520a032698eda5e9fd65 to your computer and use it in GitHub Desktop.
Save Ry0taK/5ce7be0db9ef520a032698eda5e9fd65 to your computer and use it in GitHub Desktop.
Can we execute arbitrary JavaScript with these conditions?

Demo site

https://is-xss-possible.pages.dev/

Problem

(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:

  1. The constructed function won't be executed. (No Function("...")() call)
  2. We can only control the arg1 and arg2, since the functionBody will be a constructor: 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:

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment