解释器类应用程序安全防护
背景介绍
业界主要使用Linux IMA机制对系统运行的程序发起完整性检查,一方面可以检测文件是否被篡改,另一方面通过IMA的白名单机制可以保证只有经过认证(如签名或HMAC)的文件才可以被运行。
Linux IMA机制支持对通过read()、exec()、mmap()等系统调用访问的文件进行完整性度量/评估。尽管从功能上而言,IMA能够配置对解释器运行脚本所采用的read()系统调用的完整性保护功能,但是在实际场景中,IMA摘要列表主要配置为针对exec()、mmap()进行拦截,而无法有效拦截未授权的恶意脚本执行。原因IMA无法将脚本文件和其他可变的数据文件进行区分。一旦针对read()系统调用配置完整性保护,则会将其他可变的配置文件、临时文件、数据文件等纳入保护范围,而这些文件无法预先生成基线或认证凭据,从而导致完整性检查失败。因此实际场景无法配置基于read()的度量/评估策略,无法针对性地实现拦截和保护:
执行方式 | 实际应用场景下能否通过IMA保护 |
---|---|
./test.sh | 是 |
bash test.sh | 否 |
特性介绍
本特性旨在通过内核系统调用保证通过执行方式运行脚本文件(如./test.sh)和通过解释器运行脚本文件(bash ./test.sh)具备相同的权限检查流程,具体说明如下:
execveat()支持AT_CHECK检查参数
execveat()是于Linux 3.19/glibc 2.34版本开始支持的系统调用函数,该函数允许传入一个已打开的文件描述符并执行该文件。本特性针对execveat()系统调用扩展AT_CHECK检查机制,实现对某个文件是否可执行进行检查操作,而不真正地执行该文件。
当调用者通过execveat()系统调用函数,传入目标文件描述符并指定AT_CHECK参数时,在内核的系统调用执行逻辑中,首先针对传入的文件描述符进行权限检查,该流程与普通的文件执行流程一致,包含文件的DAC权限位、LSM访问控制规则、IMA等检查,如果检查不通过则退出并返回错误码-EACCSS。直到所有权限检查流程完成后,execveat()系统调用判断参数是否包含AT_CHECK,如果包含,则不执行后续的执行流程,退出并返回0,表示该文件的可执行权限检查通过。
解释器支持调用execveat对程序进行权限检查
在支持基于execveat()的AT_CHECK检查机制的基础上,当解释器打开待运行的脚本后,可主动调用execveat()系统调用函数发起对文件进行可执行权限检查,只有当权限检查成功后,才可继续运行脚本文件。
接口介绍
系统调用接口说明
execveat()系统调用的函数类型为:
int execveat(int dirfd, const char *pathname,
char *const _Nullable argv[],
char *const _Nullable envp[],
int flags);
本特性涉及新增flags参数AT_CHECK,说明如下:
参数 | 取值 | 说明 |
---|---|---|
AT_CHECK | 0x10000 | 对目标文件进行可执行权限检查,而不真正地执行该文件 |
内核启动参数说明
本特性支持如下内核启动参数:
参数 | 取值 | 说明 |
---|---|---|
exec_check.bash= | 0/1 | 默认为0,设置为1时,bash解释器进程运行脚本时前调用execveat进行脚本文件的可执行权限检查 |
exec_check.java= | 0/1 | 默认为0,设置为1时,jdk运行脚本时class和jar文件时,需要调用execveat进行脚本文件的执行权限检查 |
exec_check. | 0/1 | 后续可扩展其他解释器 |
注意:上述启动参数实际由各个解释器进程进行读取解析,内核并不实际使用这些参数。
特性范围
本特性于openEuler 24.03 LTS SP1(6.6内核)版本支持,需要内核版本。特性支持的解释器类型如下:
解释器 | 目标文件 | 说明 |
---|---|---|
bash | shell脚本文件 | bash进程对打开的shell文件进行可执行权限检查 |
jdk | class文件/jar文件 | java虚拟机对加载的class文件和jar包进行可执行权限检查 |
社区开发人员或用户可基于该机制,自行扩展其他解释器或类似机制的支持。
使用说明
AT_CHECK参数使用示例
前置条件
内核版本大于6.6.0-54.0.0.58,glibc版本大于等于2.38-41。
glibc-2.38-41.oe2403sp1.x86_64
kernel-6.6.0-54.0.0.58.oe2403sp1.x86_64
操作指导
可编写如下测试程序(test.c)进行参数功能测试:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define AT_CHECK 0x10000
int main(void)
{
int fd;
int access_ret;
fd = open("./", O_RDONLY);
access_ret = execveat(fd, "test.sh", NULL, NULL, AT_CHECK);
perror("execveat");
printf("access_ret = %d\n", access_ret);
close(fd);
return 0;
}
步骤1: 编译测试代码:
gcc test.c -o test
步骤2: 创建测试脚本test.sh:
echo "sleep 10" > test.sh
步骤3: 如果测试脚本具备合法的可执行权限,则execveat返回0:
# chmod +x test.sh
# ./test
execveat: Success
access_ret = 0
步骤4: 如果测试脚本不具备合法的权限,则execveat返回-1,错误码为Permission denied:
# chmod -x test.sh
# ./test
execveat: Permission denied
access_ret = -1
bash解释器支持脚本可执行权限检查
前置条件
内核版本大于6.6.0-54,glibc版本大于等于2.38-41,bash版本大于等于5.2.15-13
bash-5.2.15-13.oe2403sp1.x86_64
glibc-2.38-41.oe2403sp1.x86_64
kernel-6.6.0-54.0.0.58.oe2403sp1.x86_64
操作指导
步骤1: 设置系统中所有脚本文件的权限为可执行
find / -name "*.sh" --exec chmod +x {} \;
步骤2: 设置启动参数并重启系统,添加的启动参数为:
exec_check.bash=1
步骤3: 验证只有具备可执行权限的脚本才可被bash解释器运行:
# echo "echo hello world" > test.sh
# bash test.sh
bash: line 0: [1402] denied sourcing non-executable test.sh
# chmod +x test.sh
# bash test.sh
hello world
jdk支持脚本可执行权限检查
前置条件
获取支持该特性的jdk代码:
https://gitee.com/openeuler/bishengjdk-8/tree/IMA_Glibc2_34
按照如下流程编译:
https://gitee.com/openeuler/bishengjdk-8/wikis/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3/%E6%AF%95%E6%98%87JDK%208%20%E6%BA%90%E7%A0%81%E6%9E%84%E5%BB%BA%E8%AF%B4%E6%98%8E
操作指导
步骤1: 确保系统中所有.class文件和.jar文件的可执行权限
find / -name "*.class" chmod +x {} \;
find / -name "*.jar" chmod +x {} \;
步骤2: 设置启动参数并重启系统,添加的启动参数为:
exec_check.java=1
步骤3: 验证只有具备可执行权限的class文件或jar文件才可被jvm运行:
可编写如下测试程序(HelloWorld.java)进行参数功能测试:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
# javac HelloWorld.java
Access denied to /home/bishengjdk/bishengjdk-8/install/jvm/openjdk-1.8.0_432-internal/lib/tools.jar
# chmod +x /home/bishengjdk/bishengjdk-8/install/jvm/openjdk-1.8.0_432-internal/lib/tools.jar
# javac HelloWorld.java
# java HelloWorld
Access denied to HelloWorld.class
# chmod +x HelloWorld.class
# java HelloWorld
Hello, World!
结合IMA摘要列表实现解释器类应用完整性保护
前置条件
开启IMA摘要列表功能,详见内核完整性度量(IMA) 文档章节。
操作指导
步骤1: 为目标应用程序生成IMA摘要列表(过程略,摘要列表生成方式详见内核完整性度量(IMA) 文档章节)。
步骤2: 开启IMA摘要列表功能(过程略,摘要列表生成方式详见内核完整性度量(IMA) 文档章节),以开启摘要列表+shell脚本校验为例,配置的内核启动参数如下:
ima_appraise=enforce ima_appraise_digest_list=digest-nometadata ima_policy="appraise_exec_tcb" initramtmpfs module.sig_enforce exec_check.bash=1
步骤3: 验证IMA对bash脚本完整性保护
# echo "echo hello world" > test.sh
# chmod +x test.sh
# bash test.sh
bash: line 0: [2520] denied sourcing non-executable test.sh
# 生成摘要列表后签名并导入(略)
# echo /etc/ima/digest_lists/0-metadata_list-compact-test.sh > /sys/kernel/security/ima/digest_list_data
# bash test.sh
hello world