当前位置:Linux教程 - Linux - LIDS精通与进阶(上)

LIDS精通与进阶(上)

发布日期: 2001-6-17
内容:
--------------------------------------------------------------------------------


作者:chaobowang@sina.com


一、系统入侵

  随着Internet上的Linux主机的增加,越来越多的安全漏洞在当前的GNU/Linux系统上发现。你也许在Internet上听说过在Linux下发现Bug,它会导致系统很容易的被黑客攻击。

  因为Linux是一个开放源代码的系统,漏洞很容易发现,并且也会很快的有补丁出来。但是当漏洞没有公布的时候,并且管理员很懒,没有去打补丁。黑客就会很容易的攻击这个系统,取得root权限,在现有的GNU/Linux下,他就可以做任何他想做的事情。现在你可以问,我们现在到底可以做些什么呢?

1、现在的GNU/Linux错误在哪里?

  超级用户会滥用职权,他能够做所有他要做的事情。作为root。他会改变所有的东西。
  许多系统文件很容易被更改。这些文件可能是很重要的文件,如/bin/login,如果一个黑客进入,他可以上传一个login程序来覆盖/bin/login,这样他就可以不用登陆名和密码来登陆系统。但是这些文件不需要经常改动,除非你要升级系统。
  模块modules很容易用来中断内核。模块是为了让Linux内核更模块话和更高效而设计的。但是当模块加入到内核,它就会成为内核的一部分并且能做原始内核能做的工作。因此,一些不友好的代码可以写成模块来加入到内核里,这些代码就会重定向系统调用并且作为一个病毒来运行。
  进程是不受保护的,一些进程,如后台的Web服务器,一直都认为是没有严格保护的程序。因此,他们就会很容易被黑客攻击。

2、LIDS的设想是什么。

  保护重要文件。因为文件很容易被root更改,为什么不严格文件操作呢?因此,LIDS改变了文件系统在内核里的安全系统调用。如果某个时候一些人访问一个文件,他就会进入系统调用然后我们就可以检查文件名并且看她们是否被保护。如果它已经被保护,我们就可以拒绝这个访问者的要求。
  保护重要的进程。这个和上面的保护进程的想法不是一样的。当一个系统里运行一个进程,它会在/proc 文件系统里有一个用pid作为路径名的入口。所以,如果你用"ps -axf"你就可以显示出当前运行的进程。你可以问如果保护这些进程。如果你要杀死一个进程的话,首先,你键入"ps"来得到进程的PID,然后,你键入"kill 〈pid〉"来杀死它。但是,如果我不让你看到进程,你怎么来杀死这个进程呢?因此,LIDS是用隐藏进程来保护它的。 另外一个重要的方法就是不让任何人可以杀死进程,包括root用户。LIDS能够保护父进程是init(pid=1)的所有进程 。
  封装内核。有时候我们需要要把一些必要的模块加入到内核里来使用,另外,我们也要拒绝任何人包括root用户向内核插入模块。那么如何来平衡这个矛盾的问题呢?我们可以只允许在系统启动的时候插入模块,然后我们封装模块,在封装后,内核不允许任何人插入模块到内核里。通过这种封装功能,我们能用它来保护重要的文件,进程,我们可以在系统启动的时候只允许必要的进程,只改变必要的文件。在封装内核后,我们就不能在对文件有任何的修改。


二、保护文件系统

1、保护文件系统是LIDS的重要功能之一。这个功能是在内核的VFS(虚拟文件系统)层实现的,我们可以保护任何种类的文件系统,如EXT2,FAT。 在LIDS,保护的文件按种类分为以下几种:

只读的文件或目录。只读文件意味着它们不被允许改写,如,在目录/usr/bin,/sbin。这些类型的文件大多数都是二进制系统程序或是系统配置文件,除了在升级系统的时候,我们不需要改变它们。
只可增加文件或目录。这些文件是那些只可以增加大小的文件。大多数是系统的日值文件,如在/var/log里的只可增加文件。
额外的文件或目录,这些文件没有被保护。一般来说,你想要保护目录下的所有文件,但是,还需要有一些特殊的文件不要被保护。所以我们可以定义这些文件作为额外的其他的只读文件。
保护挂载或卸载文件系统。当你在启动的时候挂载文件系统的时候,你可以禁止所有人,甚至是root,去卸载文件系统。你也可以禁止任何人在当前文件系统下挂载文件系统来覆盖它。

2、LIDS如何在内核保护文件

在这部分,我们会看到一些内核的代码来理解LIDS是如何保护文件的。

