Show Notes

73 - Windows Bugs, Duo 2FA Bypass, and some Reverse Engineering

tl;dr WhatsApp stored TLS session resumption files on the sdcard where a malicious application or some social engineering attacker could read.

Man in the Disk, any sort of attack that can allow an attacker to modify or read sensitive files from the disk. In WhatsApp’s case, its just weak file permissions when storing TLS session information (used to TLS session resumption) on the sdcard rather than within the sandboxed application data directory.

The Writeup also explores how this could be abused, first leaking the sdcard content with some social engineering and a Chrome CVE that incorrectly applies restrictions on what files JS from a content:// page can read allowing a JS file opened to basically read any file through the Media Content provider including the WhatsApp session cache files.

Once an attacker has the session resumption information it becomes possible to MitM the connection and either attack the extraction process when downloading stickers and such to overwrite WhatsApp libraries, or expose the Noise protocol key-pair to snoop on e2e traffic.

Just an overview of how opening links is broken if you don’t check the schema and let the OS deal with it.

Tl;dr /api/log endpoint writes to a log file with attacker controlled data. Also attacker can write to any *.log file.

This issue on its own is hard to discover without source access and not too damaging until you consider what happens when you need to do IR and can’t trust your logs.

Two 2FA bypassing, one based on auth state not being tied to the user’s session, the other involved swapping a transaction id to trick the server into thinking the attacker’s 2FA acceptance was the victim’s.

Bug 1 If an attackers copies their sid cookie from a 2fa request and pastes it into the request made by the victim’s account it’ll send the 2fa to the attacker instead of the victim’s device.

Bug 2 The application would receive a transaction id that it would poll for the status of. If you intercept the poll and change the id so it replies with an attacker completed transaction it’ll consider the transaction accepted.

Tl;dr Grammarly will add users to the wrong organization if an attacker creates an org with an entityId that matches the victim’s but with extra whitespace at the end.

If an attacker creates a SAML organization with an entityId that is the same as another entityId but with extra whitespace grammarly will get confused when trying to log people into the original organization. It’ll log them in against the right SAML endpoint but then add them to the organization with whitespace in the ID. It seems that it’ll trim the org for lookup and then when it gets multiple responses, just takes the first.

Four pre-auth NoSQL injections (blind) as well as an authenticated PHP injection.

Bug 1 and 2 - First issue was in the auth/check endpoint for authenticating users. It took a user parameter and passed it directly into a filter object with no sanitization. You could use the $func operator of the MongoLite library to call any PHP function with a single parameter, including var_dump to dump all the users in the db. The same injection existed in the auth/requestreset endpoint for creating password reset tokens.

Bug 3 and 4 - Third and fourth issues were in /auth/resetpassword and /auth/newpassword respectively. Similar issue but on the token parameter. The same trick allowed dumping of valid tokens. Chaining one of these two issues and one of the previous two, you had valid users and valid tokens. You can use the API with the token you acquired to dump user account information, which you can use to bruteforce the password hash and reset the password. This can lead to RCE since Cockpit’s “Finder” (file explorer) allows you to upload files, so you can upload a webshell.

Bug 5 - Final PHP injection issue was when parsing filters on the /accounts/find endpoint, which takes a user-provided filter and uses it to build up a query that gets passed to eval. This one isn’t really that important though, because you need auth to hit it and as pointed out, you can already upload a web shell if you have auth.

tl;dr Uses a known docker breakout to escape into the wrapping VM, then by replacing a logfile with a symlink you could post to locations on the host machine.

  • Kata Containers - A secure container runtime with lightweight VMs that free like containers but provider stronger isolation.
  • BitBucket Pipelines - Runs build jobs from Bitbucket repos. Uses Kata containers to separate build jobs of different users. Container host runs a Kata VM that contains several containers, the builder container, containers for various services and a Docker-in-Docker container which was privileged for executing docker commands.
  • Container Escape - The builder container could be escaped using a known privileged container escape that abused the release_agent.

Vuln 1 - Tampering With Other Builds tl;dr: /usr/bin/docker was externally provided and was r/w mounted in the VM so it could be backdoored after escaping the container.

Inside the container there are some files mounted from the host, notable /usr/bin/docker. Unfortunately this is a read-only mount. Inside the VM however, there is just a single read-write mount that is the parent directory. So you could overwrite the docker binary used by all the build containers.

