爱心技术专栏专题

LIDS精通与进阶(二)

摘录:linux系统安全 来源:linux系统安全 加入时间:2007年04月23日
摘要:
LIDS精通与进阶(二)

   五、密封内核

   我们需要在系统启动的时候做一些必要的操作,但是…

LIDS精通与进阶(二)

站点:爱心种子小博士 关键字:LIDS精通与进阶(二)

LIDS精通与进阶(二)

   五、密封内核

   我们需要在系统启动的时候做一些必要的操作,但是我们也需要在系统运行的时候保护它们。?

   例如,我们需要象内核里插入一些需要的模块,但是我们不希望在系统运行的时候插入任何模块,因为那样会十分危险。如何解决这个问题呢?这里就有一些密封的方法。我们可以在系统启动的时候做我们任何想做的事情,然后我们就密封内核。然后,我们就不能做那些以前在没有密封的时候可以做的事情。用密封的方法,我们可以用模块来解决问题,我们可以在密封前向内核里插入我们想要的模块,在密封后我们就不可以在内核里插入或是删除任何模块。

   1、用LIDS密封内核

为了密封内核,我们可以用下面的LIDS命令

#lidsadm –I -- -CAP_xxx….

它们可以放到脚本里让系统启动的时候就执行它。具体你们可以看我以前在linuxbyte和chinabyte发表的文章。LIDS是通过/proc/sys/lids/locks和内核通讯的。

当你密封了内核,lidsadm是调用lidsadm.c的lids_init()的调用。

#define LIDS_LOCKS "/proc/sys/lids/locks"

......

void lids_init(int optind, int argc, char *argv[])

{

......

if ((fd=open(LIDS_LOCKS,O_RDWR)) == -1) {

perror("open");

exit_error (2, "cant open " LIDS_LOCKS);

}

if (read(fd,&locks,sizeof(lids_locks_t))==-1) {

perror("read");

exit_error (2, "cant read " LIDS_LOCKS);

}

lids_set_caps(optind,argc,argv,&locks);

locks.magic1=LIDS_MAGIC_1;

.........

if (write(fd,&locks,sizeof(lids_locks_t))==-1) {

perror("write");

exit_error (2, "cant write " LIDS_LOCKS);

}

.....

}
这个系统调用在LIDS_LOCKS生成新的变量loks,内核会通过lids_proc_locks_sysctl()命令来读取它。Lids_proc_locks_sysctl也会从用户区完全检查并读取它,然后改变密封的变量lids_first_time为0。

让我们看看lids_proc_locks_sysctl().这个函数会在用户读写/proc/sys/lids/locks的时候调用。 te, struct file *filp,

void *buffer, size_t *lenp, int conv, int op)

