Post

[转]记录bash的操作

转自:https://www.freeoa.net/osuport/sysec/simple-linux-sys-users-operate-manage-solution_1880.html

当需要登录服务器的用户增加时,就必须知道运维人员、开发人员或者黑客在机器上进行了什么样的操作,或如果机器上的重要文件被删除了,你是否很想知道是谁在什么时候删的?

本文将介绍这样的一种组合方案,来对用户登录后的行为进行跟踪和审计。

bash 本身就有记录命令的功能,即 history。可以执行 history 命令显示你之前执行了什么命令。但history有其固有的缺陷,比如默认有大小限制、可轻易被人篡改或清空或不记录。而history分散在各机器上既无法保证其完整性也不方便审计,所以我们需要将其统一收集起来。

方案基本思路

收集 history的思路比较简单,即将history写入文件并上传到服务端或者写入syslog,然后由 syslog实时的发到远程日志服务器,或将收集到的 history 进行后续处理则可以写入文件保存或者写入数据库供日后查询。而如何收集history,则可以从bash的现有功能或者源代码着手考虑。 主要有两种方法:

一、script方法

将用户执行的命令,以及命令所产生的结果都重定向到具体文件中。 将下面的命令添加到/etc/profile中:

1
exec /usr/bin/script -a -f -q /var/log/ops/`whoami`-`date +%Y%m%d%H%M`.log 

创建日志目录:mkdir /var/log/operation/ -p

用户登录时便会自动记录用户的操作记录了,用户通过当前主机SSH到其他服务器上面去也可以记录用户在那台机器上的操作日志。

1
/usr/bin/script –a  /var/log/operation/$USER.log 2>&1

有时在日志文件里不能一直看到命令行的输出,好像有丢失,不知何故。不推荐使用这种方法,’script’指令多年不再更新了,最新的手册页是2000年的。在用户登录后,它会开启另外一个进程(/usr/bin/script -a /var/log/operation/root.log 像这种形式的),来监控用户的操作及其输出,当然从终端退出时要退出两次。

当操作输出量很大时(像导入mysql数据库)时,对应的记录日志文件会变的很大,对事后查找反而又变得不方便了。

1
exec /usr/bin/script -a -f -q /var/log/ops/`whoami`-`date +%Y%m%d%H%M`.log

You can set the PS4 variable, which is evaluated for every command being executed just before the execution if trace is on:

1
PS4='$(echo $(date) $(history 1) >> /tmp/trace.txt) TRACE: '

Then, enable trace:

1
set -x

To stop tracing, just:

1
set +x

二、设置bash 来记录的方法

$PROMPT_COMMAND

If set, contains a command to execute before printing the prompt ($PS1). Set it to the name of a function that captures the output of history 1.

思路非常简单,就是bash程序将 history发到syslog,然后利用 syslog 集中存放并入库供方便查询。当然这里需要对系统日志系统进行相应的改造,具体操作方法见文章末尾的链接。

即利用PROMPT_COMMAND将当前执行的命令通过logger命令写入syslog,再加上当前用户名,去掉history 1 中显示的数字就完美了。我们在全局配置文件 /etc/bashrc里加上就可以记录所有用户了,具体方法如下:

使用’logger’指令记录在’/etc/(r)syslog.conf’配置文件里所定义的日志类型所指的日志文件中。

1
logger -p local6.notice 'ssh of bash command.'

For BASH shells, edit the system-wide BASH runtime config file:

vim /etc/bash.bashrc

在其末尾加入:

1
2
Append to the end of that file:
export PROMPT_COMMAND='RETRN_VAL=$?;logger -p local6.debug "$(whoami) [$$]: $(history 1 | sed "s/^[ ]*[0-9]\+[ ]*//" ) [$RETRN_VAL]"'

我使用的方法:

1
export PROMPT_COMMAND='{ Cmd=$(history 1 | { read x y; echo $y; }); logger -p local5.info "$HOSTNAME [HIST] : $SSH_CLIENT : $PWD : $Cmd"; }'

在日志文件中定义相关的条目(供’logger’指令调用): Set up logging for “local6” with a new file:

1
2
3
4
sudo -e /etc/rsyslog.d/bash.conf

And the contents...
local6.*    /var/log/commands.log

重启日志服使其生效: Restart rsyslog:

1
sudo service rsyslog restart

在日志文件中记录如下:

1
2
3
4
Apr 11 09:57:01 pde root: pde.freeoa.net [HIST] : 192.168.18.98 51917 22 : /var/log : cd /var/log/
Apr 11 09:57:01 pde root: pde.freeoa.net [HIST] : 192.168.18.98 51917 22 : /var/log : ls
Apr 11 09:57:08 pde root: pde.freeoa.net [HIST] : 192.168.18.98 51917 22 : /var/log : tail -100 messages
Apr 11 10:17:03 pde root: pde.freeoa.net [HIST] : 192.168.18.98 51917 22 : /var/log : pwd

但是,这个方法非常容易被发现和被绕过。用env|set命令就可以看到PROMPT_COMMAND,而且现在的黑客都非常聪明,上来就执行unset HISTORY HISTFILE HISTSAVE HISTZONE HISTORY HISTLOG PROMPT_COMMAND; export HISTFILE=/dev/null; export HISTSIZE=0; export HISTFILESIZE=0 这样的命令防止执行的命令记录到history,那上面这个方法自然就失效了。

用户通过unset bash中的相关环境变量来逃避审计规则并不是没有方法阻止,我们依然可能通过较为高级权限设置来实现。

第一,更改相关记录文件的权限,使之只能追加,不能删除。这当然要借助于’chattr’指令来实现,注意只有’root’用户才能使用。

1
# chattr +a .sh_history or chattr +a .bash_history

第二,设置shell内的变量,并使之在普通用户使用的指令为只读,尤其是与历史记录相关的变量;这需要借助于’typeset’指令来实现: Use the shell’s typeset command with the -r option: this makes the specified variables read-only. This will make all history environment variables read-only. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
export HISTCONTROL=
export HISTFILE=$HOME/.bash_history
export HISTFILESIZE=2000
export HISTIGNORE=
export HISTSIZE=1000
export HISTTIMEFORMAT="%a %b %Y %T %z "

typeset -r HISTCONTROL
typeset -r HISTFILE
typeset -r HISTFILESIZE
typeset -r HISTIGNORE
typeset -r HISTSIZE
typeset -r HISTTIMEFORMAT

更多关于’HISTTIMEFORMAT’的相关信息,请参考文章尾部:“在bash的历史记录中显示命令执行过的日期及时间”段。

在bash中,可以通过’shopt’指令来修改其标准选项:

1
2
shopt -s cmdhist
shopt -s histappend

Setting cmdhist will put multiple line commands into a single history line, and setting histappend will make sure that the history file is added to, not overwritten as is usually done.

设置bash的’PROMPT_COMMAND’变量:

1
2
PROMPT_COMMAND="history -a"
typeset -r PROMPT_COMMAND

This is because bash actually writes the history in memory; the history file is only updated at the end of the shell session. This command will append the last command to the history file on disk.

也可以在bash的主配置文件中定义信号捕捉函数,来实现信息收集,Create a SIGDEBUG trap to send commands to syslog.

但通过’history’方法当用户知道关于其使用方法后容易绕过,从而使上面的设置失效,下面我们说说改bash源代码的方法。可能有读者说改源代码这种事情好麻烦,其实bash4.1 就提供了这样的功能,你只需要启用它就可以了。如果不是这个版本则需要再进行相应修改,也不是非常难的事。 bash从4.1版本开始,支持将操作历史记录直接记录到系统日志文件(syslog)中,但这种特性需要在编译时开启。

三、修改bash源码以直接记录用户操作

这种方法需要修改bash源码来实现,如果不想用它来代替系统原有的bash,还要为用户指定新的bash shell位置,操作要复杂些;但不必在bash的配置文件设置了,用户也不能通过设置bash环境来逃过审计(前提是用户仅使用bash)。

