CVE-2019-19726 OpenBSD dynamic loader Local Privilege Escalation Vulnerability
0x01. 漏洞介绍
CVE-2019-19726 是 OpenBSD dynamic loader 在清理 LD_LIBRARY_PATH 环境变量时存在的一个本地提权漏洞,由 Qualys Research Team 发现。该漏洞获得 2020 年 Pwnie Awards Best Privilege Escalation Bug 提名。漏洞补丁可参考 libexec/ld.so/loader.c。
0x02. 漏洞分析
2.1 setrlimit / RLIMIT_DATA
int getrlimit(int resource, struct rlimit *rlim); |
The getrlimit() and setrlimit() system calls get and set resource limits respectively. Each resource has an associated soft and hard limit, as defined by the rlimit structure:
struct rlimit { |
The soft limit is the value that the kernel enforces for the corresponding resource. The hard limit acts as a ceiling for the soft limit: an unprivileged process may only set its soft limit to a value in the range from 0 up to the hard limit, and (irreversibly) lower its hard limit. A privileged process (under Linux: one with the CAP_SYS_RESOURCE capability) may make arbitrary changes to either limit value.
The value RLIM_INFINITY denotes no limit on a resource (both in the structure returned by getrlimit() and in the structure passed to setrlimit()).
简单来说,进程可以通过 setrlimit
来限制自身的资源使用上限:
- 对于普通进程而言,soft limit 只能位于区间
[0, hard limit]
,而 hard limit 则只能进行下调操作 - 对于特权进程而言,soft limit 可以随意设置
RLIM_INFINITY
表示没有任何限制
第一个参数 resource
表示资源类型,RLIMIT_DATA
表示内存资源。
RLIMIT_DATA
The maximum size of the process’s data segment (initialized data, uninitialized data, and heap). This limit affects calls to brk(2) and sbrk(2), which fail with the error ENOMEM upon encountering the soft limit of this resource.
测试代码:
|
测试结果:
./a.out |
2.2 ARG_MAX
ARG_MAX 定义于 limits.h
头文件中,表示程序命令行参数(包含环境变量参数)的最大大小。
ARG_MAX
Maximum length of argument to the exec functions including environment data.
Minimum Acceptable Value: {_POSIX_ARG_MAX}_POSIX_ARG_MAX
Maximum length of argument to the exec functions including environment data.
Value: 4096
2.3 CVE-2019-19726
对于 SUID-root 程序,链接器 ld.so
需要去除危险的环境变量(比如 LD_LIBRARY_PATH
等),以防止通过环境变量来实现提权。
比如,glibc 中的 _dl_non_dynamic_init
函数负责清理此类危险的环境变量:
void |
UNSECURE_ENVVARS
的定义如下:
/* Environment variable to be removed for SUID programs. The names are |
FreeBSD 相关处理代码如下(参考 src/libexec/ld.so/loader.c):
/* |
对 LD_LIBRARY_PATH
的处理有点特殊,额外调用了 _dl_split_path
函数,对应的代码如下(参考 src/libexec/ld.so/path.c):
char ** |
这里调用 _dl_reallocarray
分配内存,如果分配失败,_dl_split_path
将返回 NULL
。
回到 _dl_setup_env
函数,可以发现当 _dl_split_path
返回 NULL
时,环境变量 LD_LIBRARY_PATH
将不会被清理,此时可以通过 so 动态库加载劫持来实现 root 提权。
_dl_libpath = _dl_split_path(_dl_getenv("LD_LIBRARY_PATH", envp)); |
0x03. 漏洞利用
原作者挑选了 /usr/bin/chpass
来实现漏洞利用,其 main
函数做了以下操作:
- 调用
setuid(0)
- 调用
pw_init
把RLIMIT_DATA
重置成了RLIM_INFINITY
- 调用
pw_mkdb
通过vfork / execv
执行/usr/sbin/pwd_mkdb
,其中execv
会继承环境变量
当 /usr/sbin/pwd_mkdb
执行时,会重新触发 ld.so
中的逻辑,但由于此时已经没有了 RLIMIT_DATA
资源限制,以下代码会成功执行:
_dl_libpath = _dl_split_path(_dl_getenv("LD_LIBRARY_PATH", envp)); |
而由于 /usr/sbin/pwd_mkdb
并不是 SUID-root 程序,LD_LIBRARY_PATH
不会被清理,因此最终可以通过 so 动态库加载劫持来实现 root 提权。
另外需要注意的是,对 /usr/bin/chpass
而言,由于 _dl_libpath
为 NULL
,所以 LD_LIBRARY_PATH
对 chpass
本身是不会起作用的。
综上,应该只有极少数刚好满足条件的 SUID-root 程序,才可以完成这个提权漏洞的利用。
0x04. References
- Local Privilege Escalation in OpenBSD’s dynamic loader (CVE-2019-19726) / Wayback Machine
- https://pwnies.com/qualys-security-advisory-team-2/
- https://github.com/openbsd/src/commit/eee3c75f9abd5ea51e066dd0fe6b1efa470e4d0c
- https://linux.die.net/man/2/setrlimit
- https://pubs.opengroup.org/onlinepubs/009695399/basedefs/limits.h.html
- https://codebrowser.dev/glibc/glibc/elf/dl-support.c.html
- https://codebrowser.dev/glibc/glibc/sysdeps/generic/unsecvars.h.html