RSS Security

🔒
❌ About FreshRSS
There are new articles available, click to refresh the page.
Before yesterdayZero Day Initiative - Blog

CVE-2021-2429: A Heap-based Buffer Overflow Bug in the MySQL InnoDB memcached Plugin

2 September 2021 at 16:05

In April 2021, the ZDI received a submission of a vulnerability in the MySQL database. It turned out to be a heap-based buffer overflow bug in the InnoDB memcached plugin. It was submitted to the program by an anonymous researcher.

The vulnerability affects MySQL versions 8.0.25 and prior. It can be triggered remotely and without authentication. Attackers can leverage this vulnerability to execute arbitrary code on the MySQL database server. Oracle patched it in July and assigned it CVE-2021-2429, while ZDI’s identifier is ZDI-2021-889.

The Vulnerability

The following analysis is based on the source code of MySQL Community Server version 8.0.25. The bug is in the memcached GET command, which is used for retrieving data from a table. For performance, the GET command supports fetching multiple key-value pairs in a single memcached query. Here is an example:

The keys specified in the GET command are tokenized by process_get_command() and then handled one by one in innodb_get() .

If a key in the GET command has the form @@containers.name, then the variable report_table_switch will have been set to true, satisfying the branch at (1). The memcpy at (3) copies table_name to the row_buf buffer. Before performing the memcpy, the code at (2) validates that there is still enough space in row_buf. However, this validation is performed with an assert() only. Since assert is a macro that produces code only in debug builds but not in release builds, this leads to a buffer overflow that can be reached when running a release build.

The Trigger

The InnoDB memcached plugin is not enabled by default. One must build MySQL from source with -DWITH_INNODB_MEMCACHED=ON. Here is the build detail. By default, the memcached daemon listens on TCP and UDP port 11211. The payload is a single GET command as seen in the example below.

       get @@aaa @@aaa @@aaa ...

@@aaa is one of the default rows in the innodb_memcache database.

Each @@aaa is replaced with the table name test/demo_test at (5) within the innodb_get() function shown above. The resulting overflow content has the form test/demo_testtest/demo_testtest/demo_test.... The length of the overflow is controllable by the attacker. After sending the payload, the heap overflow is triggered in the mysqld process. The call stack is shown below.

The Patch

The vulnerability was fixed in version 8.0.26. The patch is straightforward. It explicitly checks the length before copying.

Conclusion

Although the InnoDB memcached plugin is not enabled by default, it is nonetheless wise to apply the patch as soon as possible. It would not surprise me to see a reliable full exploit in the near future.

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

CVE-2021-2429: A Heap-based Buffer Overflow Bug in the MySQL InnoDB memcached Plugin

ZDI-21-502: An Information Disclosure Bug in ISC BIND server

16 June 2021 at 15:27

Last year, we received a submission of a remote code execution vulnerability in the ISC BIND server. Later, that same anonymous researcher submitted a second bug in this popular DNS server. Similar to the first bug, it exists within the Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO) component, and its location is quite close to the previous submission. The vendor categorized this bug as low severity, so they did not issue any CVE or advisory. However, this bug is still interesting and worth a closer examination.

This vulnerability affects BIND version prior to 9.11.31 and 9.16.15. It can be triggered remotely and without authentication. It leads to out-of-bounds (OOB) read on heap memory and could allow an information disclosure to remote attackers. It might be possible to leverage this vulnerability in conjunction with the previous submission to execute arbitrary code on an affected BIND server.

The Vulnerability

The root cause is in the der_match_tag_and_length() function. It is used for matching a tag and geting the following length field from network packet. Based on the normal usage of der_get_length() in BIND, the parsed length field, length_ret, should be validated by the caller. However, der_match_tag_and_length() is one of the exceptions.

The length_ret at (1) is under control and has not yet been validated. Let's see one of the callers for der_match_tag_and_length():

The untrusted len is then used to decode the negTokenInit at (2). Many checks within decode_NegTokenInit() are based on len. Now, those checks are incorrect, and could lead to OOB access on different sub-fields, such as mechTypes, reqFlags, mechToken, etc.

The Trigger

The configuration used to reproduce this bug is the same as in the previous submission. The following screenshot is the crafted SPNEGO request for this bug.

Figure 1 - Wireshark view of the crafted SPNEGO request

Figure 1 - Wireshark view of the crafted SPNEGO request