{

...........

 
/* first: check the terminal and the program which access the sysctl */

#ifndef CONFIG_LIDS_REMOTE_SWITCH

if (current->tty && (current->tty->driver.type != 2) ) {

lids_security_alert("Try to %s locks sysctl (unauthorized terminal)",

write ? "write" : "read");

return -EPERM;

}

#endif

........

/* second: check wether it is not a timeout period after two many failed attempts */

.......

if (write) {

/* Third : check what is submitted (size, magics, passwd) */

if (*lenp != sizeof(lids_locks_t)) {

lids_security_alert("Try to feed locks sysctl with garbage");

return -EINVAL;

}

if (copy_from_user(&locks,buffer,sizeof(lids_locks_t)))

return -EFAULT;

.......

if ((lids_first_time) && (!locks.passwd[0])) {

.........

number_failed=0;

if (lids_process_flags(locks.flags)) {

cap_bset=locks.cap_bset;

lids_security_alert("Changed: cap_bset=0x%x lids_flags=0x%x",cap_t(cap_bset),lids_flags);

}

Change flag here ..--> lids_first_time=0;

.....

}

   上面的函数会在密封内核或是改变内核安全级别的时候工作。变量lids_first_time是一个表明当前密封状态的的一个标志。当改变了需要的使能位,这个标志就会置1表明当前的状态是密封后。

    密封内核有两个任务,首先,改变使能位,然后,改变lids_first_time标志为1。在密封后,系统就不允许改变它们了,除非你用lidsadm和密码。

   2、在密封前保护程序

   因为在密封前的状态是危险的,我们必须知道在密封前那些运行的程序是LIDS来保护的。为什么呢?因为密封后我们就不能改变它们了。如果文件没有被保护,一些人就可以改变他们然后重新启动,这些程序可能对系统非常危险。让我们来看看在没有密封前一个运行的非保护程序的代码。 int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)

{

..........

#ifdef CONFIG_LIDS_SA_EXEC_UP

if (lids_first_time && lids_load) {

if (!lids_check_base(dentry,LIDS_READONLY))

#ifdef CONFIG_LIDS_NO_EXEC_UP

lids_security_alert("Try to exec unprotected program %s before sealing LIDS",filename);

if (dentry)

dput(dentry);

return -EPERM;

#else

lids_security_alert("Execed unprotected program %s before sealing LIDS",filename);

#endif

}

}

#endif
......

}

   你会看到当LIDS保护系统开启(lids_load==1)和当前系统没有密封(lids_firest_time 为1)的时候,内核就会检查当前程序是否在LIDS的lids_check_base()保护下。如果没有被保护,它就会启动报警信息。

   六、LIDS与Capability

   1、Capability是一套来表明一个进程可以做为什么的位标志。在LIDS,我们可以用capability的限制来限制所有的进程。

在/include/linux/capability.h


typedef struct __user_cap_header_struct {

__u32 version;

int pid;

} *cap_user_header_t;

typedef struct __user_cap_data_struct {

__u32 effective;

__u32 permitted;

__u32 inheritable;

} *cap_user_data_t;

#ifdef __KERNEL__

/* #define STRICT_CAP_T_TYPECHE

#ifdef STRICT_CAP_T_TYPECHECKS

typedef struct kernel_cap_struct {

__u32 cap;

} kernel_cap_t;

#else

typedef __u32 kernel_cap_t;

#endif

kernel_cap_t cap_bset = CAP_FULL_SET;

   在kernel_ap_t的每一位都代表一个许可。Cap_bset是capability集的主要部分。它们的值可以通过改变/proc/sys/kernel/cap-bound来改变。

看看上面的文件,你就会发现一些问题。

/* in include/linux/capability.h */

/* In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this

overrides the restriction of changing file ownership and group

ownership. */

#define CAP_CHOWN 0

/* Override all DAC access, including ACL execute access if

[_POSIX_ACL] is defined. Excluding DAC access covered by

CAP_LINUX_IMMUTABLE. */

#define CAP_DAC_OVERRIDE 1

/* Overrides all DAC restrictions regarding read and search on files

and directories, including ACL restrictions if [_POSIX_ACL] is

defined. Excluding DAC access covered by CAP_LINUX_IMMUTABLE. */

#define CAP_DAC_READ_SEARCH 2

.........

   每一个任务(进程)在结构task_struct定义了三个成员:cap_effective,cap_inheritable,cap_permitted.我们已经有了一个用来表明基本capability的变量cap_bset。它们会检测这个系统并确定那种capability用来控制系统。

在内核实现的大部分系统调用会调用函数capable() (在kernel/sched.c)。然后会调用cap_raised() (在/include/linux/capability.h)。如下:

#ifdef CONFIG_LIDS_ALLOW_SWITCH

#define cap_raised(c, flag) ((cap_t(c) & CAP_TO_MASK(flag)) && ((CAP_TO_MASK(flag) & cap_bset) || (!lids_load) || (!lids_local_load)))

#else

#define cap_raised(c, flag) (cap_t(c) & CAP_TO_MASK(flag) & cap_bset)

#endif

你会看到这里的cap_bset(一般默认都是1)是很重要的。如果有人在那里把一些位置0,capability就可以会禁止整个系统。如,18 位的CAP_SYS_CHROOT, 如果我们把他置0,表明我们就不能用chroot()了。

如果你看到sys_chroot的源代码,你就发现很多问题了:

if (!capable(CAP_SYS_CHROOT)) {

goto dput_and_out;

}


