πŸ”’
There are new articles available, click to refresh the page.
Before yesterdayZero Day Initiative - Blog

CVE-2020-3992 & CVE-2021-21974: Pre-Auth Remote Code Execution in VMware ESXi

2 March 2021 at 16:00

Last fall, I reported two critical-rated, pre-authentication remote code execution vulnerabilities in the VMware ESXi platform. Both of them reside within the same component, the Service Location Protocol (SLP) service. In October, VMware released aΒ patch to address one of the vulnerabilities, but it was incomplete and could be bypassed. VMware released a second patch in November completely addressing the use-after-free (UAF) portion of these bugs. The UAF vulnerability was assigned CVE-2020-3992. After that, VMware released a third patch in February completely addressing the heap overflow portion of these bugs. The heap overflow was assigned CVE-2021-21974.

This blog takes a look at both bugs and how the heap overflow could be used for code execution. Here is a quick video demonstrating the exploit in action:

Service Location Protocol (SLP) is a network service that listens on TCP and UDP port 427 on default installations of VMware ESXi. The implementation VMware uses is based on OpenSLP 1.0.1. VMware maintains its own version and has added some hardening to it.

The service parses network input without authentication and runs as root, so a vulnerability in the ESXi SLP service may lead to pre-auth remote code execution as root. This vector could also be used as a virtual machine escape, since by default a guest can access the SLP service on the host.

The Use-After-Free Bug (CVE-2020-3992)

This bug exists only in VMware’s implementation of SLP. Here is the simplified pseudocode:

At (3), if a SLP_FUNCT_DAADVERT or SLP_FUNCT_SRVREG request is handled correctly, it will save the allocated SLPMessage into the database. However, at (4), the SLPMessage is freed even though the handled request returns without error. It leaves a dangling pointer in the database. It is possible the free at (4) was added in the course of fixing some older bugs.

Bypassing the First Patch for CVE-2020-3992

The first patch (build-16850804) by VMware was interesting. VMware didn’t make any changes to the vulnerable code shown above. Instead, they added logic to check the source IP address before handling the request. The logic, which is in IsAddrLocal(), allows requests from a source IP address of localhost only.

After a few seconds, you might notice that it can still be accessed from an IPv6 link-local address via the LAN.

The Second Patch for CVE-2020-3992

Just over two weeks later, the second patch (build-17119627) was released. This time, they improved the IP source address check logic.

This change does eliminate the IPv6 vector. Additionally, they patched the root cause of the UAF bug by clearing the pointer to the SLPMessage after adding it to the database.

The Heap Overflow Bug (CVE-2021-21974)

Like the previous bug, this bug exists only in VMware’s implementation of SLP. Here is the simplified pseudocode:

At (5), srvurl comes from network input, but the function does not terminate srvurl with a NULL byte before using strstr(). The out-of-bounds string search leads to a heap overflow at (6). This happened because VMware did not merge an update from the original OpenSLP project.

The Patch for CVE-2021-21974

Six weeks later, the third patch (build- 17325551) was released. It addressed the root cause of the heap overflow bug by checking the length before the memcpy at (6).

Exploitation

All Linux exploit mitigations are enabled for /bin/slpd, and most notably, Position Independent Executables (PIE). This makes it difficult to achieve code execution without first disclosing some addresses from memory. At first, I considered using the UAF, but I could not figure out an effective method to get a memory disclosure. Therefore, I moved my focus to the heap overflow bug instead.

Upgrading the Overflow

SLP uses struct SLPBuffer to handle events that it sends and receives. One SLPBuffer* sendbuf and one SLPBuffer* recvbuf are allocated for each SLPDSocket* connection.

The plan is to partially overwrite the start or curpos pointer in SLPBuffer and leak some memory on the next message reply. However, the sendbuf is emptied and updated before each reply. Fortunately, there is a timeslot during which sendbuf can survive due to the select-based socket model:

  1. Fill a socket send buffer without receiving until the send buffer is full.
  2. Partially overwrite sendbuf->curpos for that socket.
  3. Start to receive from the socket. The leaked memory will be appended at the end.

There are some additional challenges, though:

Β Β Β Β Β Β Β -- Due to the use of strstr(), you cannot overflow with a NULL byte.
Β Β Β Β Β Β Β -- The overflowed buffer (obuf) will be automatically freed very soon after the return of SLPParseSrvUrl().