(1)、Linux文件系统数据结构程序

首先,我们必须了解Linux的虚拟文件系统。

在Linux里的每一个文件,不管是什么样子的,都有一个结点inode数,文件系统提供了以下数据结构。

在/usr/src/linux/include/linux/fs.h

struct inode {
struct list_head     i_hash;
struct list_head     i_list;
struct list_head     i_dentry;

unsigned long       i_ino; ----> inode number.
unsigned int       i_count;
kdev_t          i_dev; ----> device number.
umode_t          i_mode;
nlink_t          i_nlink;
uid_t           i_uid;
......
}

注意:用来鉴定一个结点inode。这个意思是你可以用一对来得到一个系统里独一无二的inode。

在/ur/src/linux/cinclude/linux/dcache.h里

struct dentry {
     int d_count;
    unsigned int d_flags;
    struct inode * d_inode;       /* Where the name belongs to - NULL is negative */
    struct dentry * d_parent;     /* parent directory */
    struct dentry * d_mounts;     /* mount information */
    struct dentry * d_covers; struct list_head d_hash;   /* lookup hash list */
    struct list_head d_lru;      /* d_count = 0 LRU list */
    struct list_head d_child;     /* child of parent list */
    struct list_head d_subdirs;    /* our
    ......
}

dentry是一个目录文件的入口。通过这个入口,我们可以很容易的在文件的父目录下移动。

例如,如果你一文件的inode是(struct inode*)file_inode,如果你可以用file_inode->d_entry来得到它的目录入口并且用file_inode->d_entry->d_parent来得到父目录的目录入口。

(2)、LIDS保护数据结构

在分析完linux文件系统后,让我们来看看LIDS是如何容VFS来保护文件和目录的。

在/usr/src/linux/fs/lids.c

struct secure_ino {
    unsigned long int ino;     /* the inode number */
    kdev_t dev;           /* the dev number /*
    int type;            /* the file type */
    };

上面的结构用一对来存储保护文件或目录的结点。"type"是用来标明保护结点文件类型的。

LIDS有4种类型:

在/usr/src/linux/include/linux/fs.h

#define LIDS_APPEND    1    /* APPEND ONLY FILE */
#define LIDS_READONLY   2    /* Read Only File */
#define LIDS_DEVICE    3    /* Protect MBR Writing to device */
#define LIDS_IGNORE    4    /* Ignore the protection */

通过secure_ino结构,我们能很容易的初使化保护的文件或是在内核里执行以下函数。

在/usr/src/linux/fs/lids.c

int lids_add_inode(unsigned long int inode ,kdev_t dev , int type)
{

     if ( last_secure == (LIDS_MAX_INODE-1))
     return 0;

     secure[last_secure].ino = inode;
     secure[last_secure].dev = dev;
     secure[last_secure].type = type;
     secure[++last_secure].ino = 0;

#ifdef VFS_SECURITY_DEBUG
     printk("lids_add_inode : return %dn",last_secure);
#endif
     return last_secure;
}

就象你在上面代码上可以看到的,给secure_ino加到一个结点上是非常容易的。被保护的结点会在系统启动的时候初使化。初使化程序在/usr/src/linux/fs/lids.c的init_vfs_security()里。

现在,让我们看看LIDS是如何来检查是否一个结点已经受到保护。

在/usr/src/linux/fs/open.c

