限制linux用户可使用的命令及权限的两种方案


这段时间思考了这么一个问题,如何限制 linux 系统中登录用户的访问权限,限制用户能够使用的命令。在某些场景下,要求能够限制用户使用的命令,同时还能够执行像shutdown这种超级用户才能使用的命令。

方案一

以 rbash 的方式来限制用户的访问权限,在 ubuntu 系统中,直接使用

bash -r

就可以进入 rbash,在 centos 7 系统中,不支持直接使用,可以建立软链接的方式

ln -s /bin/bash /bin/rbash

useradd -s /bin/rbash testuser

这样,新创建的用户 testuser 的 shell 环境就是 rbash 环境。rbash 主要对用户做了如下限制

  • 使用命令cd更改目录
  • 设置或者取消环境变量的设置(SHELL, PATH, ENV, or BASH_ENV)
  • 指定包含参数’/'的文件名
  • 指定包含参数’ - '的文件名
  • 使用重定向输出’>’, ‘>>’, ‘> |’, ‘<>’ ‘>&’,’&>’
  • 使用 exec 内置命令用其他命令代替当前shell命令
  • 使用 -f 或者 -d 选项添加和删除内置命令
  • 使用enable选项启动失效的内置命令
  • 对内置命令制定 -p 选项
  • 使用 set +r 或者 set +o\ 来关闭限制模式

当我们将用户的 shell 环境设置成 rbash 时,用户就会受到以上限制的影响。假如需要更加深入一步,还要限制用户能够访问的命令呢。这个也可以解决。

shell 上执行的命令,都是通过在 $PATH 环境变量中进行查找获取到的,因为 rbash 的缘故,不允许命令中带有路径,也就是不允许 /bin/echo 这种方式执行命令,所以限制用户的访问命令,我们可以修改用户的 PATH 环境变量,比如将 PATH 设置在 /home/testuser/usercmd 中,允许用户访问什么样的命令,就在/home/testuser/usercmd 中创建一个指向该命令的软链接即可,比如 ls 命令

export PATH=/home/testuser/usercmd

ln -s /bin/ls /home/testuser/usercmd/ls

这样,testuser 用户就能够使用 ls 命令了。

假如有这么一个场景,就是对于一线运维人员来说,他们登录的用户权限肯定是受限的,包括路径访问权限,只允许使用有限的命令,这些都可以通过上面的方法来解决,但是,如果允许他们能够重启机器,或者修改网卡配置信息呢,这些可都是需要超级用户权限才能执行的操作,直接通过上面的方式是不能直接执行的。

面对这种场景,我们可以考虑方案二

方案二

笔者使用的环境是 centos7.9,系统内置的 shell 版本是 4.2 版本。笔者在思考这个问题的时候想过,如果非要这么严格的限制用户的权限,那干脆类似交换机那样,专门实现一个shell,或者使用 busybox 裁剪一个shell 好了。这确实是一种可行的方法,但是一般来说,专门实现一个 shell 代价比较高,busybox 裁剪的话,也是很麻烦的。基于此,笔者想到,是不是可以直接在 bash4.2 源代码的基础上,改造一个 shell 出来呢。

也就是说,在每次执行 shell 的时候,都会对我们执行的命令进行一个检查,如果是我们允许的命令,那就继续执行,如果是不允许访问的命令,直接返回结果。而这些允许的命令都是在配置文件中读取的,配置的修改只允许 root 权限才能进行。这样,是不是通过可配置的方式,就直接限定了用户的执行权限和可访问的命令了呢。

经过验证,笔者认为是可以的。下面咱们来慢慢完善这个思路。

首先,我们需要解决以下这几个问题

  1. 修改 bash4.2 源代码,实现读取配置的方式来限制用户能够执行的命令
  2. 如何让用户可以执行某些超级用户权限才能执行的命令

梳理 bash-4.2 源代码

通过一个简单的代码流程图,来看一下shell代码执行的流程,如下所示

shell flow

bash 在读取命令的时候,是通过 yacc 语法解析器来解析 shell 脚本的,同时会生成一个统一的抽象语法树的结构,如下所示