Together, this means that the overwrite can only extend partway through the next chunk header. Otherwise, the size of the next free chunk will be set to a very large value (four non-NULL bytes), and shortly after obuf is freed, the process will abort.

The following layout overcomes these challenges:

layout3.PNG

Assume that the target is sendbuf. In (F1), each chunk marked β€œIN USE” can be either a SLPBuffer or a SLPDSocket. A hole is prepared for obuf in (F2). After triggering the overflow in (F4), the next freed chunk is enlarged and overlapped onto the target. Next, obuf is then freed in (F5). Now, you can allocate a new recvbuf from a new connection to overwrite the target in (F6). This time the overwrite can include NULL bytes.

There is an additional problem:

Β Β Β Β Β Β Β -- Many malloc() functions from OpenSLP are replaced with calloc() by VMware.

The recvbuf in (F6) is also allocated from calloc(), which zero-initializes memory. This means that partial pointer overwrites are not possible when recvbuf overlaps the target. There is a trick to get around that, though: You can first overwrite the IS_MAPPED flag on the freed chunk in (F4). This causes calloc() to skip the zero initialization on the next allocation. This is a general method that is useful in many situations where you want to perform an overwrite on target.

Putting It All Together

  1. Overwrite a connection state (connection->state) as STREAM_WRITE_FIRST. This is necessary so that sendbuf->curpos will get reset to sendbuf->start in preparation for the memory disclosure.
  2. Partially overwrite sendbuf->start with 2 NULL bytes, where sendbuf belongs to the connection mentioned in step 1. Start receiving from the connection. You can then get memory disclosure, including the address of sendbuf.
  3. Overwrite sendbuf->curpos from a new connection to leak the address of a recvbuf, which is allocated from mmap(). Once you have an mmapped address, it becomes possible to infer the libc base address.
  4. Overwrite recvbuf->curpos from a new connection, setting it to the address of free_hook. Start sending on the connection. You can then overwrite free_hook.
  5. Close a connection, invoking free_hook to start the ROP chain.

These steps may not be the optimized form.

Privilege Level Obtained

If everything goes fine, you can execute arbitrary code with root permission on the target ESXi system. In ESXi 7, a new feature called DaemonSandboxing was prepared for SLP. It uses an AppArmor-like sandbox to isolate the SLP daemon. However, I find that this is disabled by default in my environment.

This suggests that a sandbox escape stage will be required in the future.

Conclusion

VMware ESXi is a popular infrastructure for cloud service providers and many others. Because of its popularity, these bugs may be exploited in the wild at some point. To defend against this vulnerability, you can either apply the relevant patches or implement the workaround. You should consider applying both to ensure your systems are adequately protected. Additionally, VMware now recommends disabling the OpenSLP service in ESXi if it is not used.

We look forward to seeing other methods to exploit these bugs as well as other ESXi vulnerabilities in general. Until then, you can find me on TwitterΒ @_wmliang_, and follow theΒ teamΒ for the latest in exploit techniques and security patches.

CVE-2020-3992 & CVE-2021-21974: Pre-Auth Remote Code Execution in VMware ESXi

CVE-2020-8625: A Fifteen-Year-Old RCE Bug Returns in ISC BIND Server

25 February 2021 at 17:30

In October 2020, we received a submission from an anonymous researcher targeting the ISC BIND server. The discovery was based upon an earlier vulnerability, CVE-2006-5989, which affected the Apache module mod_auth_kerb and was initially found by an anonymous researcher. The ISC BIND server shared the vulnerable code within the Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO) component, but ISC did not merge the patch at that time. After 15 years, ISC patched the bug in BIND and assigned it CVE-2020-8625.

This vulnerability affects BIND versions from 9.11 to 9.16. It can be triggered remotely and without authentication. It leads to a 4-byte heap overflow. This submission was close to earning a larger payout through our Targeting Incentive Program, but lacked the full exploit needed to qualify for the full award. Still, it’s a great submission, and the bug is worth looking in greater detail.

The Vulnerability

The heap overflow bug exists in function der_get_oid(), which is in lib/dns/spnego.c.

This function allocates an array buffer at (1). The variable len is used to keep track of the number of available elements remaining in the buffer. The code fills the first 2 elements at (2), but it only decreases len by 1 at (3). As a result, the loop (4) can overflow the buffer by 1 element. The type of data->components is int, so we have a 4-byte heap overflow.