The length_ret at (1) is controlled from offset 0xa5 as 0x91929394. The sub-field mechToken is an octet string. Its length is the crafted value 0x727374, appearing at offset 0xcd.

Upon receiving this crafted request, an OOB read is triggered within the handling of the mechToken field. The following call stack is based on BIND version 9.16.13.

The Exploitation Plan

Here’s one possible method for gaining information disclosure on an affected server.

After decode_NegTokenInit() parses the negTokenInit and its sub-fields, the loop at (3) searches for valid OIDs within the parsed mechTypes. If a valid OID is found, the server responds with an accept message at (4). Otherwise, the server responds with a reject message at (5). This gives us the ability to get the offset for some heap chunks. It’s possible this bug could be used in conjunction with the heap overflow from the previous submission, but this requires more research.

The Patch

From version 9.16.15, the ISC implementation of SPNEGO was removed from BIND 9 source code. Instead, BIND 9 now always uses the SPNEGO implementation provided by the system GSSAPI library when it is built with GSSAPI support. Because of this, the related bugs are also removed. This feature change was also applied to version 9.11.31.

You should verify you have a patched version of BIND as many OS distributions provide BIND packages that differ from the official ISC release versions. In particular, it is not uncommon for a distribution to choose a stable base version for their BIND package then selectively apply chosen patches for only those issues they think merit inclusion.  A consequence of this is that BIND may contain a fix even if the version number is different (and possibly less) than the version number in which ISC patched the vulnerability.

Conclusion

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. This same anonymous researcher also reported a remote code execution bug within the handling of TKEY queries, which was also fixed recently.

We are also looking forward to seeing a reliable full exploit method at some point. As a reminder, ISC BIND is a part of our Targeted Incentive Program and could earn some big payouts for full exploits. We certainly hope to see some in the future.

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

ZDI-21-502: An Information Disclosure Bug in ISC BIND server

CVE-2021-31440: An Incorrect Bounds Calculation in the Linux Kernel eBPF Verifier

27 May 2021 at 16:00

In April 2021, the ZDI received a Linux kernel submission that turned out to be an incorrect bounds calculation bug in the extended Berkeley Packet Filter (eBPF) verifier. This bug was submitted to the program by Manfred Paul (@_manfp) of the RedRocket CTF team (@redrocket_ctf). Manfred Paul had successfully exploited two other eBPF verifier bugs in Pwn2Own 2020 and 2021 respectively.

This particular bug bypassed the eBPF verification and resulted in an out-of-bounds (OOB) access in the Linux kernel. The researcher exploited this bug and demonstrated a Kubernetes container escape. The patch was recently released as CVE-2021-31440 . Linux kernel versions from 5.7 and on were affected.

The Vulnerability

After CVE-2020-8835, one significant change was added to the verifier, namely 32-bit bound tracking. For the unsigned and signed minimum and maximum bounds, the 32-bit bounds u32_min_value, u32_max_value, s32_min_value and s32_max_value additionally and exclusively apply to the lower 32 bits of each tracked register.

However, a very similar mistake was reintroduced in __reg_combine_64_into_32(). This function uses the known bounds on a 64-bit register to infer bounds for the register’s lower 32 bits.

If the smin_value and smax_valueat (1) are both within in the range of signed 32-bit integers, the 32-bit signed bounds are updated accordingly. In all other cases the 32-bit signed bounds remain in the “unbounded” state. This logic is proper. However, in the corresponding logic for unsigned bounds, the checks on umin_value and umax_value are performed separately at (2) and (3). This logic is incorrect. For example, consider what happens if a register has umin_value = 1 and umax_value = 1<<32. data-preserve-html-node="true" At (2), the verifier will set u32_min_value to 1. At runtime, the register’s actual value can be 1<<32, data-preserve-html-node="true" making the lower 32 bits equal to 0. This violates the correctness of the register’s bounds, which indicate that the minimum value of the lower 32 bits is 1.

Notably, there was already a fix for the case of signed bounds at (1) from December 2020, but it missed the case of unsigned bounds.

Exploitation

The bug can be exploited as follows. Begin with these eBPF instructions:

These instructions set BPF_REG_2 as 1<<32. data-preserve-html-node="true" The two successive NEG instructions cause the verifier to lose track of all bounds for BPF_REG_2, while keeping its runtime value unchanged.

Next:

This conditional branch tests whether BPF_REG_2 is greater than or equal to 1. For the true side of the branch, the verifier sets the register’s umin_value to 1. Furthermore, the verifier calls __reg_combine_64_into_32(), which sets u32_min_value to 1 as well. This is the branch that will be followed at runtime.