int do_truncate(struct dentry *dentry, unsigned long length)
{
     struct inode *inode = dentry->d_inode;
     int error;
     struct iattr newattrs;
     /* Not pretty: "inode->i_size" shouldn''t really be "off_t". But it is. */
     if ((off_t) length < 0)
     return -EINVAL;

#ifdef CONFIG_LIDS
     if (lids_load && lids_local_load) {
     error =
     lids_check_base(dentry,LIDS_READONLY);
     if (error) {
              lids_security_alert("Try to truncate a protected file (dev %d %d,inode %ld)",
              MAJOR(dentry->d_inode->i_dev),
              MINOR(dentry->d_inode->i_dev),
              dentry->d_inode->i_ino);
.....................

这个是LIDS加到内核里做检测的一个例子。你会看到lids_check_base()是LIDS保护方法的一个核心函数。

你可以在LIDS要保护的地方看到很多LIDS保护方法用到lids_check_base()函数,特别是在linux内核的子目录下。

在/usr/src/linux/fs/lids.c

int lids_check_base(struct dentry *base, int flag)
{
..................
inode = base->d_inode;     /* get the inode number */
parent = base->d_parent; /* get the parent diretory */
.................
---->  do {
          if ( inode == parent->d_inode)
          break;
         if ((retval = lids_search_inode(inode))) {
            if ( retval == LIDS_IGNORE || (retval == LIDS_DEVICE && flag != LIDS_DEVICE)) break;
           if ( flag == LIDS_READONLY || ( flag == LIDS_APPEND && retval >flag ) || ( flag == LIDS_DEVICE && flag == retval )) {
                         return -EROFS;
                      } break; }
                      inode = parent->d_inode;
                  } while( ((parent = parent->d_parent ) != NULL) );
            return 0;
     }

lids_check_base()会检查一个给定文件的dentry和它的父目录是否被保护。

注意:如果它的父目录被保护,它下面的文件也会被保护。

例如,如果"/etc/"被保护,"/etc/passwd"也一样被保护。

(3)、在内核保护系统调用

为了保护系统,LIDS会在一些检查临界的系统调用的时候做检查。因此,我们可以保护系统调用和限制文件系统的用户调用。

这些是一些例子:

open(),open是通过禁止一些权利来保护文件的打开。你可以在打开调用open_namei()调用的时候LIDS在检测它。
mknod(),mknod是用来在指定目录下保护mknod。
unlink(),在内核代码检查do_unlink()。


三、保护设备

Linux的设备会在/dev/目录下以文件的形式列出,我们可以用上面保护文件的方法来保护设备。但是在一些情况下,用户也可以用IO操作来旁路文件系统来读写设备,我们必须注意这个问题。

1、设备,内核I/O

在GNU/Linux系统下的设备会以文件的形式表达,所以我们可以用保护文件系统那样来保护设备。

用户的I/O访问是通过系统调用sys_operm和sys_iopl来实现的。你可以看看/usr/src/linux/arch/i386/kernel/ioport.。这个是要基于系统结构的,要是到其他平台,就需要注意它们的变化。

2、如何用LIDS来保护

大多数情况下,程序不需要通过在/dev的设备文件名称来访问设备。但是,一些特殊的程序需要直接访问,如X Server,这个会写到/dev/mem和甚至是I/O设备。我们需要一些额外的东西来保护设备。LIDS会在配置内核的时候来定义这个功能。

CONFIG_LIDS_ALLOW_DEV_MEM,如果你选择了开启这个功能,你就可以允许一些特殊程序来访问/dev/men和/dev/kmen这些内核临界的设备。如果你想要用内核的X Server,选择这个功能就会在配置内核的时候提供整个路径和文件名。
CONFIG_LIDS_ALLOW_RAW_DISKS,如果选择这个开启,你就可以允许一些特殊的程序来访问物理磁盘。
CONFIG_LIDS_ALLOW_IO_PORTS,如果你选择了开启这个功能,你就可以允许一些特殊的程序来访I/O端口。
当系统运行fs/lids.c里的init_vfs_security()的时候初使化就被调用。

#ifdef CONFIG_LIDS_ALLOW_DEV_MEM

lids_fill_table (allow_dev_mem,&last_dev_mem,LIDS_MAX_ALLOWED,CONFIG_LIDS_DEV_MEM_PROGS);

#endif

#ifdef CONFIG_LIDS_ALLOW_RAW_DISKS

lids_fill_table(allow_raw_disks,&last_raw_disks,LIDS_MAX_ALLOWED,CONFIG_LIDS_RAW_DISKS_PROGS);

#endif

#ifdef CONFIG_LIDS_ALLOW_IO_PORTS

lids_fill_table (allow_io_ports,&last_io_ports,LIDS_MAX_ALLOWED,CONFIG_LIDS_IO_PORTS_PROGS);

#endif

如果一个进程或是程序要直接访问ip端口或是磁盘设备,LIDS就会检查它在数组allow_raw_disk,last_io_ports,等)。这个检查是通过调用lids_check_base()里的lids_search_inode(inode)来实现的。

比如,让我们看看CONFIG_LIDS_ALLOW_DEV_MEM

/* in lids_search_inode() */

#ifdef CONFIG_LIDS_ALLOW_DEV_MEM

for( i = 0 ; i < last_dev_mem ;i++ ) {

if ( allow_dev_mem[i].ino == ino && allow_dev_mem[i].dev == dev) {

return LIDS_READONLY;

}

}

#endif

#ifdef CONFIG_LIDS_ALLOW_RAW_DISKS

在allow_dev_mem包括了哪一个程序结点在系统启动的时候在init_vfs_security()里初使化。用同样的方法,除了一些特殊程序,我们可以保护设备,I/O访问等等。