In a brilliant write-up, [Stephen Tong] brings us his “Use-After-Free for Dummies“. It’s a surprising tale of a vulnerability that really shouldn’t exist, and a walkthrough of how to complete a capture the flag challenge. The vulnerable binary is running on a Raspberry Pi, which turns out to be very important. It’s a multithreaded application that uses lock-free data sharing, through pair of integers readable by multiple threads. Those ints are declared using the
volatile keyword, which is a useful way to tell a compiler not to optimize too heavily, as this value may get changed by another thread.
On an x86 machine, this approach works flawlessly, as all the out-of-order execution features are guaranteed to be globally transparent. Put another way, even if thread one can speed up execution by modifying shared memory ahead of time, the CPU will keep the shared memory changes in the proper order. When that shared memory is controlling concurrent access, it’s really important that ordering happens the way you expect it. What was a surprise to me is that the ARM platform does not provide that global memory ordering. While the out-of-order execution will be transparent to the thread making changes, other threads and processes may observe those actions out of order. An example may help:
volatile int value; volatile int ready; // Thread 1 value = 123; // (1) ready = 1; // (2) // Thread 2 while (!ready); // (3) print(value); // (4)
This is one of [Stephen]’s examples. If this were set up to run in two threads, on an x86 machine you would have a guarantee that (4) would always print 123. On an ARM, no such guarantee. You may very well have an uninitialized value. It’s a race condition. Now you may look at this and wonder like I did, how does anyone program anything for ARM chips? First thing, even though memory reordering is a thing, ARM guarantees consistency within the same thread. This quirk only affects multi-threaded programming. And second, libraries for multi-threaded programming offer semantics for marking memory access that need to be properly ordered across threads.
The actual exploitable binary in question uses a circular queue for the inter-process buffer, and tracks a head and tail location, to determine how full the buffer is. One process puts data in, the second reads it out. The vulnerability is that when the buffer is completely full, memory manipulation reordering can result in a race condition. This ring buffer gets filled with pointers, and when the race is won by an attacker, the same pointer is used twice. In essence, the program now has two references to the same object. Without any further tricks, this results in a double free error when the second reference is released.
What are the tricks we could use to make this into an exploit? First, know that what we have is two references to an object. That object contains a pointer to another string, the length of which is entirely controlled by the user provided data. We can trigger a release of one of those references, which leads to the object getting freed, but we still have another reference, which now points to uninitialized memory. To turn this into an arbitrary read, a very clever trick is used. Before freeing our object, we allocate another object, and store a long-ish string. Then we free the object we have a double reference to, and finally free the object with the long string. Finally, we allocate one more object, but the string we store is crafted to look like a valid object. Memory gets reallocated in a last in, first out order, so the string is stored in the reclaimed memory we still have a reference to. The program expects the object to contain a pointer to a string, so our fake object can point to arbitrary memory, which we can then read.
The last trick is arbitrary write, which is even harder to pull off. The trick here is actually perform the double free, but manipulate the system so it doesn’t result in a segfault. We can use the above trick to write arbitrary data to a freed memory location. Because the location has made it onto the free list twice, the system still considered it free even though it’s also in use. The Linux memory manager uses a clever trick to manage reclaimed memory chunks, storing a pointer to the next reclaimed location in each chunk. Write the location you want to overwrite in that free chunk, and then allocate another chunk. The system now thinks your arbitrary location is the next free memory location to use. The next allocation is your arbitrary write. The writeup has more details, as well as the rest of the exploitation chain, so be sure to read the whole thing.
How Secure is that WiFi?
[Ido Hoorvitch] of CyberArk had some pandemic induced time on his hands, and opted to collect packet captures of 5000 password protected WiFi networks around Tel Aviv. In the old days, you had to capture a 4-way handshake to have any chance at breaking WPA encryption. In 2018 a new technique was discovered, where a single authentication response was all that was required to attempt to crack the key — no active user required. The magic string here is the PMKID, which is a SHA-1 hash of the WPA password and other network details, first run through a key derivation function.
The popular tool, Hashcat, can take advantage of a GPU to accelerate the cracking of a PMKID. SHA-1 hashes are one of the things GPUs are particularly good at, after all. The 8 Quadros managed almost 7 million hash calculations per second. The problem with trying to crack a WPA key is that while they must be at least 8 characters long, they can be much longer, making for an enormous search space. The first trick [Ido] used was to take advantage of one of the common password sources, a cell phone number. In Tel Aviv, that means the password is 05 followed by 8 more digits. That’s a searchable key space, and of the 5000 sniffed networks, nearly half were cracked by this approach. Next was pointing Hashcat at a dictionary file, to automatically try known passwords. Between the dictionary attack, and constraint-based approaches like the cell number format, 70% of the networks targeted were cracked. The takeaway? Use a long password that isn’t easily guessed, and won’t be easily part of a constrained search.
Google Use After Free PoC
Reported in June of this year by the Security For Everyone Team, CVE-2021-30573 now has a published PoC. This vulnerability was fixed in Chrome/Chromium 92. The triggering code is a bit of simple but very malformed HTML. Trying to parse this code just by looking at it, I immediate called it “cursed” HTML, so there’s no wonder Chrome had trouble with it, too.
<select class="form-control"> <option style="font-size: 1rem;" value=" <""> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(abbreviated) </>">a </option> </select>
PHP Worker to Root
A bug in PHP-FPM discovered by Ambionics Security allows jumping from control over a PHP worker straight to system root. While it’s a serious problem, this isn’t a remote code execution vulnerability. Some other techniques needs to be used first to take over a PHP worker thread. This means an attacker would nead to be able to run PHP code, and then also find a way to escape the PHP “sandbox.” While not trivial, there are techniques and bugs to make this possible.
The problem is that the inter-process communication mechanism is shared mapped memory, and far too much of the data structure is made available to the individual workers. A worker can modify the main data structure, causing the top-level process to write to arbitrary memory locations. While the location may be arbitrary, the actual data writes are extremely limited in this vulnerability. In fact, it boils down to two write primitives: Set-0-to-1 and clear-1168-bytes. That may not seem like much, but there are a lot of flags that can be toggled by setting a value to 1, and the rest of the exploit makes heavy use of that technique. The real trick is to generate arbitrary error messages, then use the 0-to-1 primitive to corrupt the data structure of those messages.
The vulnerability has been around for a very long time, since PHP 5.3.7. It’s fixed in 8.0.12, 7.4.25, and 7.3.32. One final wrinkle here is PHP 7.3 is still in security support, but this was considered an invasive change, and the PHP maintainers initially opted not to push the fix to this older version. After some back and forth on the bug discussion, the right call was made, and 7.3.32 has been released with the fix.
Gitlab in the Wild
HN Security had a client report something suspicious, and it turns out to be CVE-2021-22205 in use in the wild. This bug is a problem in ExifTool, where a DjVu file can execute arbitrary perl code. This RCE was abused to make new users admin on the attacked system. If you’re running Gitlab, make sure you’re up-to-date. Versions 13.10.3, 13.9.6, and 13.8.8 were released with the fix on April 14 of this year. It appears that the 14.* versions were never vulnerable, as version 14.0 was released after this fix. Hackerone and the entire bug bounty community has its share of problems, but the disclosure thread for this one is an example of a program run correctly.