Next:

This second conditional branch tests whether BPF_REG_2 is less than or equal to 1, so for the true side of the branch, the verifier sets the register’s u32_max_value to 1. At this point, for the true path, u32_max_value and u32_min_value are both set to 1, meaning that the verifier believes that the value of the lower 32 bits is known to be exactly 1. Recall, though, that the runtime value of BPF_REG_2 has been set to 1<<32 data-preserve-html-node="true" from the beginning, so that the true value of the lower 32 bits is 0. Thus, the verifier has inferred incorrect bounds for the register.

Finally, we can use this to produce an out-of-bounds access:

The BPF_MOV32_REG extends the wrong knowledge to the whole 64-bit register. After two ALU (arithmetic) operations, the register value is believed by the verifier to be 0, but is actually 1. This situation is the same as in the exploit steps in CVE-2020-8835, and the later steps of that exploit can be reused here. The out-of-bounds read/write access is achieved by using the wrongly bounded register, followed by an arbitrary read/write, and privilege escalation to root.

Demonstrating Exploitation

Since the Kubernetes container by default allows access to all system calls, a container escape can be achieved by exploiting this bug. The researcher set the current UID and GID to 0 and obtained the CAP_SYS_MODULE capability. This allowed the program to load an arbitrary kernel module outside of the container.

Conclusion

The eBPF module is still a good place for kernel bug hunting. It was also recently introduced to Windows. This attack surface is usable not only for local privilege escalation but also for container escapes. Those running systems affected by this bug should apply this mitigation, or even better, upgrade the kernel to an unaffected version. Thanks again to Manfred Paul(@_manfp) of the RedRocket CTF team (@redrocket_ctf) 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.

CVE-2021-31440: An Incorrect Bounds Calculation in the Linux Kernel eBPF Verifier

CVE-2021-20226: A Reference-Counting Bug in the Linux Kernel io_uring Subsystem

22 April 2021 at 16:27

In June 2020, we received a Linux kernel submission detailing a reference-counting bug in the recently introduced io_uring subsystem. The bug leads to a use-after-free on any file structure, which can be leveraged for privilege escalation in the kernel. This bug was submitted by Ryota Shiga (@Ga_ryo_) of Flatt Security.

We believe that the vulnerability affected the Linux kernel from version 5.6 to 5.7 inclusive. The vulnerability has been assigned identifiers ZDI-21-001 and CVE-2021-20226.

The Vulnerability

Linux kernel 5.1 introduced a new asynchronous I/O feature called io_uring. This subsystem operates by batching I/O operation system calls, so that multiple I/O operations can be performed in one system call.

Linux kernel 5.6 has a flawed implementation of the IORING_OP_CLOSE operation. When a system call passes a files_struct to a kernel thread, io_grab_files() doesn’t increment the reference counter at (1). This can lead to a later access of the freed file structure.

Exploitation

The map_lookup_elem() and map_update_elem() functions are good candidates for use in exploiting this bug.

The fdget() at (2) is an optimized function that doesn't increase the reference count if the current task is single-thread. The returned file structure, f, can be freed by a later IORING_OP_CLOSE. The __bpf_copy_key() syscall at (3) is actually a wrapper for copy_from_user(). This provides an opportunity to produce a race condition by using userfaultfd and triggering the vulnerability. At this point, file structure f and its corresponding map are freed. The memory of the map can be reallocated with fake data at (4) and (5). Finally, we can read arbitrary memory at (6) and disclose to usermode.

Here is an overview for the exploit timeline:

Figure 1 - The Exploit Timeline

Figure 1 - The Exploit Timeline

The recvmsg() function is for timing control. The freed bpf_map can be faked by spraying with setxattr(). The arbitrary write can be achieved by map_update_elem(). This exploit method is restricted to a single-core environment due to the condition of fdget().

Conclusion

New features mean new attack surfaces, and new attack surfaces often lead to new bugs being discovered. It will be interesting to see if any other vulnerabilities are found in this subsystem. Regardless, it was a great find by Ryota, and we appreciate his submission. If that name sounds familiar at all, Ryota also competed in the most recent Pwn2Own and won $30,000 demonstrating a different privilege escalation bug on Ubuntu. We look forward to seeing 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.

CVE-2021-20226: A Reference-Counting Bug in the Linux Kernel io_uring Subsystem

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

❌