PathAuditor 是一个通过审计 libc 函数来发现文件访问相关漏洞的工具。
其思路大致如下:
我们使用 LD_PRELOAD 来钩住所有与文件系统相关的库调用,并将遇到的任何违规记录到 syslog。
这不是谷歌官方支持的产品。
让我们看看这个工具可以检测的一种漏洞示例。CVE-2019-3461 是 tmpreaper 中的一个 bug,tmpreaper 是一个遍历 /tmp 并删除旧文件的工具。它通常作为 root 用户的 cron 任务运行。由于它不想删除 /tmp 之外的文件,它使用以下代码来检查一个目录是否为挂载点:
if (S_ISDIR (sb.st_mode)) {
char *dst;
if ((dst = malloc(strlen(ent->d_name) + 3)) == NULL)
message (LOG_FATAL, "malloc failed.\n");
strcpy(dst, ent->d_name);
strcat(dst, "/X");
rename(ent->d_name, dst);
if (errno == EXDEV) {
free(dst);
message (LOG_VERBOSE,
"File on different device skipped: `%s/%s'\n",
dirname, ent->d_name);
continue;
}
// [...]
简而言之,此代码调用 rename("/tmp/foo", "/tmp/foo/x"),如果 "/tmp/foo" 是一个挂载点,它将返回 EXDEV。如果 "/tmp/foo" 由除 root 之外的任何用户拥有,PathAuditor 会将此调用标记为潜在漏洞。要理解原因,我们必须考虑内核在执行 rename 系统调用时发生了什么(简化版):
"/tmp/foo"。"/tmp/foo/x"。这里存在一个竞争条件,因为 "/tmp/foo" 将被解析两次。如果它由用户控制,用户可以在任何时候用不同的文件替换它。
特别地,我们希望 "/tmp/foo" 首先是一个目录,以通过 tmpreaper 代码中的 if(S_ISDIR) 检查。然后我们在代码进入系统调用之前将其替换为一个文件。当内核解析第一个参数时,它将看到一个带有用户控制内容的文件。
现在我们再次替换它,这次用指向同一文件系统上任意目录的符号链接替换。内核将第二次解析路径,跟随符号链接,并将受控文件移动到选定的目录。
相同文件系统的限制是因为 rename 不能在文件系统之间工作。但在某些 Linux 发行版上,/tmp 默认只是根文件系统上的一个文件夹,你可以利用这个 bug 将文件移动到 /etc/cron,该文件将以 root 身份执行。
要试用它,你需要使用 bazel 构建 libpath_auditor.so,并使用 LD_PRELOAD 将其加载到二进制程序中。任何违规都会被记录到 syslog,因此请确保 syslog 正在运行。
bazel build //pathauditor/libc:libpath_auditor.so
LD_PRELOAD=/path/to/bazel-bin/pathauditor/libc/libpath_auditor.so cat /tmp/foo/bar
tail /var/log/syslog
也可以通过将其添加到 /etc/ld.so.preload 来在所有系统进程上运行。但请注意,仅建议在测试系统上这样做,因为它可能导致不稳定。
作为快速入门,你可以尝试本项目附带的 Docker 容器:
docker build -t pathauditor-example .
docker run -it pathauditor-example
# LD_PRELOAD=/pathauditor/bazel-bin/pathauditor/libc/libpath_auditor.so cat /tmp/foo/bar
# cat /var/log/syslog