The Trigger

Since the vulnerability exists within the SPNEGO component, TKEY-GSSAPI configuration is necessary in BIND.

The dns.keytab file can be found in bin/tests/system/tsiggss/ns1/, and the example.nil.db file is generated by the script bin/tests/system/tsiggss/setup.sh.

Now the environment is ready. Upon receiving a crafted request, the vulnerability is triggered, producing the following call stack:

Exploitation

The exploitability for this bug is highly dependent on the glibc version. The following explanation is based on Ubuntu 18.04 with glibc 2.27, which enables tcache support.

First, we have to determine what is under control from this overflow bug.

Β Β Β Β Β Β Β -- The size and content of the vulnerable buffer, which is allocated in der_get_oid(), is controllable. By the way, the buffer will be freed when the current request is done.
Β Β Β Β Β Β Β -- There is a while loop in decode_MechTypeList() to execute der_get_oid() repeatedly. The loop count is controllable.

With these two points in mind, we can manipulate the heap fairly easily. To prepare the heap, we can exhaust tcache bins of any size and refill them after the request is done. Also, the refilled chunks can be contiguous in memory. This makes the memory layout quite conducive to exploitation via a buffer overflow.

Arbitrary write

At this stage, achieving an arbitrary write is straightforward by abusing the tcache freelist.

  1. Trigger a 4-byte overflow to enlarge the next free chunk size.
  2. Allocate the corrupted chunk on the next request. It will be moved to the new tcache bin when the request is ended.
  3. Allocate the corrupted chunk again with the new size. The corrupted chunk overlaps the next free chunk and overwrites its freelist with an arbitrary value.
  4. Allocate from the poisoned tcache freelist. It will return an arbitrary address.

Attempting to leak an address

All Linux mitigations are enabled by default for BIND. We have to struggle with ASLR first, which means we will need to find a way to leak an address from memory. A possible chance for obtaining a leak is in code_NegTokenArg() function. It is used for encoding response messages into a buffer, which will be sent to the client.

buf at (5) is a temporary buffer. Its initial size is 1024 bytes, which is within the range of sizes handled by tcache. outbuf at (6) is the buffer that will be sent to the client. Its size is within range for tcache also. If it is possible to apply a tcache dup attack on these two buffer sizes, the two malloc() calls at (5) and (6) will return the same address. After the free() at (7), a tcache->next pointer will be updated into buf, which is already overlapped with outbuf. This means a heap pointer will leak to the client.

Ideally, buf_len at (6) should be chosen to be large enough to avoid interfering with small tcache bins. Unfortunately, it seems the maximum value is only about 96 bytes. Due to this problem, the process does not survive and crashes very soon after the client gets the leaked heap pointer. More research is needed to find a way to continue the path to a full exploit.

The Patch

The patched versions are BIND 9.16.12 and BIND 9.11.28. To fix BIND 9.16, ISC fixed the buffer allocation size at (1). In BIND 9.11, they applied the patch as well.

Conclusion

This bug shows how vulnerabilities can reside undetected for years, even when the software is open source and in wide use. Software maintainers need to closely monitor all of the external modules they consume to ensure they stay up to date with the latest patches. It also shows how complex this challenge can be. ISC BIND is the most popular DNS server on the internet. The scope of impact is quite large, especially since the vulnerability can be triggered remotely and without authentication. All are advised to update their DNS servers as soon as possible.

For more information about our Targeted Incentive Program, check out this blog. We hope to see more submissions for this program in the future. Until then, you can find me on Twitter @_wmliang_, and follow the team for the latest in exploit techniques and security patches.

CVE-2020-8625: A Fifteen-Year-Old RCE Bug Returns in ISC BIND Server

ZDI-20-1440: An Incorrect Calculation Bug in the Linux Kernel eBPF Verifier

19 January 2021 at 17:13

In April 2020, the ZDI received a Linux kernel submission that turned out to be an incorrect calculation bug in the extended Berkeley Packet Filter (eBPF) verifier. If you’re not familiar with it, eBPF is a Linux subsystem that is designed to safely execute untrusted, user-defined extensions inside the kernel for purposes such as packet filtering. It relies on static analysis to protect the kernel against problematic extensions. The submission we received from Ryota Shiga (@Ga_ryo_) of Flatt Security bypasses the eBPF verification and can lead to out-of-bounds (OOB) access in the Linux kernel. The eBPF verifier is a well-known source of Linux kernel local privilege escalation vulnerabilities and has been seen in many cases in the past, including being used atΒ Pwn2Own 2020.

