HexBrain

Elastic · Report #1636382

Synthetics Recorder: Code injection when recording website with malicious content

Validstatus
highseverity
47upvotes
8 Apr 2023disclosed

How it was found

Summary

Hello team! Synthetics recorder has a quote function to escape user-controlled input, but in one particular scenario the escaping isn't enough and a malicious website can inject arbitrary code in the recorder session.

Description

The waitForNavigation event calls quote within the context of a multi-line comment (/* ... */) so we can break out of that without using the escaped characters (reference)

In a normal situation the code generated looks like this for a navigation event to https://example.com

    page.waitForNavigation(/*{ url: 'https://example.com' }*/),

but it's possible to escape out of the comment without using single quotes (which would be escaped) with a specially crafted URL like https://example.com?q=*/require(child_process).exec(touch$IFS/tmp/haxx)/*

    page.waitForNavigation(/*{ url: 'https://example.com?q=*/require(`child_process`).exec(`touch$IFS/tmp/dee-see`)/*' }*/),

The syntax highlighting here on HackerOne helps visualizing how that works. $IFS is used because spaces get encoded to %20.

It's possible to have code execution when the victim uses the test feature inside of the synthetic recorder but the code we're allowed to use is fairly limited because the require function isn't available. The maximum impact is when the user saves the recorded session as a project and executes it using the synthetic runner.

Steps To Reproduce

Preparation

Install the synthetics recorder (See https://github.com/elastic/synthetics-recorder/, I'm following the instructions to run it in development mode (nvm install; nvm use; npm install; npm run dev) but you could also download the binary on the releases page)

Reproduction

  1. Start Synthetics Recorder and enter http://deesee.xyz:4567 in the text box where it says "Enter a Starting URL"

  2. Click "Start Recording"

  3. A browser has opened, this website is a modified clone of my blog. Click the GitLab icon in the top right

    {F1820934}

  4. Close the browser window

    In a normal Synthetics Recorder session there would be much more steps to record but here we only did what's necessary to trigger the issue.

  5. Click the "Export" button and you'll see this code

    step('Go to http://deesee.xyz:4567/', async () => {
      await page.goto('http://deesee.xyz:4567/');
      await Promise.all([
        page.waitForNavigation(/*{ url: 'https://gitlab.com/dee-see?query=*/require(`child_process`).exec(`touch$IFS/tmp/dee-see`)/*' }*/),
        page.click('[aria-label="GitLab"] svg')
      ]);
    });
    

    We can see the payload is in place. It's fairly obvious because we only recorded one step, but in a long recording session it would be buried deeper.

  6. Click the "Export" button and save the file in a directory

  7. In that directory run npm init -y; npm install @elastic/synthetics; npx @elastic/synthetics .

  8. When the tests finished running observe that the touch /tmp/dee-see command ran

Those last steps seem contrived, but that's how a synthetics test suite is setup and how a developer would make sure the session they just recorded would be integrated into their builds and whatnot.

Supporting Material/References:

{F1820942}

CVSS

Confidentiality and Integrity impact are High because of the arbitrary command execution. I also included Availability impact because those commands can shut down the system. I will concede though that Attack Complexity could be "very high" if that existed on the Attack Complexity scale. :)

Impact

Someone with control over the website's content can run arbitrary code where ever the synthetics recorded session will be re-executed.

Developer computers and CI systems come to mind. The most realistic attack scenario would be privilege escalation from within a company.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original