Detect Exploitation Attempts of the Looney Tunables Vulnerability in Your System
Once again, Qualys strikes gold with a great bug!
I am talking, of course, about no other than the recently disclosed Linux vulnerability, CVE-2023-4911, also known as Looney Tunables, found in the GNU C library’s dynamic loader.
In a world where genuine high-impact vulnerabilities often get lost amidst “undisclosed vulnerability in X advisories”, Qualys consistently stands out. Their latest find was yet another monumental bug, accompanied by a meticulously detailed PoC — truly a testament to their dedication.
Thanks to advisories like these, blue, red, and purple teams can excel at their roles.
The specifics of the vulnerability itself have already been covered in a number of resources with all the needed levels of details, including the original oss-security post, which is more instructive than one might expect. While it’s unnecessary to delve into the specifics in this post, I want to highlight a couple of key takeaways:
- The vulnerability lives in the GNU C Library’s dynamic loader and can be leveraged with most SUID binaries.
- Although it was introduced fairly recently (April 2021), Fedora 37/38, Ubuntu 22.04/23.04 and Debian 12/13 are vulnerable.
But maybe the most interesting thing to me is the following sentence included in the oss-security post:
“Although we discovered this buffer overflow manually, … both AFL++ and libFuzzer re-disOpenwall mailing listcovered this overflow in less than a second”
For those unaware, AFL++ and libFuzzer are two open source and well-known fuzzers, tools designed to unearth bugs by manipulating program inputs in other programs by fuzzing (or altering) inputs.
From the same oss-securiry post: “The dynamic loader is extremely security sensitive because its code runs with elevated privileges when a local user executes a set-user-ID
program, a set-group-ID
program, or a program with capabilities.”
Given that the vulnerability relies on fairly recently introduced code, I think it tells a lot about the progression margin that exists in the automatic software for such a critical piece of code that is known to have been a source of bugs in the past.
“Historically, the processing of environment variables such as LD_PRELOAD
, LD_AUDIT
, and LD_LIBRARY_PATH
have been a fertile source of vulnerabilities in the dynamic loader.”
But the journey doesn’t end at discovery. With the PoC in hand, the real test is to detect the exploitation or probing of the vulnerability.
env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=`printf '%08192x' 1`" /usr/bin/su --help
Segmentation fault (core dumped)
We are assuming, based on circulating exploits and the initial advisory, that the attacker trying to exploit this vulnerability will likely crash the binary at least once before succeeding in exploiting the vulnerability.
It might sound bold, but given this is a local vulnerability, there is less incentive for the attacker (unless they actually want to be stealth 😅) to have a “one-shot” exploit, given they can retry it indefinitely at little to no cost.
The initial advisory lets us think that brute-forcing addresses is implied in the exploitation method (look for the paragraph around “Where should the overwritten l_info[DT_RPATH]
point to?” in the oss-security post), reinforcing the idea that crashes will be observed before successful exploitation.
We’ll be keeping an eye out for when a public exploit lands and see how things turn out. And yes, we do bet on attackers’ sloppiness!
To address the exploitation threats for the Looney Tunables vulnerability, we put together two scenarios for the CrowdSec Security Engine, which can notify you when someone is attempting to exploit Looney Tunables in your system. The CVE-2023-4911 scenario offers a more targeted approach and relies on default Linx kernel logs. The auditd-suid-crash scenario offers a more generic approach and is based on auditd.
If you are not familiar with CrowdSec and the Security Engine, I encourage you to take our CrowdSec Fundamentals cource or check out our documentation.
Detecting exploitation via kernel logs
Let’s first have a look at the kernel logs.
Oct 05 09:15:54 sd-xxxxxx kernel: su[198452]: segfault at 7f4134f2f000 ip 00007f4134f0ed7d sp 00007ffebd252cb0 error 6 in ld-linux-x86-64.so.2[7f4134efc000+25000] likely on CPU 0 (core 0, socket 0)
Oct 05 09:15:54 sd-xxxxxx kernel: Code: c0 0f 85 24 01 00 00 4c 89 f0 b9 01 00 00 00 45 84 c9 0f 84 40 01 00 00 48 8b 44 24 18 4c 89 e6 48 29 c6 48 89 c1 48 83 c0 01 [45] 88 4c 06 ff 44 0f b6 0c 06 45 84 c9 75 ea 48 8d 71 02 4c 01 f0
The logs tell us that the su binary (su[198452]
) crashed with signal 11 (`segfault` at 7f…) in ld-linux-x86-64.so.2
. It will be very specific to this vulnerability, but having your dynamic linker/loader segfaulting is bad news… I guess?
The CrowdSec scenario itself is very straightforward and doesn’t need much explanation.
type: trigger
name: crowdsecurity/CVE-2023-4911
description: "Trigger on suspicious segfault"
filter: "evt.Meta.log_type == 'kernel' && evt.Meta.sub_log_type == 'segfault' && evt.Meta.library startsWith 'ld-linux-'"
blackhole: 1m
labels:
remediation: false
scope:
type: exe
expression: evt.Meta.binary
The scenario is available in the CrowdSec Hub as part of the LPE collection, which can be installed using this command:
cscli collections install crowdsecurity/linux-lpe
Make sure to have kernel logs in your acquisition. If you’re using pre-journalctl
distribution:
filenames:
- /var/log/kern.log
labels:
type: syslog
Or if you are using journalctl
system:
journalctl_filter:
- "-k"
labels:
type: syslog
With this scenario alone, we will be able to detect attempted exploitation of the CVE-2023-4911 on any Linux machine. Isn’t that great?
Detecting exploitation via auditd logs
Crowdsec already supports auditd, and the collection includes various scenarios targeting post-exploitation behavior, so this privilege escalation should be a great addition.
For those less familiar with auditd, it is the userspace component of the Linux Auditing System and it is present on most distributions, or can be easily installed). It needs to be configured to enable logging of specific events, and if you are lost you can refer to Florian Roth’s Auditd Best Practice. It is actively maintained and covers most use cases (although quite verbose).
That being said, let’s have a look at the auditd log generated when we’re executing the PoC:
Oct 05 09:15:54 sd-xxxxxx audit[198452]: SYSCALL arch=c000003e syscall=59 success=yes exit=0 a0=7fffe66ad66e a1=7fffe66aa298 a2=559408873a60 a3=7f8f214aee50 items=3 ppid=192010 pid=198452 auid=1000 uid=1000 gid=1000 euid=0 suid=0 fsuid=0 egid=1000 sgid=1000 fsgid=1000 tty=pts0 ses=417 comm="su" exe="/usr/bin/su" subj=unconfined key="priv_esc"
Oct 05 09:15:54 sd-xxxxxx audit: EXECVE argc=2 a0="/usr/bin/su" a1="--help"
Oct 05 09:15:54 sd-xxxxxx audit: PATH item=0 name="/usr/bin/su" inode=19796169 dev=08:02 mode=0104755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
Oct 05 09:15:54 sd-xxxxxx audit: PATH item=1 name="/usr/bin/su" inode=19796169 dev=08:02 mode=0104755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
Oct 05 09:15:54 sd-xxxxxx audit: PATH item=2 name="/lib64/ld-linux-x86-64.so.2" inode=19793507 dev=08:02 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
Oct 05 09:15:54 sd-xxxxxx audit: PROCTITLE proctitle=2F7573722F62696E2F7375002D2D68656C70
Oct 05 09:15:54 sd-125732 audit[198452]: ANOM_ABEND auid=1000 uid=1000 gid=1000 ses=417 subj=unconfined pid=198452 comm="su" exe="/usr/bin/su" sig=11 res=1
The interesting parts are:
SYSCALL … syscall=59 … auid=1000 … euid=0
: This line allows us to see that an EXECVE syscall (program execution) was done, and that the audit user ID (loginUID
,auid=1000
) is different from the effective user ID (euid=0
). In layman’s terms, it means that someone executed a suid binary, or executions via sudo.ANOM_ABEND … sig=11
: This line means that a process crashed with signal 11, the famous SIGSEGV, segmentation fault.
With those two lines, we can detect that a SUID process was invoked, and later crashed or was terminated abnormally. While this scenario is a bit more complex than the one matching kernel logs, it has the advantage of being more generic, potentially allowing detection of exploitation attempts on any privileged binary.
The scenario looks like this:
type: conditional
name: crowdsecurity/auditd-suid-crash
description: "Detect root suid process crashing"
filter: |
(evt.Meta.log_type == 'execve' && evt.Meta.euid == '0' && evt.Meta.auid != '0') ||
(evt.Meta.log_type == 'anom_abend' && evt.Meta.sig in ["4", "5", "6", "7", "11"])
groupby: evt.Meta.pid
condition: |
len(queue.Queue) >= 2 and
queue.Queue[0].Meta.exe == queue.Queue[1].Meta.exe
leakspeed: 1s
capacity: -1
blackhole: 1m
labels:
service: linux
type: exploitation
remediation: false
scope:
type: exe
expression: evt.Meta.exe
With this scenario, we’re looking for:
- Execution of privileged (
suid
) binaries (evt.Meta.log_type == 'execve`
…) - Crashes (
evt.Meta.log_type == 'anom_abend'
…)
We’re then grouping them by PID and ensuring that it’s the same binary that is involved in both, which effectively allows us to detect a SUID binary that later crashes.
The scenario is part of the auditd collection, so it can installed like this:
cscli collections install crowdsecurity/auditd
And for it to work, ensure that CrowdSec is configured to read auditd logs:
filenames:
- /var/log/audit/*.log
labels:
type: auditd
Final testing
With both our scenarios enabled, let’s play with the PoC again and check how our alerts look like!
env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=`printf '%08192x' 1`" /usr/bin/su --help
Segmentation fault (core dumped)
On the CrowdSec side, we see our two alerts (I have both scenarios on this machine, and auditd enabled):
To make it even more useful, you can configure alert context to push the following contextual data to the CrowdSec Console:
cscli lapi context add --value evt.Meta.uid --value evt.Meta.euid --value evt.Meta.ppid --value evt.Meta.parent_progname --value evt.Meta.str_UID --value evt.Meta.binary
And on the Console, our alerts with context are looking nice and clean!
Closing thoughts
As I’m finalizing this article, functional exploits are already out and seem to validate our hypothesis that brute force is the most prevalent way to exploit the Looney Tunables vulnerability. The good news is that by using CrowdSec, you are in a solid position to detect and possibly thwart such exploits.
Struggling with these scenarios? Don’t fret. Hop onto our Discord and let’s troubleshoot together! 😃