Show Notes

82 - NETGEAR smart switches, SpookJS, & Parallels Desktop

What if authentication was optional? That seems to be the case here where the Netgear Switch Discovery Protocol, a UDP based protocol where each datagram is a header following by a Type Length Value (TLV) chain. The expectation is that all of the “get” commands can be used without authentication but that “set” commands should send the password authentication entry (Type 10) as the first part of the TLV chain. The /sqfs/bin/sccd daemon that implements the protocol does not enforce this however. So an attacker can send the “set” TLV for changing the password (type 9) to set the password without first authenticating.

tl;dr A well positioned attacker (needs to be using the same IP as the victim) can hijack a successful authentication flow and take over the session victims session by polling the get.cgi endpoint after the victim’s login was successful but before the victim has polled the same page (which happens every second)

The foundation of this attack comes from the authentication system that is in-place on the device. The basic idea is that the user sends their login to set.cgi. This creates a file containing the login information and some information about the attempt (like a browser category and client ip). It then triggers another program to read that file and write the results to another file.

The browser in the mean time polls get.cgi every second to see the state of the authentication waiting on that result for to be written.

The problem is that get.cgi doesn’t have a good way to determine that the user who started the login is the same user who is polling the get.cgi. So a well placed attacker (needs to be on the same IP, and with the same browser category (bucketed into 5 possibilities)) could make the request to get.cgi before the proper user and be successfully authenticated without having provided the credentials.

Great exploit chain starts with a newline injection, leading to the ability to write “2” to any file culminating in a login and root code execution, all doable with remotely hosted javascript.

Uncontrolled File Write

You can read a bit more about the authentication system in the “Draconian Fear” vulnerability report, but for here what matters is that when a new login comes in the username, password and some other information like where to write the results to are written to a file with newline separating each field. Then another program is signaled to process the new login and it writes the results out.

The first problem in this chain being that a newline might be part of the username or password and that breaks program that processes the login. By injecting your own newline you can take control of the file where the results will be written. While the attacker can write any file, the contents of that file will be based on the results of the login, so “2” for a bad login, “3” for too many failed attempts.

Session Validation

While the file contents are pretty limited, due to some unchecked issues while parsing “2” will read as a valid session file. This is because the parsing of the file calls fscanf("%d\n%u\n%u\n%u\n%u\n%s\n%d\n") to read everything but does not check the return value to ensure enough data was actually read. This leaves most fields with their inital “0” value.

In order to write the session file though you need to know what file is used for your session, the session file is based on an encrypted value sent in the X-CSRF-XSID header. Fortunately for the attacker, when the decryption of that header fails, the return value is never checked and the value remains blank, and the file /var/tmp/sess/login_http_ will be used without any session id on the end.

The last challenge to getting the fake session accepted is that the session creation time will be 0 which will generally be considered too old of a session and rejected due to age. The session creation time is based on the seconds since last rebook, so getting the device to reboot and then performing this attack will result in a valid session

Reboot Denial of Service Attack

Using the prior attack one could target three sysctl files to enable crashing when out of memory:

  • /proc/sys/vm/panic_on_oom
  • /proc/sys/kernel/panic
  • /proc/sys/kernel/panic_on_oops

Then to cause the device to run out of memory, simply performing a file upload against any endpoint will result in the file being written to a location that is backed by RAM. After the reboot the fake session will be considered valid and the attack will be authenticated against the device.

Code Injection

Once logged in the diag_traceroute method in set.cgi is vulnerable to trivial shell injection through the hostname field However it is worth mentioning that though this is a bug, code execution isn’t exactly a privilege escalation in this case as an attacker could legitimately perform other actions that would result in code execution like changing out teh firmware on the device.

The cool part of this paper is the speculative type confusion attack where the browser’s optimizer is trained to expect a memory access will be a uint8 array, and the CPU branch predictor that it will always go down that path. Then the attack changes both conditions leading to the CPU speculatively executing the uint8 access using data from another object, aligned in memory such that two 32bit value in JavaScript become one 64bit value.

There are a number of gotchas or conditions that are needed for this attack. For example the authors need to prevent Chrome from deoptimizing the code when the type of the access changes. They manage this by attempting to keep the entire attack within the speculation window from a mispredicted branch so to Chrome it never accesses the array with the wrong type.

