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

February 25, 2021 | Lucas Leong

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.