此前一直只在用户态利用的角度下看 glibc,实际上 ld.so 也存在提权的可能 —— 借助有 suid 程序,如 su。
Environment
libc | GLIBC 2.35-0ubuntu3.3 |
---|
debugOS | NixOS 23.11 |
victimOS | ubuntu 22.04 |
Analysis
这不是第一个由环境变量导致的提权漏洞,同样是该漏洞的发现团队 qualys 早在 2021 年就利用 pkexec 的溢出漏洞与环境变量的覆写实现了本地提权(CVE-2021-4034)。
回到这个漏洞,glibc-2.34 在 2021 年的四月引入了由环境变量 GLIBC_TUNABLES
控制的可变参数,并在处理函数 parse_tunables
中出现了溢出漏洞,调试时函数调用栈为:
先考虑正常形如 GLIBC_TUNABLES=tunable1=easxuelian:tunable2=nana7mi
的环境变量,以等号形成键值对并以冒号分割,会先由 _dl_start
读到栈上并在 __tunables_init
函数中被 get_next_env
取出判断,若满足条件则进入到 parse_tunables
函数中做进一步处理:
其中 tunables_strdup
调用了 __minimal_malloc
来申请一块大小为 envname + 1
的空间存放新的环境变量,envval 则仍然指向栈上的环境变量,作为参数进入 parse_tunables
函数:
这个函数中用一个 while(true)
循环来处理 GLIBC_TUNABLES
的值,其中又有一个 for 循环来判断当前 tunable 的合法性,若检查通过则会向 __minimal_malloc
得到的 tunestr
空间进行赋值,但是这里没有考虑诸如 tunable1=tunable2=AAA
的情况,此时:
-
第一轮循环中,L226-229 用匹配到的变量名填入 tunestr,L231-234 将变量值继续填入 tunestr,由 strdup 得到的空间正好已经被填满了;
-
但是此时 p[len]
正等于 ‘\0’,即指向变量的末尾而非 ’:‘,即 p 不会进入递增,现在 p 指向 tunable2=AAA
,若这也是一个合法的 tunables 环境变量,它就会继续被接到 tunestr 后,形成可控的任意长度溢出;
在调试漏洞的时候,可以关注:
__minimal_malloc
的相关变量:
alloc_last_block
:上一块分配的区域;
alloc_ptr
:下次分配的起始地址;
Exploitation
这个利用也是很有趣的,提权的关键就是溢出覆写 l_info[DT_RUNPATH]
(29)或者 l_info[DT_RPATH]
(15)的值使其最终指向预先布置好的地址,进而通过动态链接的 elf 调库偏移利用内存中已有的字符找到在当前目录下构造的恶意路径内含有 shellcode 的 libc,关闭 aslr 进行调试得到如下提权 poc:
为了能稳定利用,增大 SPARY_SIZE 到 0x20000 即可($ARG_MAX
),在比赛中已经有了提权成功的情况,平均 1000 次以内就能成功,还是非常稳定迅速的。
References
[1] Looney Tunables: Local Privilege Escalation in the glibc’s ld.so . Qualys
[2] CVE-2023-4911 - Looney Tunables . RickdeJager
[3] PoC of CVE-2023-4911 “Looney Tunables” . leesh3288