赞
踩
The Linux Audit Subsystem is a system to Collect information regarding events occurring on the system(s) ,Kernel events (syscall events), User events (audit-enabled programs)
syslog记录的信息有限,主要目的是软件调试,跟踪和打印软件的运行状态,而audit的目的则不同,它是linux安全体系的重要组成部分,是一种“被动”的防御体系。在内核里有内核审计模块,记录系统中的各种动作和事件,比如系统调用,文件修改,执行的程序,系统登入登出和记录所有系统中所有的事件,它的主要目的是方便管理员根据日记审计系统是否允许有异常,是否有入侵等等,说穿了就是把和系统安全有关的事件记录下来。
首先内核需要打开CONFIG_AUDIT的配置,在打开了配置重新编译内核后,audit功能默认是关闭的,有两种方法在使能audit:
1)cmdline中加入audit= 1参数,如果这个参数设置为1,而且auditd没有运行,则审计日志会被写到/var/log/messages中。
2)使用守护进程auditd
下面是auditd整体的框架图:
从这个图大概就能看出audit是如何工作的,可以看到 audit 是内核中的一个模块,内核的运行情况都会记录在 audit 中,当然这个记录的规则是由超级用户来设置的。audit.rules 是 audit 的规则文件,auditctl程序负责将规则写入audit模块的过滤器中,过滤后的数据都会传送到 auditd 中,然后再由 auditd 进行其它操作。auditd.conf 是 auditd 的配置文件,确定 auditd 是如何启动的,日志文件放在哪里等等。auditd 收到的数据后会有两个去处。默认的是将日志保存在 audit.log 文件中,默认路径/var/log/audit/audit.log。另一个通过 audispd 将日志进行分发。
简单的使用:
auditd
auditctl -R /etc/audit/rules.d/audit.rules
cat /var/log/audit/audit.log 就可以查看audit记录的信息
auditd对应的源码在:audit-2.7.1/src/auditd.c,其中audit-2.7.1/lib 对应着库的源码,简单的浏览一下源码就能知道auditd每个选项的作用和它的主要工作
-s指定启动时的audit工作状态,可选的状态有:startup_disable,startup_enable,startup_nochange,如果未指定,默认为
enum startup_state opt_startup = startup_enable;
auditctl对应的源码在:audit-2.7.1/src/auditctl.c,简单浏览一下源码就能知道auditctl每个选择的作用和程序的主要原理。
auditctl的工作流程大概如下:
int main(int argc, char *argv[])
{
if ((argc == 3) && (strcmp(argv[1], "-R") == 0)) {
fd = audit_open();
if (is_ready(fd) == 0)
return 0;
fileopt(argv[2])
}
}
其中需要特别注意的是is_ready(),如果-R选项,也就是从文件中读取规则,则audit系统必须要先处于enable状态,is_ready()的实现如下:
static int is_ready(int fd)
{
if (audit_is_enabled(fd) == 2) { //规则不可改变
audit_msg(LOG_ERR, "The audit system is in immutable mode,"
" no rule changes allowed");
return 0;
} else if (errno == ECONNREFUSED) { //audit处于disable状态
audit_msg(LOG_ERR, "The audit system is disabled");
return 0;
}
return 1;
}
接着上面的分析,fileopt()打开规则文件:
static int fileopt(const char *file) { rc = open(file, O_RDONLY); if (rc < 0) { ... //规则文件不存在,返回错误 } ... //接下来判读规则文件的访问权限,当前进程是否是root,**规则文件是否全局可写**,是否是常规文件 while (get_line(f, buf)) { //循环读取文件中的每一行 preprocess(buf); ptr = audit_strsplit(buf); //去除行首的空白 if (ptr == NULL) //空行标志着规则文件的结束 break; if (ptr[0] == '#') { //#开头为注释,直接跳过,读取下一行 lineno++; continue; } reset_vars();//复位变量,也就是说规则是以行为单位的,行之间无关联 rc = setopt(i, lineno, fields); //分析每一行的选项,分析的结果放在rule_new中 if (rc != -3) { handle_request(rc);//如果规则没错,这执行存放在rule_new中的分析结果 } lineno++; }//while }
需要说明的选项是:
-e: 0表示disable,1表示enable,2表示设置规则不可变
-f:当audit反应严重错误是应该采取的动作,0=silent啥都不干, 1=printk 打印错误,2=panic
/etc/audit/auditd.conf存放在配置文件,决定auditd程序的行为,对其中一些重要的选项进行说明:
指定log文件存放的位置
指定单个log文件的最大大小,单位是Mbyte
当log文件达到max_log_file设定的大小时执行的动作,可选的动作 有:ignore/syslog/suspend/rotate, ignore表示忽略max_log_file设置的限制,继续写log文件,syslog表示会向syslog中写入一条warning,suspend表示auditd不再写log文件,但是auditd继续运行,rotate表示分多个log文件,一个log文件达到上限后在创建一个新的不同名字的log文件,后面会继续讲解该选项。
表示log_file 文件所在的分区空闲空间少于这个设定的值时,触发相应的动作,单位是Mbyte
先来一种整体的架构图:
内核audit模块定义了user,task,exit钩子,每个钩子对应一张表,当内核路径调用对应钩子时,对比钩子对应表中的规则,如果条件符合,则打印log到audit子系统,exclude用于auditd取audit log时进行过滤,过滤掉不感兴趣的event。
//task,user,exit钩子 Kernel: if (audit_enabled) { struct audit_buffer *ab; uid_t loginuid = from_kuid(&init_user_ns, audit_get_loginuid(current)); unsigned int sessionid = audit_get_sessionid(current); ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_KERNEL_OTHER); if (!ab) return; audit_log_format(ab, "auid=%u ses=%u" ,loginuid, sessionid); audit_log_task_context(ab); audit_log_format(ab, " comm="); audit_log_untrustedstring(ab, comm); audit_log_end(ab); } //用户空间也可产生audit消息,此时对应user钩子 User space: char buf[4096], *acct; int fd = audit_open(); // acct is untrusted string and must be encoded acct = audit_encode_nv_string("acct", pamh->user, 0); snprintf(buf, sizeof(buf), "op=change-password sauid=%d %s", audit_getloginuid(), acct); audit_log_user_message(fd, AUDIT_USER_CHAUTHTOK, buf, NULL, NULL, NULL, 0); free(acct); close(fd);
audit_open()和audit_log_user_message()函数在audit lib中定义,用户空间和audit内核空间是通过netlink进行数据交换的。
auid是Audit User IDentity的缩写,下面一段话有助于理解它的作用:
Clearly, we first need a way to track a user, be it an actual person or a system user, in such a way that we won’t lose track e.g. by a “sudo” or “su”. The way this is done is by setting an additional UID, the AUID, which is different from the “normal” UID a user has, and is supposed to remain unchanged, whatever the user does.
4294967295 is just (unsigned long) -1. -1 means that loginuid was not set. This is normal behavior for processes that were not spawned by any login process (e.g. for daemons). loginuid is -1 by default; pam_loginuid module changes it to your user id whenever you login (in a tty/in DM/via ssh), and this value is preserved by child processes.
pam_loginuid 是pam的一个库,通过调用audit_setloginuid()函数来修改:
int audit_setloginuid(uid_t uid) { char loginuid[16]; int o, count, rc = 0; errno = 0; count = snprintf(loginuid, sizeof(loginuid), "%u", uid); o = open("/proc/self/loginuid", O_NOFOLLOW|O_WRONLY|O_TRUNC); if (o >= 0) { int block, offset = 0; while (count > 0) { block = write(o, &loginuid[offset], (unsigned)count); if (block < 0) { if (errno == EINTR) continue; printf("Error writing loginuid\n"); close(o); return 1; } offset += block; count -= block; } close(o); } else { printf("Error opening /proc/self/loginuid\n"); rc = 1; } return rc; }
当然可能有人会说黑客可以篡改这个值,kernel也是有响应的机制来防止auid设置了之后是不可修改的:
static int audit_set_loginuid_perm(kuid_t loginuid) { /* if we are unset, we don't need privs */ if (!audit_loginuid_set(current)) return 0; /* if AUDIT_FEATURE_LOGINUID_IMMUTABLE means never ever allow a change*/ if (is_audit_feature_set(AUDIT_FEATURE_LOGINUID_IMMUTABLE)) return -EPERM; /* it is set, you need permission */ if (!capable(CAP_AUDIT_CONTROL)) return -EPERM; /* reject if this is not an unset and we don't allow that */ if (is_audit_feature_set(AUDIT_FEATURE_ONLY_UNSET_LOGINUID) && uid_valid(loginuid)) return -EPERM; return 0; }
每个钩子都有一张表,表中每一行存放一条规则,规则由一系列条件组成,在决定是否打印log到audit子系统时,依次比较每一条规则,如果规则满足,则停止比较,产生audit log,如果所有的规则都不满足,则log丢弃。
auditctl工具就是向对应的list(task,user,exit)中添加,删除规则。
注意user都是从用户空间写入audit log的,user中的规则主要是过滤从用户空间写入的audit log。
规则可以存放在文件中,由auditctl -R /etc/audit/rules.d/audit.rules加载规则文件。
There are three types of Audit rules that can be specified
Control rules — allow the Audit system’s behavior and some of its configuration to be modified.
File system rules — also known as file watches, allow the auditing of access to a particular file or a directory.
auditctl -w path_to_file -p permissions -k key_name
path_to_file is the file or directory that is audited. permissions are the permissions that are logged.
permissions can be one or a combination of r(read), w(write), x(execute), and a(attribute change).
key_name is an optional string that helps you identify which rule(s) generated a particular log entry.
System call rules — allow logging of system calls that any specified program makes.
auditctl -a action,filter -S system_call -F field=value -k key_name
-a [list,action|action,list]
Append rule to the end of list with action. Please note the comma separating the two values. Omitting it will cause errors. The fields may be in either order. It could be list,action or action,list. The following describes the valid list names:
task
Add a rule to the per task list. This rule list is used only at the time a task is created – when fork() or clone() are called by the parent task. When using this list, you should only use fields that are known at task creation time, such as the uid, gid, etc.
exit
Add a rule to the syscall exit list. This list is used upon exit from a system call to determine if an audit event should be created.
user
Add a rule to the user message filter list. This list is used by the kernel to filter events originating in user space before relaying them to the audit daemon. It should be noted that the only fields that are valid are: uid, auid, gid, pid, subj_user, subj_role, subj_type, subj_sen, and subj_clr. All other fields will be treated as non-matching.
exclude
Add a rule to the event type exclusion filter list. This list is used to filter events that you do not want to see. For example, if you do not want to see any avc messages, you would using this list to record that. The message type that you do not wish to see is given with the msgtype field.
The following describes the valid actions for the rule:
never
No audit records will be generated. This can be used to suppress event generation. In general, you want suppressions at the top of the list instead of the bottom. This is because the event triggers on the first matching rule.
always
Allocate an audit context, always fill it in at syscall entry time, and always write out a record at syscall exit time.
Replacing -a with -A in the above command will insert the rule at the top instead of at the bottom.
action and filter specify when a certain event is logged. action can be either always or never. filter specifies which kernel rule-matching filter is applied to the event. The rule-matching filter can be one of the following: task, exit, user, and exclude. action,filter will be always,exit in most cases, which tells auditctl that you want to audit this system call when it exits.
system_call specifies the system call by its name. Several system calls can be grouped into one rule, each specified after a -S option. The word all may also be used. You can use the sudo ausyscall --dump command to view a list of all system calls along with their numbers.
field=value specifies additional options that modify the rule to match events based on a specified architecture, user ID, process ID, path, and others.
key_name is an optional string that helps you identify later which rule or a set of rules generated a particular log entry.
You can also define a filesystem rule using the system call rule syntax. For example, the following rule:
sudo auditctl -a always,exit -F path=/etc/hosts -F perm=wa -k hosts_file_change
does the same job as the filesystem rule we saw in the earlier section:
sudo auditctl -w /etc/hosts -p wa -k hosts_file_change
https://www.digitalocean.com/community/tutorials/how-to-write-custom-system-audit-rules-on-centos-7
https://linux.die.net/man/8/auditctl
规则分析
分析的入口是:src/auditctl.c
static int setopt(int count, int lineno, char *vars[])
分析之后的数据为:
struct audit_rule_data {
__u32 flags; /* AUDIT_PER_{TASK,CALL}, AUDIT_PREPEND */
__u32 action; /* AUDIT_NEVER, AUDIT_POSSIBLE, AUDIT_ALWAYS */
__u32 field_count;
__u32 mask[AUDIT_BITMASK_SIZE]; /* syscall(s) affected */
__u32 fields[AUDIT_MAX_FIELDS];
__u32 values[AUDIT_MAX_FIELDS];
__u32 fieldflags[AUDIT_MAX_FIELDS];
__u32 buflen; /* total length of string fields */
char buf[0]; /* string fields buffer */
};
通过以下接口将规则数据写入内核:
rc = audit_add_rule_data(fd, rule_new, add, action); rc = audit_delete_rule_data(fd, rule_new, del, action); int audit_add_rule_data(int fd, struct audit_rule_data *rule, int flags, int action) { int rc; rule->flags = flags; rule->action = action; rc = audit_send(fd, AUDIT_ADD_RULE, rule, sizeof(struct audit_rule_data) + rule->buflen); if (rc < 0) audit_msg(audit_priority(errno), "Error sending add rule data request (%s)", errno == EEXIST ? "Rule exists" : strerror(-rc)); return rc; }
可以看出是通过netlink和内核进行通信的。
具体的分析过程不讲,感兴趣的可以自己去看源码,这里仅简单说明以下-F选项:
int audit_rule_fieldpair_data(struct audit_rule_data **rulep, const char *pair, int flags) { field = audit_name_to_field(f); rule->fields[rule->field_count] = field; case AUDIT_ARCH: if (_audit_syscalladded) return -EAU_ARCHMISPLACED; if (!(op == AUDIT_NOT_EQUAL || op == AUDIT_EQUAL)) return -EAU_OPEQNOTEQ; if (isdigit((char)*(v))) { int machine; errno = 0; _audit_elf = strtoul(v, NULL, 0); if (errno) return -EAU_ELFUNKNOWN; // Make sure we have a valid mapping machine = audit_elf_to_machine(_audit_elf); if (machine < 0) return -EAU_ELFUNKNOWN; } else { const char *arch=v; unsigned int machine, elf; machine = audit_determine_machine(arch); /* OK, we have the machine type, now convert to elf. */ elf = audit_machine_to_elf(machine); if (elf == 0) return -EAU_ELFUNKNOWN; _audit_elf = elf; } rule->values[rule->field_count] = _audit_elf; _audit_archadded = 1; break; rule->fieldflags[rule->field_count] = op; } //field名字和对应的field num之间的转换 static const char field_strings[] = "a0\0a1\0a2\0a3\0arch\0auid\0devmajor\0devminor\0dir\0egid\0" "euid\0exe\0exit\0field_compare\0filetype\0fsgid\0fstype\0fsuid\0gid\0inode\0" "key\0loginuid\0msgtype\0obj_gid\0obj_lev_high\0obj_lev_low\0obj_role\0obj_type\0obj_uid\0obj_user\0" "path\0perm\0pers\0pid\0ppid\0sessionid\0sgid\0subj_clr\0subj_role\0subj_sen\0" "subj_type\0subj_user\0success\0suid\0uid";
#define AUDIT_MAX_FIELDS 64, 最多支持64个field。
//kernel/audit.c static int __net_init audit_net_init(struct net *net) { struct netlink_kernel_cfg cfg = { .input = audit_receive, .bind = audit_bind, .flags = NL_CFG_F_NONROOT_RECV, .groups = AUDIT_NLGRP_MAX, }; struct audit_net *aunet = net_generic(net, audit_net_id); aunet->nlsk = netlink_kernel_create(net, NETLINK_AUDIT, &cfg); if (aunet->nlsk == NULL) { audit_panic("cannot initialize netlink socket in namespace"); return -ENOMEM; } aunet->nlsk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT; return 0; } /* Receive messages from netlink socket. */ static void audit_receive(struct sk_buff *skb) { mutex_lock(&audit_cmd_mutex); audit_receive_skb(skb); mutex_unlock(&audit_cmd_mutex); } static void audit_receive_skb(struct sk_buff *skb) { struct nlmsghdr *nlh; /* * len MUST be signed for nlmsg_next to be able to dec it below 0 * if the nlmsg_len was not aligned */ int len; int err; nlh = nlmsg_hdr(skb); len = skb->len; while (nlmsg_ok(nlh, len)) { err = audit_receive_msg(skb, nlh); /* if err or if this message says it wants a response */ if (err || (nlh->nlmsg_flags & NLM_F_ACK)) netlink_ack(skb, nlh, err); nlh = nlmsg_next(nlh, &len); } } static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh) { ... case AUDIT_ADD_RULE: case AUDIT_DEL_RULE: if (nlmsg_len(nlh) < sizeof(struct audit_rule_data)) return -EINVAL; if (audit_enabled == AUDIT_LOCKED) { audit_log_common_recv_msg(&ab, AUDIT_CONFIG_CHANGE); audit_log_format(ab, " audit_enabled=%d res=0", audit_enabled); audit_log_end(ab); return -EPERM; } err = audit_rule_change(msg_type, NETLINK_CB(skb).portid, seq, data, nlmsg_len(nlh)); break; ... } //kernel/auditfilter.c int audit_rule_change(int type, __u32 portid, int seq, void *data, size_t datasz) { int err = 0; struct audit_entry *entry; entry = audit_data_to_entry(data, datasz); if (IS_ERR(entry)) return PTR_ERR(entry); switch (type) { case AUDIT_ADD_RULE: err = audit_add_rule(entry); audit_log_rule_change("add_rule", &entry->rule, !err); break; case AUDIT_DEL_RULE: err = audit_del_rule(entry); audit_log_rule_change("remove_rule", &entry->rule, !err); break; default: err = -EINVAL; WARN_ON(1); } ... } //kernel/auditsc.c void __audit_syscall_entry(int major, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4) { struct task_struct *tsk = current; struct audit_context *context = tsk->audit_context; enum audit_state state; if (!context) return; BUG_ON(context->in_syscall || context->name_count); if (!audit_enabled) return; context->arch = syscall_get_arch(); context->major = major; context->argv[0] = a1; context->argv[1] = a2; context->argv[2] = a3; context->argv[3] = a4; state = context->state; context->dummy = !audit_n_rules; if (!context->dummy && state == AUDIT_BUILD_CONTEXT) { context->prio = 0; state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY]); //比较规则 } if (state == AUDIT_DISABLED) //如果规则不匹配,则返回 return; context->serial = 0; context->ctime = CURRENT_TIME; context->in_syscall = 1; //否则设置in_syscall 标志位 context->current_state = state; context->ppid = 0; } void __audit_syscall_exit(int success, long return_code) { struct task_struct *tsk = current; struct audit_context *context; if (success) success = AUDITSC_SUCCESS; else success = AUDITSC_FAILURE; context = audit_take_context(tsk, success, return_code); if (!context) return; if (context->in_syscall && context->current_state == AUDIT_RECORD_CONTEXT) //如果in_syscall 为1,也就是规则满足 audit_log_exit(context, tsk); //打印审计日志 ... } static void audit_log_exit(struct audit_context *context, struct task_struct *tsk) { int i, call_panic = 0; struct audit_buffer *ab; struct audit_aux_data *aux; struct audit_names *n; /* tsk == current */ context->personality = tsk->personality; ab = audit_log_start(context, GFP_KERNEL, AUDIT_SYSCALL); if (!ab) return; /* audit_panic has been called */ audit_log_format(ab, "arch=%x syscall=%d", context->arch, context->major); if (context->personality != PER_LINUX) audit_log_format(ab, " per=%lx", context->personality); if (context->return_valid) audit_log_format(ab, " success=%s exit=%ld", (context->return_valid==AUDITSC_SUCCESS)?"yes":"no", context->return_code); audit_log_format(ab, " a0=%lx a1=%lx a2=%lx a3=%lx items=%d", context->argv[0], context->argv[1], context->argv[2], context->argv[3], context->name_count); audit_log_task_info(ab, tsk); audit_log_key(ab, context->filterkey); audit_log_end(ab); }
关于这方面的资料挺少,有人说性能会降低一半,所以制定规则的时候需要特别小心的评估:
Auditing system calls results in high logging activity, which in turn puts a heavy load on the kernel. With a kernel less responsive than usual, the system’s backlog and rate limits might well be exceeded. Carefully evaluate which system calls to include in your audit rule set and adjust the log settings accordingly. See Section 33.2, “Configuring the Audit Daemon” for details on how to tweak the relevant settings.
参考资料:
https://doc.opensuse.org/documentation/leap/security/html/book.security/cha.audit.comp.html
https://www.suse.com/documentation/sled11/book_security/data/sec_audit_auditd.html
https://www.ibm.com/developerworks/cn/linux/l-lo-use-space-audit-tool/index.html
https://blog.csdn.net/qwertyupoiuytr/article/details/58278349
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/security_guide/sec-defining_audit_rules_and_controls
https://github.com/linux-audit/audit-documentation/wiki
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。