修改的具体步骤如下: 主要是打开syslog宏定义调用函数,再配以其它需要记录的值就可以了。本文以目前最新的bash-4.2以例进行说明: 在其解压后的目录下有三个与日志历史记录相关的文件:config-top.h、config-bot.h、bashhist.c

对其进行简单的阅读后,只需修改’config-top.h’这个头文件,打开相应的注释即可。本例从104-108行:

1
2
3
4
5
/* #define SYSLOG_HISTORY */
#if defined (SYSLOG_HISTORY)
#  define SYSLOG_FACILITY LOG_USER
#  define SYSLOG_LEVEL LOG_INFO
#endif

仅需要把104行/* #define SYSLOG_HISTORY */去掉注释#define SYSLOG_HISTORY,在将其编译安装到其它位置(‘/opt/ebash’)。

然后修改用户的使用shell:

1
2
usermod -s /opt/ebash/bin/bash hto
more /etc/passwd|grep hto

可以检查一下用户’hto’的shell是不是修改成功了。然后用该用户登录,这样他的操作便会记录在日志文件中(debian下在’/var/log/messages’文件中)。记录如下:

1
2
3
4
5
Apr 11 09:50:53 pde -bash: HISTORY: PID=12672 UID=1000 unset PROMPT_COMMAND
Apr 11 09:50:55 pde -bash: HISTORY: PID=12672 UID=1000 w
Apr 11 09:51:11 pde -bash: HISTORY: PID=12672 UID=1000 env
Apr 11 09:53:33 pde -bash: HISTORY: PID=12672 UID=1000 date
Apr 11 09:53:58 pde -bash: HISTORY: PID=12672 UID=1000 echo $BASH_VERSION

当然这种记录的字段及格式可能通过修改’bashhist.c’文件而改变。代码段如下: #if defined (SYSLOG_HISTORY) #define SYSLOG_MAXLEN 600

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void bash_syslog_history (line)
     const char *line;
{
  char trunc[SYSLOG_MAXLEN];

  if (strlen(line) < SYSLOG_MAXLEN)
    syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d UID=%d %s", getpid(), current_user.uid, line);
  else
    {
      strncpy (trunc, line, SYSLOG_MAXLEN);
      trunc[SYSLOG_MAXLEN - 1] = '\0';
      syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d UID=%d %s", getpid(), current_user.uid, trunc);
    }
}
#endif

修改’syslog’函数的两种情况下的记录样式:

1
2
3
syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d PPID=%d SID=%d User=%s Cmd=%s", getpid(), getppid(), getsid(getpid()),current_user.user_name, line);

syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d PPID=%d SID=%d User=%s Cmd=%s", getpid(), getppid(), getsid(getpid()),current_user.user_name, trunc);

编译安装后,记录格式如下:

1
2
3
4
Apr 11 11:26:05 pde -bash: HISTORY: PID=24849 PPID=24848 SID=24849 User=hto Cmd=unset PROMPT_COMMAND
Apr 11 11:26:48 pde -bash: HISTORY: PID=24849 PPID=24848 SID=24849 User=hto Cmd=perl -v
Apr 11 11:26:51 pde -bash: HISTORY: PID=24849 PPID=24848 SID=24849 User=hto Cmd=gcc -v
Apr 11 11:26:57 pde -bash: HISTORY: PID=24849 PPID=24848 SID=24849 User=hto Cmd=ping www.freeoa.net

当用户’hto’ su 成’root’后,root用户的默认shell不是改良后的bash shell时,便不记录操作日志了。只有将’root’用户的shell改为改良后的’bash’时,就可以记录后续的日志:

1
2
3
Apr 11 11:31:51 pde -su: HISTORY: PID=24995 PPID=24994 SID=24849 User=root Cmd=w
Apr 11 11:31:57 pde -su: HISTORY: PID=24995 PPID=24994 SID=24849 User=root Cmd=echo $BASH_VERSION
Apr 11 11:32:19 pde -su: HISTORY: PID=24995 PPID=24994 SID=24849 User=root Cmd=ls /bin