capable()会返回0,在位18为0,这样chroot就会给用户返回一个错误信息。

   2、在LIDS里的capability

   LIDS用capability来限制整个动作进程。LIDS用的函数是capable()。在内核代码中已经存在的许多capable()里。我们可以禁止一些当前系统默认的capability并且在用户违反LIDS定义的规则的时候报警。

   至于管理员,他们也可以用lidsadm和密码来改变capability。当内核授权用户的时候,capability变量cap_bset 就会改变。

   作为管理员一个需要理解的重要东西是每一个capability的意思。然后,在密封内核的时候禁止capability,并用密码来改变它们。

   七、LIDS在内核中的安全级别

   一些时候,我们需要改变保护系统的配置。那样我们该怎么做呢?LIDS给我们提供了两种方法。

  • 我们可以重新启动系统,在LILO:里键入security=0.
  • 我们可以在用lidsadm –S中用密码来转换安全级别。

   1、在内核中的两个安全级别

   LIDS在内核中定义了两个安全级别,安全的security和无安全的none_security.默认情况下,是设置成安全的级别。如果你需要改变它。就在启动的时候键入security=0.

   在内核中有一个全局变量lids_load。它表明是否lids的安全变量security被开启。它默认是1。如果在系统启动的时候在LILO:键入security=0 ,所有的LIDS的保护都会失效,就象那些没有LIDS保护的系统一样。


/* variant defined in fs/lids.c */

int lids_reload_conf=0;

int lids_load=0; /* it is raised to 1 when kernel boot */

int lids_local_on=1;

int lids_local_pid=0;

/* in init/main.c */

#ifdef CONFIG_LIDS

/*

* lids_setup , read lids info from the kernel.

*/

static void __init lids_setup(char *str, int *ints)

{

if (ints[0] > 0 && ints[1] >= 0)

====> _lids_load= ints[1];

}

#endif

....

/* init the LIDS when the system bootup up */

static void __init do_basic_setup(void)

{

......

/* Mount the root filesystem.. */

mount_root();

#ifdef CONFIG_LIDS

/* init the ids file system */

---> lids_load=_lids_load;

lids_local_on=_lids_load;

lids_flags=lids_load * (LIDS_FLAGS_LIDS_ON | LIDS_FLAGS_LIDS_LOCAL_ON);

===> printk("Linux Intrusion Detection System %s n",lids_load==1?"starts":"stops");

init_vfs_security();

#endif

......

}

    在系统启动的时候,你可以看到Linux Intrusion Detection System 0.9 starts,表明LIDS的保护开启了。当保护停止的时候,你可以看到Linux Intrusion Detection System 0.9 stops。这里的0.9是当前的LIDS版本号。

    2、用lidsadm来改变系统安全级别

一些时候,有也可以在线的时候改变你的安全级别,你必须把CONFIG_LIDS_ALLOW_SWITCH功能开启。并且在编译前配置内核的时候提供一个RipeMD-160 encrypted password 。

这个密码可以用lidsadm –p 命令来获得

用内核鉴定

用提供的密码,LIDS可以鉴定用户来区分哪个用户可以转换内核的安全级别。

这个功能也要用到lidsadm的-S参数。如。

# /sbin/lidsadm -S -- -LIDS

SWITCH

Password:xxxxxx

#

在输入密码后,我们就可以转换LIDS的安全关闭。

让我们看看内部的编码来了解它是如何工作的,

/* in the fs/lids.c lids_proc_locks_sysctl() */

int lids_proc_locks_sysctl(ctl_table *table, int write, struct file *filp,

void *buffer, size_t *lenp, int conv, int op)