Vuln 2 - Escaping the VM tl;dr: The stdout logs were mounted inside the VM, but were written to by a process on the host. Symlinking the log file, along wth triggering a log rotation to force the file to be reopened resulted in redirecting logs to any location on the host machine.

Another hostpath mount was under the /var/log/pods/… contained stdout logs from each container. These were written by a process on the host, and used by the Web UI. Using a symlink one could get the output written to any location on the host, but couldn’t overwrite files. This had to do with containerd not reopening the file after the symlink was made and ignoring errors. The new file would be created because a separate watch-dog process would see that 0.log didn’t exist (or where it pointed) and would send a message to containerd to open it. When symlinked to an existing file, this didn’t happen. This could be triggered in a race by triggering a log rotation, which could copy the log to 0.log. and tell containerd to reopen 0.log. You could race the rotation and the reopen command to get an arb host-filesystem write.

tl;dr Cleverly crafting a packet with a large header+options length could cause a null dereference. The net buffer would be created with DataSize=uint16_t(length), but it would attempt to read with size=length (no truncation), which would result in an error case and a null return.

NdisGetDataBuffer is used to get a contiguous block of data from a NET_BUFFER structure. When there is an error, it’ll return a null. Ipv6pReassembleDatagram doesn’t account for that so in the error condition it will try to write to the null pointer, crashing the system

Before this call, it’ll calculate the size of the Unfragmentable headers and the ipv6 headers. This value it used when setting up the net buffer, where it is passed into NetioRetreatNetBuffer as a uint16_t, which sets the DataLength.

Then it tries to get the data buffer, with GetDataBuffer, passing in that size as the amount of bytes needed, but it does not truncate the length. So you end up with the NetBuffer->DataLength being less than the needed number of bytes, and a NULL return.

The trick is how do you craft a packet with more than 0xFFFF bytes for the headers. MTU will prevent such packets from being sent on most setups. QuarksLabs post covers this. Basically because of the MTU even fragmented packets are limited in their headers, but if each fragment itself has a fragment header you can cause a recursive reassembly of the packets and get around that, resulting in a packet with more headers than the MTU.

Two vulns related to properties on a DirectComposition buffer. Adding a new property it adds it, then checks some values and potentially returns an error before finalizing, but the property gets added. It creates a disconnect between the propertyCount and the actual numerb of properties.

The main vuln is an an OOB write in updating properties. It user-land side of code doesn’t check propertyId to ensure it is within the array (kernel does, but thats where the Add issue is used to create a disconnect) It does also check a couple values at the propertyId offset so a bit of heap spraying and grooming to get around that to get the OOB write.

Heap overflow in Windows Defender (mpengine.dll). Overflow happens while unpacking an ASProtect packed executable. While iterating over the section table it looks for the next highest virtual address. So if you have two entries with the same vaddr, one with a 0size it’ll ignore the second occurrence, resulting in the section being allocated with the wrong size, and then when data is copied into it, overflowing.

Porting of a V8 nday to Tesla Model 3. The vuln is older (from 2020) and is a turbofan optimizer based bug. Jist of the bug is when it tries to get a “map” (or type) of an object used in a call, that type can be either reliable or unreliable. Unreliable is when the call has side-effects on the object. If that call has side-effects but it mistakenly is not marked as unreliable, there’s possibility for type confusion. By abusing such a path that gets taken with Reflect.construct, you can cause a targeted type confusion on an array, changing it from an array of integers to an array of doubles.

That then gets exploited to corrupt a float array to get OOB read/write. By then setting up adjacent objects they can get addrof and fakeobj primitives, which are the browser endgame prims. Author had some trouble getting the weaponized exploit to work out of the box on the tesla though. That’s because the original exploit from exodus abused the “pointer compresion” feature enabled in Chrome 80, where the upper 32-bits of JSObjects were stored in a register and combined with 32-bit pointers on the heap to form final pointers. Tesla’s browser was too outdated for that, so they had to change the strategy to leak using array.pop instead of inline slots for addrof, and to use array.push to corrupt the backing store on a uint32array.

The bug/exploit weren’t really new, but the porting aspect is interesting, especially to such a unique target.