Linux用户命令执行轨迹记录

实验环境:Ubuntu 16.04,多用户

Linux是一个多用户系统,通常情况下系统不会自动记录用户在终端的命令执行记录,而是将用户的命令记录缓存在当前的终端session中,当用户退出终端终止session,命令记录缓存被写进用户目录下的 .bash_history 文件中。.bash_history 缓存文件记录用户命令执行记录功能非常有限:

  1. 这种记录是用户级别的,各用户对该文件有管理权,管理员用户要读取文件信息需提升用户权限
  2. 用户可以自行清理文件内容,导致命令记录丢失,管理员不能获得完整命令轨迹
  3. 文件能够缓存对用户命令记录有限,HISTFILESIZE限定了文件能缓存的记录条数,当用户结束终端session被写入缓存文件后,文件中只能有 HISTFILESIZE 条最新历史命令会被保留,其他旧的历史命令会被截断舍弃,该操作会导致命令记录丢失。除非开启 histappend 功能。
  4. 在写入缓存文件之前,终端session中能够缓存的命令记录有限,通过 HISTSIZE 限定。终端session命令缓存是一个队列,当用户在终端执行命令数超出 HISTSIZE 后,最初的命令会被舍弃,仅最新的 HISTSIZE条命令将被保留在缓存中。当session终止时,这些保留在缓存中的命令才被写到命令历史文件。显然,如果用户在同一个session中执行指令超出限制,旧的命令记录将丢失,并且结合上一条HISTFILESIZE 的限制命令丢失的几率更大。

如果我们能利用 rsyslog 系统日志功能将用户的每一条命令执行记录实时写进系统日志,那么上述这些问题将不存在。刚好我们可以利用 PROMPT_COMMAND 来完成这个任务。

那么什么是 PROMPT_COMMAND ?为什么它能实现命令历史实时记录呢?

Bash provides an environment variable called PROMPT_COMMAND. The contents of this variable are executed as a regular Bash command just before Bash displays a prompt.

可见,PROMPT_COMMAND 是bash的一个环境变量,特别的地方在于它存储的是可执行bash指令,该指令在bash 提示符出现之前执行。提示符大家并不陌生,当我们打开终端的时候系统已经为我们准备了一个提示符等待我们输入指令。当我们输入指令按下 Enter 执行完后,系统又为我们准备了一个提示符等待更多命令输入。PROMPT_COMMAND 中的命令就是在用户命令执行完之后,系统准备新提示符之前执行。在这个时间点,用户的命令记录已存入终端session缓存,我们只要把读出来并利用 PROMPT_COMMAND 中的代码把它写进日志即可。

这里是我们记录用户命令执行轨迹的具体做法:1)定义一个 Bash 过程,在该过程中实现对session最新缓存记录的读取;2)将读取的内容用 logger 程序写进系统日志。

usrlogd() {
    # get runtime directory
    if [ -z "$__BASH_CMD_CONTEXT" ]; then
        __BASH_CMD_CONTEXT="$PWD"
    fi
    __BASH_CMD_HISNO="$( history 1 | { read __BASH_CMD_HISNO __BASH_CMD_HISCMD; echo $__BASH_CMD_HISNO; } )"
    __BASH_CMD_HISCMD="$( history 1 | { read __BASH_CMD_HISNO __BASH_CMD_HISCMD; echo $__BASH_CMD_HISCMD; } )"
    # filter out ENTER and only log valuable user command
    # fix bug: "history -c" will clear all histories and __BASH_CMD_HISNO,__BASH_CMD_HISCMD will be empty
    # fix bug: ENTER in terminal will result __BASH_CMD_HISNO=__BASH_CMD_HISPRENO
    # fix bug: the latest command will be logged everytime when you launch a new terminal
    # For now the terminal exit command will not be recorded
    if [ ! -z "$__BASH_CMD_HISPRENO" ] && [ ! -z "$__BASH_CMD_HISNO" ] && [ "$__BASH_CMD_HISNO" != "$__BASH_CMD_HISPRENO" ]; then
        CMD_XXX="$( echo $__BASH_CMD_HISCMD | { read CMD_XXX CMD_IGNORE; echo $CMD_XXX; } )"
        CMD_XXY="$( which $CMD_XXX 2>/dev/null )"
        if [ ! -z "$CMD_XXY" ]; then CMD_XXX=$CMD_XXY; fi
        CMD_YYY="$(echo $__BASH_CMD_HISCMD | { read CMD_IGNORE CMD_YYY; echo $CMD_YYY; } )"
        #MSG="$(who -m | { read x y z; echo "$x : TTY=$y ; PWD=$__BASH_CMD_CONTEXT ; USER=$x ; COMMAND=$CMD_XXX $CMD_YYY"; })"
        MSG="$(x=$(whoami); y=$(tty|sed "s/\/dev\///g");  echo "$x : TTY=$y ; PWD=$__BASH_CMD_CONTEXT ; USER=$x ; COMMAND=$CMD_XXX $CMD_YYY"; )"
        logger -i -t usrlogd -p user.info "$MSG"
    fi
    # record command history ID
    __BASH_CMD_HISPRENO="$__BASH_CMD_HISNO"
    # update runtime directory
    __BASH_CMD_CONTEXT="$PWD"
}
# export usrlogd

PROMPT_COMMAND=usrlogd

代码中我们对几种特殊的情况进行了特别处理:

  1. 用户打开终端的第一个提示符时不记录,否则将会重复记录上一终端关闭命令 exit.bash_history 里记录的最后一条指令,特别是同时开启多个终端的时候,因为当我们打开一个新session时它的运行缓存是从 .bash_history 读进来的。
  2. 用户按下 Enter 输入空命令时,不激活命令记录,否则将重复记录上一条指令

代码尚存在一个问题:当用户执行 history -c 清空session历史缓存的时,history -c 命令记录将不被记载(因为此时缓存队列里已经没有任何记录了)。我们目前忽略这条指令的记录。

命令记录将按照下面格式由 logger 写进系统日志:

username : TTY=pts/1; PWD=/home/username; USER=username; COMMAND=/bin/ls

外加 logger添加的时间戳其他信息,日志将详细记录“谁,在哪个终端里,在什么目录,以什么身份,执行了什么指令”, 可谓非常全了。

将上述代码添加到指定用户的 .profile.bashrc 文件里实现定向用户监督,也可以添加到 /etc/profile 文件 或 (建议)写入新建文件 /etc/profile.d/usrlogd.sh 实现全局监督。

此外结合 rsyslog 强大的管理功能,还能将命令历史轨迹日志按用户、时间、命令存入不同的文件,或将日志发送到远程日志中心管理服务器处理。我们目前的处理方法是将日志转发到中心服务器处理,本地将舍弃该日志的记录。

参考资料:
  1. bash HISTSIZE vs. HISTFILESIZE?
  2. Bash Shell 中的 PROMPT_COMMAND
  3. PROMPT_COMMAND
  4. Bash记录用户操作 -- PROMPT_COMMAND

你可能感兴趣的:(Linux用户命令执行轨迹记录)