{

lids_locks_t locks;

byte hashcode[RMDsize/8];

char rmd160sig[170];

.......

locks.passwd[sizeof(passwd_t)-1]=0; /* We dont take the risk */

rmd160sig[0]=0;

#ifdef CONFIG_LIDS_ALLOW_SWITCH

if ((!lids_first_time) || (locks.passwd[0])) {

RMD((byte *)locks.passwd,hashcode);

memset((char *)locks.passwd,,sizeof(passwd_t));

for (i=0; i

sprintf(rmd160sig+2*i,"%02x", hashcode[i]);

}

if ( ((lids_first_time) && (!locks.passwd[0])) ||

----------> (!strncmp(rmd160sig,CONFIG_LIDS_RMD160_PASSWD,160)) ) {

#else

if ((lids_first_time) && (!locks.passwd[0])) {

#endif

/* access granted ! */

number_failed=0;

if (lids_process_flags(locks.flags)) {

cap_bset=locks.cap_bset;

lids_security_alert("Changed: cap_bset=0x%x lids_flags=0x%x",cap_t(cap_bset),lids_flags);

}

lids_first_time=0;

}

........

}

   在密码检查正确后。Lids_process_flag()就会改变当前的lids标记为关闭状态,然后你就可以在不受保护的系统做你想要做的事情了。你可以看看fs/lids.c的lids_process_flag的代码来了解它。

   转换LIDS和LIDA_GLOBAL

   如果你把LIDS的保护关闭,你会有两个结果,一,关闭后其它没有被LIDS保护的控制台一样不受保护,二,可以本地的关闭它们,在其它的控制台,所有的系统依然被LIDS保护。它们一样很安全。

   这些细节是fs/lids的lids_process_flag()来实现。

   八、内核的网络安全

   用LIDS,你可以用下面的功能来保护你的网络。

   1、保护防火墙和路由的规则

   如果你的主机包含一些防火墙规则。你可以用LIDS来保护它们。你可以开启

   CONFIG_LIDS_ALLOW_CHANGE_ROUTESLAI 实现这个功能。你也必须在密封内核的时候关闭CAP_NET_ADMIN。

   然后,你也可以允许程序更改路由规则。

   让我们来看看保护防火墙规则的代码吧。每一个改变防火墙的请求都会调用内核的ip_setsockopt()函数。

int ip_setsockopt(struct sock *sk, int level, int optname, char *optval, int optlen)

{

........

switch(optname)

{

.......

case IP_FW_DELETE_NUM:

case IP_FW_INSERT:

case IP_FW_FLUSH:

case IP_FW_ZERO:

case IP_FW_CHECK:

case IP_FW_CREATECHAIN:

case IP_FW_DELETECHAIN:

case IP_FW_POLICY:

#ifdef CONFIG_LIDS_ALLOW_CHANGE_ROUTES

if (!(capable(CAP_NET_ADMIN) || (current->flags & PF_CHROUTES))) {

#else

if (!capable(CAP_NET_ADMIN)) {

#endif

#ifdef CONFIG_LIDS

lids_security_alert("CAP_NET_ADMIN violation: try to change IP firewall rules with option %d",optname);

#endif

return -EACCES;

}
........

   从上面的代码,我们可以看到如果有人要改变防火墙的规则,你必须把CAP_NET_ADMIN开启,并且你要修改规则的程序必须用routing_changeable来标记。

   2、禁止嗅探

   这个功能也是在上面的changing_route来实现的。让我们来看看net/core/dev.c的代码。

int dev_ioctl(unsigned int cmd, void *arg)

{

........

switch(cmd)

{

........

case SIOCSIFMETRIC:

case SIOCSIFMTU:

case SIOCSIFMAP:

case SIOCSIFHWADDR:

case SIOCSIFSLAVE:

case SIOCADDMULTI:

case SIOCDELMULTI:

case SIOCSIFHWBROADCAST:

case SIOCSIFTXQLEN:

case SIOCSIFNAME:

#ifdef CONFIG_LIDS_ALLOW_CHANGE_ROUTES

if (!(capable(CAP_NET_ADMIN) || (current->flags & PF_CHROUTES))) {

#else

if (!capable(CAP_NET_ADMIN)) {

#endif

#ifdef CONFIG_LIDS

lids_security_alert("CAP_NET_ADMIN violation: ioctl SIOC #%i",cmd);

#endif

return -EPERM;
.........

   你能发现,如果你要为嗅探改变状态的话, 你必须让CAP_NET_ADMIN开启来实现这个功能。你可以在内核密封前启动CAP_NET_ADMIN,然后在内核密封后禁止它。

   3、内核的检测端口扫描的功能

   为什么要在内核里需要一个端口扫描的检测?

   因为一个端口扫描的时候能检测到半连接的扫描。所以,端口扫描检测需要作为一个嗅探的程序来运行。如果我们需要检测端口扫描。我们又要开启允许嗅探程序的运行,在内核中的端口扫描检测很有用处。

端口扫描的主要思想是在一段短的时间内扫描一个范围的端口,然后,它们会在扫描后记录那些端口是开放的。用这个方法,扫描器就可以检测到远端的机器开放了什么端口。在内核里,我们能发现下面的代码。

内核中的端口扫描检测

让我们看看tcp的端口扫描

/* in net/ipv4/tcp_ipv4.c */

int tcp_v4_rcv(struct sk_buff *skb, unsigned short len)

{

........

__skb_queue_tail(&sk->back_log, skb);

return 0;

no_tcp_socket:

#ifdef CONFIG_LIDS

lids_check_scan(skb->nh.iph->saddr,ntohs(th->dest));

#endif

tcp_v4_send_reset(skb);

discard_it:

.........

}

lids_check_scan()有两个参数,一个是影响no_sock_srror的源地址,另外一个是你要通讯的机器上的端口。

Lids_check_scan()主要的任务是统计由同一个资源发出的错误信息。但是lids_check_scan()在源地址是一个端口扫描器的时候不会做检查,它会要timer来做。现在,让我们看看lids_check_scan()的代码。

/* in net/ipv4/lids_check_scan.c */

int lids_check_scan(__u32 addr,__u16 port)

{

...........

if((p = lids_find_scan(addr)) == NULL) {

p1 = &lids_scan_head;

p = (struct lids_scan*)kmalloc(sizeof(struct lids_scan),GFP_ATOMIC);

if(p == NULL ) {

return -1;

}

while((p1->next)!=NULL)p1=p1->next;

/* init the structure. */

p1->next = p;

spin_unlock(p->lock);

p->next = NULL;

p->addr = addr;

p->counter = 0;

p->lower_counter = 0;

p->create_time = current_time;

/* init a timer to do the detect thing */

init_timer(&(p->timer));

p->timer.expires = LIDS_SCAN_TIMEOUT + current_time;

p->timer.data = (unsigned long) p;

p->timer.function = lids_proceed_scan ;

add_timer(&(p->timer));

}

/* add the counter when hit */

spin_lock(p->lock);

(p->counter)++;

/* we here defined the port < 1024 and > 1024 */

if(port < 1024)

(p->lower_counter)++;

spin_unlock(p->lock);

return 0;

}

    从上面的代码,我们能看到这个函数的主要是一个列表,所以它很快。为了防止在kmalloc()的DoS的攻击,我们也需要来限制检测的列表。在这个代码里可能是错误的,但是因为记时的函数timer lids_proceed_scan能非常快的更新这个列表(每3秒一次)。所以DoS攻击很难让内核迷惑扫描源的真实性。

   九、入侵响应系统

   当我们检测到了一些程序违反了我们定的规则,我们必须要对它做出响应。在当前的LIDS系统,我们可以用记录的功能来记录下所有的信息。我们也可以挂起这个用户用到的控制台。然后,我们就会给LIDS加上更多的响应系统,不但是在内核里,还是在用户区。

   1、允许用安全的方法登陆

   在传统的内核登陆模式,我们每次都是用printk在控制台打印信息。但是这样很容易会被DoS攻击内核。他会让系统频繁的运行printk命令,我们可以在内核用security_alert()来实现报警响应功能。

   你可以看看/include/linux/kernel.h的代码。

   2、控制台挂起

   这个功能是用安全日志来挂起那些违反LIDS定义的安全规则的人的控制台。他们要继续必须重新登陆系统。但是他们所做的一切都已经被系统日志记录下来或是用e-mail的方法发送给了管理员。



转载:转载请保留本信息,本文来自http://www.51dibs.com/lp07/la/a5/l_a_9cb15104ec70ef22.html