typedef struct command {
   
  enum command_type type;	/* FOR CASE WHILE IF CONNECTION or SIMPLE. */
  int flags;			/* Flags controlling execution environment. */
  int line;			/* line number the command starts on */
  REDIRECT *redirects;		/* Special redirects for FOR CASE, etc. */
  union {
   
    struct for_com *For;
    struct case_com *Case;
    struct while_com *While;
    struct if_com *If;
    struct connection *Connection;
    struct simple_com *Simple;
    struct function_def *Function_def;
    struct group_com *Group;
#if defined (SELECT_COMMAND)
    struct select_com *Select;
#endif
#if defined (DPAREN_ARITHMETIC)
    struct arith_com *Arith;
#endif
#if defined (COND_COMMAND)
    struct cond_com *Cond;
#endif
#if defined (ARITH_FOR_COMMAND)
    struct arith_for_com *ArithFor;
#endif
    struct subshell_com *Subshell;
    struct coproc_com *Coproc;
  } value;
} COMMAND;

该结构中 type 表示命令的类型,常用的简单命令,类型为 SIMPLE, WHILE 和 IF 表示 while 循环语句和 if 条件语句,而 CONNECTION 类型,表示该 shell 脚本是通过管道符号连接起来的多个 shell 操作。value 结构,表示的是命令的具体内容。

针对我们的场景,开放给用户有限的命令行,type 类型就是 SIMPLE 或者 CONNECTION。也就是简单命令或者通过管道符号连接的起来的若干个简单命令的 shell 脚本。

接着我们看下 connection 的结构

typedef struct connection {
   
  int ignore;			/* Unused; simplifies make_command (). */
  COMMAND *first;		/* Pointer to the first command. */
  COMMAND *second;		/* Pointer to the second command. */
  int connector;		/* What separates this command from others. */
} CONNECTION;

connection 结构,包含两个 COMMAND 指针,分别指向管道符号左右两边的shell命令,这是一个递归的关系,first 指针指向管道符号左边的 shell 命令,通常就是一个 SIMPLE 类型的命令,second 指向的是右边的shell命令,而 second 同样可以管道符号连接起来的多个 shell 命令,类型为 CONNECTION,以此类推。

simple_com 的结构就比较简单

typedef struct simple_com {
   
  int flags;			/* See description of CMD flags. */
  int line;			/* line number the command starts on */
  WORD_LIST *words;		/* The program name, the arguments,
				   variable assignments, etc. */
  REDIRECT *redirects;		/* Redirections to perform. */
} SIMPLE_COM;

line 表示命令起始行,words 就表示命令的具体内容,而 redirects 表示重定向信息。

typedef struct word_desc {
   
  char *word;		/* Zero terminated string. */
  int flags;		/* Flags associated with this word. */
} WORD_DESC;

/* A linked list of words. */
typedef struct word_list {
   
  struct word_list *next;
  WORD_DESC *word;
} WORD_LIST;

words 是一个单链表结构,比如 ls -l 这条命令,bash 会将其解释成 ls --color=auto -l,words 结构的组成如下所示

ls word

解析完 shell 脚本之后,在执行命令时,分为三个步骤进行。

  • Find_func() 从 PATH 中查找命令,如果存在,执行;如果不存在,进入下一步
  • 查找命令是否是 builtin 内建命令,如果存在,执行;如果不存在,进入下一步
  • 调用 search_for_command 查找命令,即认为命令的执行方式是 /bin/echo 这种方式,然后在 /bin 这个路径中查找命令,如果存在就执行;如果不存在,说明命令不存在,报错。

shell 执行的流程大概就是上述这个流程,我们需要限制用户能够访问的命令,那么在命令执行之前,检查命令是否符合我们的标准即可,也就是在 execute_command_internal() 函数中,执行 COMMAND 执行进行检查。

添加代码

execute_command.c 中,在执行 execute_in_subshell 函数之前,我们进行检查,添加如下代码

if (running_startup_files == 0) {
   
    if (check_command_permission_for_smbash(command) 
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫步旅人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值