... Loading ...

SSD Secure Disclosure

Disclosing vulnerabilities responsibly since 2007
Dark Theme

SSD Advisory – Firefox Sandbox Infoleak From Uninitialized Handle In CrossCall

Vulnerability summary

The crosscall FilesystemDispatcher::NtOpenFile can leak an uninitialized handle value to a renderer due to an incorrect return value in FileSystemPolicy::OpenFileAction. The crosscall NtOpenKey seems to also suffer from the exact same bug.

In this advisory, we show how to leak a function pointer stored in the broker’s stack (corresponding, in this case, to a return address).

We’ll first have a glance at a crosscall implementation, from the renderer to the broker. Then, we’ll explain where the bug lies before eventually explaining how to trigger the bug and find a way to disclose a function pointer.

Vendor Response

Firefox has released version 67 to address this issue, additional information can be obtained from: https://www.mozilla.org/en-US/security/advisories/mfsa2019-13/#CVE-2019-11694

CVE

CVE-2019-11694

Credit

An independent Security Researcher, Jeremy Fetiveau (@__x86), has reported this vulnerability to SSD Secure Disclosure program.

Affected systems

The bug is specific to the Windows implementation of the sandbox. Both x86 and x64 builds are affected.

Vulnerability Details

Sandboxing:

On Windows, browsers usually have a sandbox where there is one main normally privileged process and several processes running at a lower privilege level. The basic idea is that every tab would execute in a restricted environment so that compromising this process would not give an attacker a complete access to its target’s machine. He would need to also evade the sandbox.

To implement that, browsers make use of different mechanisms provided by the operating system such as restricted tokens, low integrity levels, job objects or station/desktop isolation.

In order to be able to do operations, a renderer process will likely have to ask the broker to do it for him using an IPC mechanism.

When trying to do system calls, a renderer might instead do what is called a crosscall. This means that instead of directly making a system call, a renderer will send a message asking the broker to do it for him. Depending on the policy, the broker may or may not execute the system call and send the result back to the renderer, through IPC.

Crosscalls:

When setting up the sandbox, renderer-side functions in ntdll like ntdll!NtCreateFile are hooked so as to redirect execution to the interception functions implementing the crosscall. Those functions write data in memory and signal the broker to make a crosscall.

Then, the broker executes dispatcher functions that does the actual syscall and sends the result back to the renderer through shared memory.

If we’ll take the case of ntdll!NtCreateFile, the renderer will actually end up executing TargetNtCreateFile. What is does is the following:
1. Call the original syscall
2. If the resulting NTSTATUS code is either STATUS_ACCESS_DENIED or STATUS_NETWORK_OPEN_RESTRICTION
3. Validate parameters
4. Prepare shared memory
5. Send a signal to actually make the crosscall using the function CrossCall

After the renderer called CrossCall so as to make an NtCreateFile crosscall, the broker is going to execute the function FilesystemDispatcher::NtCreateFile.

After a few checks and evaluating the low level policy, it will eventually call the FileSystemPolicy::CreateFileAction. This is where the actual syscall will be made.

Here, NtCreateFileInTarget will do the actual system call.

Let’s compare both OpenFileAction to CreateFileAccess

In OpenFileAction, the following code is incorrect:

The return value should be false instead of true.
OpenFileAction is called by the FilesystemDispatcher::NtOpenFile crosscall as follows:

When the condition ASK_BROKER != eval_result is true, access to the file is denied and the broker should then execute.

But as we saw previously, that is not case and we would therefore execute the following code:

io_information is set to 0 and nt_status would be set to STATUS_ACCESS_DENIED.

However the handle is not initialized! Therefore, the broker will put some uninitialized stack memory in the shared memory when executing ipc->return_info.handle = handle

Triggering the bug

Long story short, to trigger the bug, you only need to make an NtOpenFile crosscall on a file for which access would be denied.

It is required to manually read the shared memory or do the call to CrossCall directly because TargetNtOpenFile checks answer.nt_status and does not fetch shared memory the nt_status is an error code (which is what we expect). The line *file = answer.handle; would not get executed.

But as this is renderer code, you can do whatever you want. Either call CrossCall (or make your own) or scan the shared memory.

The trigger would roughly look like this.

Getting a function pointer

We need to make sure that the stack contains something interesting. If you try to open the file L"\\??\\C:\\secret\\canttouchme.txt", you would not get anything interesting. The return handle would actually be the number of characters of the file name (in this case, 0x1D).

Indeed, when receiving the signal for a crosscall, the broker would first execute the function sandbox::SharedMemIPCServer::InvokeCallback.

The function GetArgs will fetch the arguments from the shared memory. For every argument, it will call a function GetRawParameterXXX. Henceforth, when fetching the filename parameter, GetParameterStr will get called.

The code of GetParameterStr is the following:

If you’ll read the disassembly, you will find the following instructions:

Those instructions check if the length of the file name is greater than the length of the parameter string. When debugging, we can than see the original capacity of the string is of 7 characters. So we compare the number of characters of the file name to 7.

In the case of a greater string such as L"\\??\\C:\\secret\\canttouchme.txt", the following basic block is executed.

This will store the number of characters of the file name string. This is something an attacker does not want to happen because it is a completely irrelevant information.

But what happens if the string is smaller? A valid file name could be L"\\??\\C:\\".
The following code will be executed instead:

This is a much better thing because it doesn’t write the string length on the address that will later correspond to the uninitialized handle.

Let’s do an NtOpenFile crosscall with the filename L"\\??\\C:\\" and use FILE_WRITE_DATA as a DesiredAccessMask like this: NtOpenFileStruct(&file, FILE_WRITE_DATA, &obj_attrib, &io_status_block, 1, NULL);

Now we will take a look at the leaking handle and we will see what to what it belongs when attaching to the broker process.

So basically, instead of leaking a string size, we leak a function pointer that corresponds to the return address pushed on the stack while executing the _memmove called by GetParameterStr.

This demonstrates that this bug does leak some sensitive information from the broker and that it is possible to get different kind of data by playing with the crosscall parameters.

Testing the vulnerability

To play with the sandbox without using a renderer RCE bug, a simple way to do that is using reflective DLL injection. However, Firefox prevents that by hooking the function kernel32!BaseThreadInitThunk.

Therefore, simply patch the JMP by the standard MOV EDI, EDI.

Looking at the dissassembly:

If the file we’re trying to open gets an DENY_ACCESS EvalResult instead of ASK_BROKER, the bug will be triggered. The disassembly shows that the handle will be uninitialized.

Bug variants

The NtOpenKey dispatcher (broker side) contains the exact same bug and will also write an uninitialized HANDLE in the shared memory. Me might be able to leak different kind of information using this bug because the handle variable would be allocated at a different place on the stack.

Exploit

The POC uses the reflective DLL injection tool from Stephen Fewer. https://github.com/stephenfewer/ReflectiveDLLInjection Simply change ReflectiveDll.c and Sandbox.h. Firefox prevents DLL injections by hooking kernel32!BaseThreadInitThunk. You can remove that using this windbg command: eb kernel32!BaseThreadInitThunk 8b ff 55 8b ec

In order to find IPC Shared Memory, the POC simply uses OFFSET_TO_G_SHARED_IPC_MEMORY as an offset between the firefox module base and firefox!g_shared_IPC_memory. Here is the exact version used for which the offset works.
$ hg log -l 1
changeset: 451842:8330fe920aea

Print Friendly, PDF & Email

Leave a Reply