This vulnerability affects the current Linux kernel long term version from 4.9 to 4.13. One particular distribution, Debian 9, is currently using an affected kernel version. The ZDI is disclosing this bug publicly asΒ ZDI-20-1440 without a patch in accordance with our 120-dayΒ disclosure policy.

The Vulnerability

If you are not familiar with the eBPF verifier, we highly recommend the write-up by Manfred Paul (@_manfp). There are two passes of verifications before executing any BPF programs. The first pass (check_cfg()) ensures the code is loop-free. The second pass (do_check()) attempts to determine if there are any invalid instructions or possible memory violations. Emulation is used to check for possible memory violations. The incorrect calculation described here comes from opcode BPF_RSH during the second pass. The following excerpts are based on 4.9.249.

The BPF_RSH (unsigned right shift) instruction belongs to the BPF_ALU64 class of instructions. When emulating BPF_RSH, do_check calls check_alu_op at (1), which then calls adjust_reg_min_max_vals at (2). At (3) and (4), it tries to update the minimum and maximum value of dst_reg based upon how the shift operation will modify dst_reg. Note that the local variables min_value and max_value contain the known bounds of the operand that specifies the shift distance. There are corresponding fields named min_value and max_value that hold the known bounds of dst_reg.

However, the calculations at (3) and (4) are wrong. For example, to calculate max(a >> b) (the maximum possible value of a when right-shifted by b bits), the correct formula is max(a) >> min(b). (To understand why, consider that a right shift is equivalent to division by a power of two. The largest possible result is produced by choosing the largest possible numerator and the smallest possible denominator.) Instead, the code at (4) calculates max(a) >> max(b). A corresponding mistake is present at (3).

The consequences of bounds miscalculation during eBPF verification are catastrophic. If the attacker later uses dst_reg as the address for a load or store, the verification in (5) below will be bypassed.

Once the eBPF program passes verification, it will execute in the kernel, and the attacker can achieve an out-of-bounds memory access, as seen in (6) below.

The Trigger

Before triggering the bug, we have to first create two bpf maps with bpf_create_map(). A bpf map is a memory region designated to be accessible from within eBPF code. One map is for triggering the bug, while the other is the target for OOB access. The following opcodes perform preliminary work:

The BPF_FUNC_map_lookup_elem function returns a pointer to a location in a bpf map. After execution of the code shown above, BPF_REG_8 and BPF_REG_9 are set to the values from map1[1] and map1[2] respectively. They will be used as operands for BPF_RSH. The final BPF_GET_MAP shown above loads BPF_REG_0 with a pointer to map2[0].

The next step is to get the verifier to recognize that the operands to BPF_RSH will be bounded within a certain range. Here are the opcodes to limit the range of the registers by using branches.

The verifier will correctly deduce that execution cannot fall through past these instructions unless 0 <= REG_8 <= 0x1000 and 0 <= REG_9 <= 1024. (Note that JA means β€œjump always”, not β€œjump if above” as in x86.)

It's time to trigger the bug.

After the BPF_RSH instruction, BPF_REG_8 can still have a value as high as 0x1000. But due to the incorrect computation discussed above, the verifier concludes that the maximum possible value of BPF_REG_8 is now 0. On the basis of this, the verifier incorrectly concludes that the memory operation at (B) is guaranteed to be safe.

BPF_STX_MEM at (B) will perform an OOB write on map2 with an arbitrary offset specified by BPF_REG_8.

However, there is one additional precondition. Recall from above that when encountering an instruction that operates on memory, the verifier performs checks in a function named check_mem_access(). When the address of the memory operation is controlled by a register, check_mem_access() additionally ensures that the verifier has already marked the register contents as PTR_TO_MAP_VALUE or PTR_TO_MAP_VALUE_ADJ. The verifier will only set this mark if the allow_ptr_leaks flag is enabled in the environment, and to enable this flag, the caller must have the CAP_SYS_ADMIN capability.

This means CAP_SYS_ADMIN is required to trigger the bug, even if the eBPF program is attached to a socket owned by the attacker.

Conclusion