This leads into another problem, the speculation window for this attack needs to be quite large. Under the original Specter attack all that was needed was an L1 cache eviction primitive, cause speculation on the evicted value and you’d have a 10 cycle speculation window while the value was retrieved from the L2 cache. For this attack a Last-Level Cache eviction primitive is necessary which gives a considerable longer speculation window as the value is retrieved from system memory. It is worth pointing out here that while the attack could work against AMD CPUs also, the authors were unable to discover a reliable LLC eviction primitive.

Another problem was that while they needed to trigger the LLC eviction, they also needed some of the information within the object to remain in the cache. To do this they coerced Chrome into compacting a set of objects in memory, and sizing the objects such that they wouldn’t quite fill an entire cache line, leading to a guarentee that there would be at least one object that straddled the boundary between 64 byte cache lines.

All of this adds up to the ability to speculate and leak information across the entire 64bit process space despite the 32bit addressing using by the JS engine.

The idea here is that by overflowing the value containing the size of a header name you can cause the header to be misinterpreted.

What happens is that in the first stage of processing by HAProxy the the headers are parsed into a structure that encodes the name length into 8 bits. So providing a header name more than 255 bytes will overflow into adjacent memory. The 1 bit resulting in also setting the size of the header’s value to 1.

For the request smuggling attack, you use this vulnerability to create a header that the first-stage parser will not recognize as being a “Content-Length” header. The second stage however will read the structure with corrupt values, and treat it and the value it reads as the legitimate Content-Length. Potentially resulting in a final proxied request with more data after the supposed end of the content that will be treated as a second request.

There is an out-of-bounds access The vulnerability is in the parsing of .dxf files. Which is a set of new-line separated groupCode and groupValue pairs. Uses this to parse out various entities, the vulnerability is in the parsing of LWPOLYLINE

The usual flow seems to be:

  • groupCode 0 and groupValue “LWPOLYLINE” pair is provided to mark the start of a new entity.
  • Followed by groupCode 90 and the number of vertices which allocates buffers for the vertices and initalizes values
  • Followed by groupCodes 10, 20, 30, and 42 in sequence providing the four vertices of the entity

The problem being that while the code expects the values to be sent in this order, nothing enforces the ordering. So if groupCode 10 is never sent, the vertexIndex will hold the value -1 because that it was the initalized to in groupCode 90

In the remaining groupCodes this index is used for an array access. In 10, 20 and 30 an offset based on the groupCode is added so no out of bounds access occurs, but in groupCode 42 is particularly interesting:

} else if (groupCode==42 && vertexIndex<maxVertices) { 
	vertices[4*vertexIndex + 3] = toReal(groupValue);  

The results of the calculation will be a write to vertices[4*-1+3] or vertices[-1]. Which is an 8-byte (its an array of doubles) out of bounds write to the memory immediately preceding the array. This may be exploitable heap meta-data though this would be determined by the application using the library.

Stack Clashing is a bit of an uncommonly seen vulnerability class, but the idea is simple, its a vulnerability resulting in the stack pointer pointing outside of the stack.

In the case of Parallels here a guest-provided size is trusted and allocated on the stack making it possible to push one thread’s stack into another threads stack region and the data being read into that region is attacker controlled. The post doesn’t go into the exploitation details, but being able to arbitrarily controll the data on another thread’s stack should be fairly straight forward.

The post focuses more on first raising the question why this was an issue to begin with. There are guard pages between stacks so that prevents any sort of slow stack growth, but its still vulnerable to large allocations skipping the guard page. On the current version of MacOS, Apple’s LLVM will instrument these stack allocations with ___chkstk_darwin() which allocates the memory one page at a time so a large allocation cannot just skip the guard page.

It was found that Parallels was shipped with this mitigation disabled however. It may be that Parallels intentionally disabled it, but another more charitable interpretation is that in attempt to compile so that the program was compatible with the last couple MacOS versions the mitigation was automatically disabled by the compiler.

The third focus of the article is on performing variant analysis using Binary Ninja’s API to discover locations TG_GetBuffer is used and the stack size no longer being deterministic. Implying that an untrusted stack allocation may have been used.