Attacker Value
High
(2 users assessed)
Exploitability
Moderate
(2 users assessed)
User Interaction
None
Privileges Required
Low
Attack Vector
Local
4

CVE-2023-4911

Disclosure Date: October 03, 2023
Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

A buffer overflow was discovered in the GNU C Library’s dynamic loader ld.so while processing the GLIBC_TUNABLES environment variable. This issue could allow a local attacker to use maliciously crafted GLIBC_TUNABLES environment variables when launching binaries with SUID permission to execute code with elevated privileges.

Add Assessment

3
Ratings
Technical Analysis

This is a privilege escalation vulnerability in the dynamic loader of glibc. GLIBC_TUNABLES is an environment variable which contains a colon separated set of switches that allow the user to tweak how glibc runs without having to recompile the binary. Example of how to properly set the environment variable:

GLIBC_TUNABLES=tunable1=AAA:tunable2=BBB

When the GLIBC_TUNABLES environment variable is parsed in vulnerable versions, we can cause a buffer overflow by setting:

GLIBC_TUNABLES=tunable1=tunable2=AAA

Taking a look at glibc source code we can see why this works; the key is the absence of the colon in the above example and how that is handled at lines 203-204:

// (GLIBC ld.so sources in ./glibc-2.37/elf/dl-tunables.c)
162 static void
163 parse_tunables (char *tunestr, char *valstring)
164 {
...
168   char *p = tunestr;
169   size_t off = 0;
170 
171   while (true)
172     {
173       char *name = p;
174       size_t len = 0;
175 
176       /* First, find where the name ends.  */
177       while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
178         len++;
179 
180       /* If we reach the end of the string before getting a valid name-value
181          pair, bail out.  */
182       if (p[len] == '\0')
183         {
184           if (__libc_enable_secure)
185             tunestr[off] = '\0';
186           return;
187         }
188 
189       /* We did not find a valid name-value pair before encountering the
190          colon.  */
191       if (p[len]== ':')
192         {
193           p += len + 1;
194           continue;
195         }
196 
197       p += len + 1;
198 
199       /* Take the value from the valstring since we need to NULL terminate it.  */
200       char *value = &valstring[p - tunestr];
201       len = 0;
202 
203       while (p[len] != ':' && p[len] != '\0')
204         len++;
205 
206       /* Add the tunable if it exists.  */
207       for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
208         {
209           tunable_t *cur = &tunable_list[i];
210 
211           if (tunable_is_name (cur->name, name))
212             {
...
219               if (__libc_enable_secure)
220                 {
221                   if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)
222                     {
223                       if (off > 0)
224                         tunestr[off++] = ':';
225 
226                       const char *n = cur->name;
227 
228                       while (*n != '\0')
229                         tunestr[off++] = *n++;
230 
231                       tunestr[off++] = '=';
232 
233                       for (size_t j = 0; j < len; j++)
234                         tunestr[off++] = value[j];
235                     }
236 
237                   if (cur->security_level != TUNABLE_SECLEVEL_NONE)
238                     break;
239                 }
240 
241               value[len] = '\0';
242               tunable_initialize (cur, value);
243               break;
244             }
245         }
246 
247       if (p[len] != '\0')
248         p += len + 1;
249     }
250 }

In the initial loop of “while (true)” within parse_tunables() (at lines 221-235), the entire “tunable1=tunable2=AAA” gets copied directly into tunestr, effectively filling it up. Moving to lines 247-248, p remains unchanged (as p[len] is ‘\0’ due to the absence of ‘:’ at lines 203-204), hence still referencing the value of “tunable1,” specifically “tunable2=AAA.” As the second loop of “while (true)” in parse_tunables() executes, “tunable2=AAA” gets added (as if it were another parameter) to tunestr, which is already at maximum capacity, resulting in a tunestr overflow. This can be used to invoke a SUID binary which will then, if we’re lucky, invoke the shell code we’ve overflowed onto the stack.

Attacker Value & Exploitability

There were a wide range of linux distributions that shipped with the affected glibc library. However to exploit each distribution you need to determine a specific “magic” offset in order for the buffer overflow to result in RCE. The original PoC shipped with tooling for users to determine this magic offset when ASLR was disabled, however it wasn’t working for Fedora, RedHat, Amazon Linux, Gentoo and a couple others I tested. Due to the low level complexity of the vuln the PoC likely needed some massaging in order to exploit those distros.

Because ASLR is enabled by default, you need to loop over the exploit many times in order for it to be successful. You have approximately a 1/2700 chance of the exploit being successful every time you attempt to exploit it. You can easily loop over exploit attempts quickly so that part doesn’t affect exploitability too much though does add a significant wait time for the exploit to be successful.

By default Ubuntu patches this vuln automatically as a part of the Automatic Security Updates, so you do have to go out of your way to set up a vulnerable instance.

1
Ratings
  • Attacker Value
    High
  • Exploitability
    High
CVSS V3 Severity and Metrics
Base Score:
7.8 High
Impact Score:
5.9
Exploitability Score:
1.8
Vector:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Attack Vector (AV):
Local
Attack Complexity (AC):
Low
Privileges Required (PR):
Low
User Interaction (UI):
None
Scope (S):
Unchanged
Confidentiality (C):
High
Integrity (I):
High
Availability (A):
High

General Information

Vendors

  • canonical,
  • debian,
  • fedoraproject,
  • gnu,
  • redhat

Products

  • codeready linux builder eus 8.6,
  • codeready linux builder for arm64 eus 8.6,
  • codeready linux builder for ibm z systems eus 8.6,
  • codeready linux builder for power little endian eus 8.6,
  • debian linux 12.0,
  • debian linux 13.0,
  • enterprise linux 8.0,
  • enterprise linux 9.0,
  • enterprise linux eus 8.6,
  • enterprise linux for arm 64 eus 8.6 aarch64,
  • enterprise linux for ibm z systems eus s390x 8.6,
  • enterprise linux for power big endian eus 8.6 ppc64le,
  • enterprise linux server aus 8.6,
  • enterprise linux server tus 8.6,
  • fedora 37,
  • fedora 38,
  • fedora 39,
  • glibc,
  • ubuntu linux 22.04,
  • ubuntu linux 23.04,
  • virtualization 4.0,
  • virtualization host 4.0

Exploited in the Wild

Reported by:

References

Miscellaneous

Additional Info

Technical Analysis