引言
2022年6月的一个夜晚,Ethan向团队发出询问:“有人知道如何在Linux上欺骗Auditd吗?我正在研究如何修改auid(审计用户ID)字段。即使用户使用su或sudo切换身份,这个字段也保持不变(其他用户ID字段会跟踪这些变化)。”
Ethan还贴心地提供了参考链接说明他正在研究的内容。虽然我对Auditd的了解程度不足以保证能独立解决这个问题,但作为Linux的忠实爱好者(兼傻瓜),我决定加入研究。
Ethan的原始目标是:SOC团队正在构建一个"金丝雀"脚本,用于欺骗特定auid,并确定在编写Linux系统检测规则时auid值是否可靠。如果auid字段能在用户切换身份或提权时保持跟踪,那么修改该字段的能力对攻击者可能极具价值。
基础概念
首先需要明确:auid字段是什么?如何访问?最关键的是如何修改?
根据Ethan提供的文档,auid字段源自uid(用户标识符)字段,旨在跨进程子级保持一致,即使子进程的uid被修改。上下文说明:uid为0代表root用户,1000以下为系统账户,1000及以上为普通用户账户。用户映射信息存储在/etc/passwd文件中,也可通过getent passwd命令查看。
内核源码分析
现在我有理由做最喜欢的事:查看内核代码。使用elixir.bootlin.com浏览内核源码,首先寻找auid字段的使用位置、初始设置位置和可能修改位置。
搜索结果显示,auid字段的日志记录始于/kernel/audit.c第1600行(内核版本6.0.19)。auid字段通过函数调用audit_getloginuid(current)获取。
审计日志中的auid字段位置
了解了日志服务如何获取auid字段后,接下来查找audit_get_loginuid函数。该函数在审计头文件/include/linux/audit.h中定义和实现。从第199行开始的函数实现显示,该函数接受指向任务结构(task_struct)的指针,并返回其中的loginuid字段。
audit_get_loginuid函数内容
我们接近底层了,现在需要在该任务结构定义中找到该字段。任务结构位于/include/linux/sched.h,用于包含进程信息。loginuid字段被定义为结构字段,但仅在定义CONFIG_AUDIT时有效——这很合理,如果系统未配置审计,就不需要loginuid字段(及相关字段)。
审计专用的loginuid任务结构字段
基于此,我开始构思写入该字段的方法。考虑到任务背景,内核补丁甚至可加载内核模块都显得过于硬核,因此决定尝试在用户空间解决问题。遗憾的是,内核源码探索之旅到此结束。
在深入了解auid字段后,我开始寻找修改方法,最终在GitHub提交记录中找到了详细说明字段修改条件和方法的资料。
说明登录UID可变性的Auditd提交记录
综合该提交记录的信息,只要权限有效、设置CAP_AUDIT_CONTROL Linux能力,且未设置AUDIT_FEATURE_LOGINUID_IMMUTABLE和AUDIT_FEATURE_ONLY_UNSET_LOGINUID内核配置选项,当前进程的auid字段应可通过/proc/self/loginuid进行修改。掌握这些信息后,我们已准备好开始解决问题。
实战:Now You Auditd Me
初始的内核探索具有启发性,但整体缺乏上下文。为获取上下文,我们查看审计条目的记录方式。以下内容经过轻微修改,省略了不相关的周围日志条目,使输出更清晰易读。
日志文件信息可在/var/log/audit/audit.log中找到(至少在RHEL系统上)。我先运行sudo ls,然后打印日志文件最后六行。可见记录的可执行文件是/usr/bin/sudo,相关命令是ls,这与我们刚运行的内容一致。
具有一致UID和AUID的Sudo使用记录
那么运行sudo su时会记录什么?正如预期,审计日志显示了命令执行的条目。往下几条记录可见uid字段已更新为0(root),但auid字段仍保持原用户值。
UID改变但AUID一致的Sudo使用记录
至此我们明确了任务:创建一个简单程序,在拥有sudo权限的前提下,允许通过/proc/$pid/loginuid伪文件手动设置auid字段。
实战:Now You Auditdon’t
充分研究任务后,先从简单思路开始。如果能写入伪文件,是否还需要编程?虽然我喜欢用代码解决问题,但这原本不是我的问题,因此我会谨慎推进。
简单方法? 首先,直接写入文件会发生什么?我们应该有写入自身进程的必要权限吧?
写入操作被拒绝
显然不行。这虽然不是权限拒绝错误(表示访问错误),但我们暂时搁置。如果使用echo新值然后以sudo身份写入文件内容呢?
失败的写入尝试
这次没有错误,但仍然失败。
又一次失败的写入尝试
快速说明:你可能想知道在使用sudo时,每个命令和用户上下文生成不同进程的情况下,/proc/self是否可靠。根据测试,使用当前终端特定PID时行为相同。
更有趣的是,root用户可以修改自己的loginuid文件,但不能修改其他用户进程拥有的文件。Ethan也确认尝试给cat或tee赋予CAP_AUDIT_CONTROL能力并未奏效。所有这些表明,我最终还是需要用代码解决问题。
至此,终于可以开始编写代码了。下一节有个有趣的"饮酒游戏":每次读到"能力"或"capabilities"时小口喝水,确保保持水分充足……
硬核趣味方法! 编码前快速说明:继续之前需确保系统已安装libcap-dev(Debian)或libcap-devel(RHEL)包,否则缺少必要的头文件。
除初步参数验证外,我编写的代码主要包含以下五个核心组件,其中几个主要涉及错误检测和异常时短路处理:
- 获取进程能力,确保支持CAP_AUDIT_CONTROL
- 设置进程能力以启用CAP_AUDIT_CONTROL能力
- 将新auid字段写入/proc/self/loginuid伪文件
- 验证新auid字段是否正确写入
- 生成shell
由于修改auid字段需要特定能力,第一步是检索进程能力并确定CAP_AUDIT_CONTROL能力是否在进程中启用。如何实现?在没有立即熟悉能力编程的情况下,我在终端运行apropos capability,得到了一些结果。
搜索相关手册页
检查其中一个结果的手册页,可见大多数都指向头文件sys/capabilities.h。cap_clear页面的"参见"部分引用了cap_get_proc,这是我最终用于检索进程能力的函数。关键注意:包含sys/capabilities.h需要编译时使用-lcaps链接能力库。
知道了检索进程能力的方法后,检查系统是否支持CAP_AUDIT_CONTROL能力会很有用。方便的是,cap_get_proc的手册页包含一个名为CAP_IS_SUPPORTED的宏。手册页描述如下:“CAP_IS_SUPPORTED(cap_value_t cap)在系统支持指定能力cap时评估为真(1)。如果系统不支持该能力,函数返回0。”
掌握这些信息后,我们可以构建程序的第一个主要部分:将进程能力存储在名为caps的变量中,并检查CAP_AUDIT_CONTROL支持。
片段1 – 能力检索与枚举
此时代码已高度确信所需能力至少受系统支持。下一步涉及创建包含CAP_AUDIT_CONTROL的新能力列表,使用cap_set_flag()函数启用它,然后使用cap_set_proc()函数将更新后的能力写入进程。
片段2 – 能力构建与写入
能力写入后,是时候获取新auid值并写入/proc/self/loginuid了。为此,我们使用标准文件I/O函数打开文件并将第一个也是唯一一个命令参数写入文件。
片段3 – 文件打开与新AUID写入
你可能注意到我们在上一片段未关闭文件。这是因为下一片段涉及从文件读回新值以确保成功写入。此步骤非必需,但鉴于之前修改值遇到的困难,我认为谨慎总比后悔好。验证值正确设置后,关闭文件并使用cap_free()函数释放为caps变量分配的内存。
片段4 – 文件内容验证与内存管理
最后,使用execve()函数生成简单的/bin/bash shell。虽不华丽也不特别安全,但能完成任务。
片段5 – Shell生成
代码基本就是这些。都明白了吗?很好。
演示
使用gcc -lcaps capybara.c -o capybara编译代码后,通过以下方式验证其工作:
成功修改AUID字段
好吧,这还不错,但更新进程看到的数字只是我们任务的前半部分。我们真正的目的是更改记录到审计文件的值!
再次运行代码并执行sudo whoami(用于演示)后,可以输出/var/log/audit/audit.log的部分内容,并添加俏皮的Perl正则表达式增加趣味。查看两个高亮审计日志行的第一个,我发现了最初与Ethan开发时未注意到的内容:存在old-auid字段!该字段仅在字段被修改时出现。第二个高亮日志文件条目显示,whoami命令以"正确"的auid值"12345"执行,未提及旧auid值。
显示old-auid字段的解析后Auditd日志条目
因此,分析员检测Auditd篡改的途径是:检查变化的old-auid字段以跟踪可能变化的用户ID。但Ethan后来向我提到,该特定环境的SIEM未呈现LOGIN事件,这给日志可见性问题增加了另一个难点。
结论
是时候用一些离别思考来总结本文了。
在检测方面,确保SIEM从Auditd接收LOGIN事件,以便感知任何篡改。在可能的修复方面,请注意前面展示的GitHub提交还讨论了名为AUDIT_FEATURE_LOGINUID_IMMUTABLE和AUDIT_FEATURE_ONLY_UNSET_LOGINUID的配置选项,分别防止auid字段的覆盖和取消设置。从企业Linux角度看,启用这些配置是不错的选择。但我在Linux 6.1.3等最新内核版本的配置选项中,初步搜索未能找到这两个配置选项。
显然,存在auditctl命令可设置内核不可变性(-e 2)和常规loginuid不可变性(–login-immutability),但我至今未能使任一命令成功阻止此技术生效。
总而言之,这项工作并不代表突破性发现,但似乎为Ethan和SOC团队其他成员带来了满意结果。更重要的是,这应作为编写围绕Auditd设施的检测规则时需谨慎的理由。根据组织编写检测规则或监控审计信息的方式,攻击者可能利用类似策略在未配备检测变化auid值能力的环境中 effectively 消失。