Although the precondition reduces the impact and risk, it would still be better to apply thisΒ mitigation, or even better, upgrade the kernel to an unaffected version. Our team will try to follow up on the patch when it is released. Thanks again to Ryota Shiga of Flatt Security for submitting this bug. He’s submitted a few other reports to the program, and each has been great. We hope to see more from him in the future.

You can find me on TwitterΒ @_wmliang_, and follow theΒ teamΒ for the latest in exploit techniques and security patches.

ZDI-20-1440: An Incorrect Calculation Bug in the Linux Kernel eBPF Verifier

CVE-2020-7468: Turning Imprisonment to Advantage in the FreeBSD ftpd chroot Jail

21 December 2020 at 18:05

In July, we received a local privilege escalation bug in FreeBSD from an anonymous researcher. The target is the file transfer protocol daemonΒ (ftpd)Β that ships as part of FreeBSD. It provides a feature,Β ftpchroot, that is designed to restrict the file system access of authenticated users. The feature is implemented using the β€œchroot” system call, a security technique commonly known as a β€œchroot jail”. A chroot jail functions by confining a process to a restricted portion of the filesystem. By exploiting a vulnerability in the implementation, though, an attacker can actually use this imprisoned state to gain an enormous advantage, escalating their privileges from a restricted FTP account to `root`. This allows the attacker to execute arbitrary code on the system. This vulnerability was present in the FreeBSD FTP daemon for a long time. It can be tracked back to FreeBSD 6.3-Release. The bug is assigned asΒ CVE-2020-7468/ZDI-20-1431Β and theΒ patchΒ was released in September.

The Vulnerability

The root cause of the vulnerability is the flawed handling of chroot() inside freebsd/libexec/ftpd/ftpd.c. Here is a simplified version of the vulnerable function:

If an FTP user attempts to log in and is configured to be jailed inside a chroot jail in /etc/ftpchroot, ftpd will call the chroot and chdir syscalls as shown above. If the chdir syscall fails, the code jumps to label bad. In this situation, ftpd still awaits a new login, but the connection is already locked inside the chroot jail. This causes incorrect behavior during the next login attempt on that connection.

Exploitation

In order to force the chdir syscall to fail during login, an attacker can change the permissions on their home directory by using the command chmod 0. Additionally, the attacker would upload a specially prepared file named etc/spwd.db relative to their home directory. This file is a modified password database of a regular FreeBSD system containing a known password for the root user. After a chdir failure, ftpd is locked inside the chroot jail, so that all subsequent file system accesses are made relative to the user’s home folder instead of the true root of the filesystem. As a result, when performing authentication for a subsequent login, ftpd reads the attacker’s spwd.db instead of the legitimate /etc/spwd.db located relative to the true root of the filesystem. At this point, the attacker can log in as root with the known password. The next step is to upload /etc/pam.d/ftpd and /usr/lib/pam_opie.so.5. The first file forces ftpd to load serval dynamic libraries, including the second file, during the login process. The second file is designed to break the chroot jail with the obtained root permission and execute a reverse shell. Then, the attacker can execute arbitrary code as root. Here is a summary of the steps of the exploit.

  1. Log in as a restricted FTP account.
  2. Upload etc/spwd.db containing a known root password.
  3. Execute chmod 0.
  4. Log in as the restricted FTP account again. During login, chdir fails, leaving the ftpd process locked in the chroot jail.
  5. Log in as root with the known password.
  6. Upload /etc/pam.d/ftpd and /usr/lib/pam_opie.so.5, which contains a reverse shell.
  7. Log in as the restricted FTP account again. As before, chdir fails, leaving the ftpd process locked in the chroot jail.
  8. Log in as root with the known password. ftpd executes the reverse shell.

The Patch

To address this vulnerability, FreeBSD made a simple change. If the chdir syscall fails, ftpd will now close the connection immediately.

Conclusion

This is a logic bug for privilege escalation. Because of this, this bug is quite reliable, unlike the FreeBSD privilege escalation we blogged about inΒ September. This is the first bug submitted by this anonymous researcher. We don’t receive many bug reports for the FreeBSD operating system, so we hope they submit more in the future.

You can find me on TwitterΒ @_wmliang_, and follow theΒ teamΒ for the latest in exploit techniques and security patches.

CVE-2020-7468: Turning Imprisonment to Advantage in the FreeBSD ftpd chroot Jail

  • There are no more articles
❌