上面的修改加入了进程及会话id信息,能记录是从其他用户’su’过去的一些信息。与老方案相比,它没有记录当前的工作目录,可能会对重现指令当时的情况有影响,当然加上它并不难,代码如下:

1
syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d PPID=%d SID=%d User=%s Pwd=%s Cmd=%s", getpid(), getppid(), getsid(getpid()),current_user.user_name,get_current_dir_name(),line);

这里使用了’get_current_dir_name’函数,编译后重新安装。记录的日志格式如下:

1
2
3
4
5
6
7
Apr 11 11:50:23 pde -bash: HISTORY: PID=25872 PPID=25871 SID=25872 User=hto Pwd=/home/hto Cmd=w
Apr 11 11:50:25 pde -bash: HISTORY: PID=25872 PPID=25871 SID=25872 User=hto Pwd=/home/hto Cmd=date
Apr 11 11:51:42 pde -bash: HISTORY: PID=25872 PPID=25871 SID=25872 User=hto Pwd=/home/hto Cmd=man scp
Apr 11 11:54:48 pde -bash: HISTORY: PID=25872 PPID=25871 SID=25872 User=hto Pwd=/home/hto Cmd=su -
Apr 11 11:54:59 pde -su: HISTORY: PID=25948 PPID=25947 SID=25872 User=root Pwd=/root Cmd=ls /sbin
Apr 11 11:55:16 pde -su: HISTORY: PID=25948 PPID=25947 SID=25872 User=root Pwd=/root Cmd=cd /var/log
Apr 11 11:55:20 pde -su: HISTORY: PID=25948 PPID=25947 SID=25872 User=root Pwd=/var/log Cmd=ls -lth

方案缺点:

1、该方案只能记录history类似的命令执行日志,无法记录通过程序或者脚本执行的命令;

2、该方案依然存在被用户绕过的风险,用户可以修改自己的默认shell,而linux默认提供了除bash 之外的其它shell,比如/bin/csh、/bin/zsh、/bin/ksh等,如果用户登录之后将自己的bash 改为/bin/csh或没加记录history到syslog的 bash 则无法记录,或者直接执行/bin/csh即可绕过,当然你可以将不用的shell 想办法去掉;

3、关于审计linux操作还有一些其它的技术实现,比如加载内核模块拦截系统调用或者读取系统pty接口等。


Bash History: Display Date And Time For Each Command 在bash的历史记录中显示命令执行过的日期及时间

需要设置’HISTTIMEFORMAT’环境变量来实现: If the HISTTIMEFORMAT is set, the time stamp information associated with each history entry is written to the history file.Defining the environment variable as follows:

1
$ HISTTIMEFORMAT="%d/%m/%y %T "

OR

1
$ echo 'export HISTTIMEFORMAT="%d/%m/%y %T "' >> ~/.bash_profile

Where,

1
2
3
4
5
6
%d - Day
%m - Month
%y - Year
%T - Time

$ history

会有如下输出:

1
2
3
4
5
6
987  11/03/10 04:31:36 w
988  11/03/10 04:31:37 iostat
989  11/03/10 04:31:37 top
993  11/03/10 04:31:41 grep CPU /proc/cpuinfo
994  11/03/10 04:31:45 vmstat 3 100
..

参考-References:

1
2
3
man bash
help history
man 3 strftime

Don’t save commands in bash history (only for current session) 如何在bash shell中不记录当前执行过的命令

通过对’HISTFILE’环境变量的操作来实现(这种设置仅对当前会话有效)

1
unset HISTFILE

this will cause any commands that you have executed in the current shell session to not be written in your bash_history file upon logout.

1
export HISTSIZE=0

Don’t save commands in bash history (only for current session).

1
HISTFILE=/dev/null

disable history for current shell session. 禁止记录当前会话

1
history -c

Clear current session history (bash). 清空当前会话的历史记录

1
ssh user@host "> ~/.bash_history"

Don’t save commands in bash history (only for current session) Only from a remote machine: Only access to the server will be logged, but not the command. The same way, you can run any command without loggin it to history. ssh user@localhost will be registered in the history as well, and it’s not usable.

This post is licensed under CC BY 4.0 by the author.