完善linux bash操作记录审核方法

发布时间:May 1, 2015 // 分类:运维工作,工作日志,VC/C/C++,linux,转帖文章 // No Comments

linux工作的同事一般都了解过bash历史记录的问题,我们之前的做法是通过修改bash源码,把历史记录发送到syslog/var/log/messages这个文件里面,再在syslog配置文件里面加入 *.* @Ip发送到远程日志服务器上面。期间有经历过一些多多少少的bug之类的,比如我们是通过keyroot权限登录服务器的,记录的时候可能一条操作记录同时记录到几个人的key,普通用户记录的问题,特别是最近解决的一个严重bug:不能记录远程ssh执行的命令,比如 ssh ip “pwd” 我们记录不到这个pwd命令!在比较空闲时候通过n多的测试,这些都解决了。我们一步一步说起:

先来几个效果图:

正常情况下登录,然后执行命令是这个效果:

远程ssh过来执行命令时候是这个效果:

中间那个ip是远程过来的ip,在本机执行了pwd这个命令,user值就是我们要得到的

远程scp复制的日志是这个效果:

 

为了bash能记录到操作记录,我们修改源码:

config-top.h文件里面把#define SYSLOG_HISTORY这个宏定义打开,修改bashhist.c里面的内容:

701行左右修改稿bash_syslog_history函数,修改完之后内容:

void
bash_syslog_history (line)
     const char *line;
{
  char trunc[SYSLOG_MAXLEN];
     const char *p; 
     p = getenv("NAME_OF_KEY");
  if (strlen(line) < SYSLOG_MAXLEN)
    syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d PPID=%d SID=%d  User=%s USER=%s CMD=%s", getpid(), getppid(), getsid(getpid()),  cu
rrent_user.user_name, p, line);
  else
    {   
      strncpy (trunc, line, SYSLOG_MAXLEN);
      trunc[SYSLOG_MAXLEN - 1] = '\0';
      syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d  PPID=%d SID=%d User=%s USER=%s CMD=%s", getpid(), getppid(), getsid(
getpid()), current_user.user_name, p, trunc); 
    }   
}

我们这里定义的是NAME_OF_KEY这个变量,即bash记录的操作记录是这个人操作的,稍后我们要做的就是怎么得到NAME_OF_KEY这个变量。

接下来要做的就是编译bash

./configure –prefix=/usr/local/bash_4.1
make
make install

echo ‘/usr/local/bash_4.1/bin/bash’ >> /etc/shells

然后把/etc/passwd里面定义的/bin/bash全部指向/usr/local/bash_4.1/bin/bash这个我们编译的bash

这样重新登录,在messages里面就有了bash打出的log了,只是NAME_OF_KEY这个变量是空的,我们只知道是root执行的,但不知道具体是谁执行的,我们可以通过在登录时候在~/.ssh/authorized_keys这个文件里面找到自己的key,最后一列定义好自己的名字,然后赋值给NAME_OF_KEY就可以了,实现方法是打开sshd登录的debug日志,在/etc/ssh/sshd_config 这个文件里面加入LogLevel DEBUG,保存重启sshd服务即可,这样会在/var/log/secure里面打印出登录过程,我们需要的是Found matching RSA key: b7:51:9e:a6:03:cf:1e:23:a7:c2:9a:f5:2f:c4:af:0c这样类似的日志,这样匹配到了rsakey指纹,然后我们用系统里面加入好的.ssh/authorized_keys这个文件里面的key做对比就知道是哪个key登录的,我们取最后一列的注释即可(.ssh/authorized_keys里面的格式最后一列想对于一个注释,我们加key的时候把具体名字写好)。以前是通过登录的时间和登录的ip来得到匹配的Found matching RSA key这一行,但是问题来了,我们办公网如果有两个人同时登录的话,就有bug了,得到了几个key。。bug,我们继续探讨。

有什么可以决定用户登录系统的唯一指标而且又很容易得到呢?id号,我们登录系统,就会产生一个bash进程,可以通过echo $$得到,这个进程就可以判断这个时候登录用户的唯一性,我们匹配到这个进程idkey,然后取最后一位就可以了。但是也有特殊情况,如果是root用户登录的话,日志里面记录的(sshd[16322]: Found matching RSA key: b7:51:9e:a6:03:cf:1e:23:a7:c2:9a:f5:2f:c4:af:0csshd这里的进程号就是bash进程id,果不是root用户,验证指纹的是另外一个进程号,经测试,发现如果是普通用户的话,这个进程号得通过ps去匹配就可以了。

实现办法:

/etc/profile 里面加入下面这段,让用户每次登录都要加载:

pid=$PPID
#在自己home目录得到所有的key,如果/var/log/key 没有的时候,添加进去
while read line
do
grep "$line" /var/log/key >/dev/null || echo "$line" >> /var/log/key
done < $HOME/.ssh/authorized_keys
#得到每个key的指纹
cat /var/log/key | while read LINE
do
                NAME=$(echo $LINE | awk '{print $3}')
echo $LINE >/tmp/key.log.$pid
                KEY=$(ssh-keygen -l -f /tmp/key.log.$pid | awk '{print $2}')
grep  "$KEY $NAME"  /var/log/ssh_key_fing >/dev/null || echo "$KEY $NAME" >> /var/log/ssh_key_fing
done
#如果是root用户,secure文件里面是通过PPID号验证指纹
if [ $UID == 0 ]
then
ppid=$PPID
else
#如果不是root用户,验证指纹的是另外一个进程号
ppid=`/bin/ps -ef | grep $PPID |grep 'sshd:' |awk '{print $3}'`
fi
#得到RSA_KEY和NAME_OF_KEY,用来bash4.1得到历史记录
RSA_KEY=`/bin/egrep 'Found matching RSA key' /var/log/secure|/bin/egrep "$ppid"|/bin/awk '{print $NF}'|tail -1`
 if [ -n "$RSA_KEY" ];then
NAME_OF_KEY=`/bin/egrep "$RSA_KEY" /var/log/ssh_key_fing|/bin/awk '{print $NF}'`
fi
#把NAME_OF_KEY设置为只读
readonly NAME_OF_KEY
export NAME_OF_KEY
/bin/rm /tmp/key.log.$pid

这样我们基本实现了messages里面记录日志的功能,但久了就会发现远程执行的时候这个是无效的,我们另外下办法。

经多次测试,发现远程执行时候,执行的命令会放在BASH_EXECUTION_STRING这个变量里面,所以我们可以把这个变量打入到日志里面,再加好NAME_OF_KEY这个变量就可以了。实现方法:

先判断是否为空test -z “$BASH_EXECUTION_STRING” ,如果发现这个值不为空就通过logger这个命令直接发日志就可以了logger -t -bash -s “HISTORY $SSH_CLIENT USER=$ NAME_OF_KEY CMD=$BASH_EXECUTION_STRING ” >/dev/null 2>&1 ,这里的-t是制定和之前bash记录那样有关-bash的前缀,但是现在没有NAME_OF_KEY这个变量值,原因是之前我们放在/etc/profile文件,远程执行时候是不加载那个文件的,现在我们更改一下位置,把之前那个分离出来,我这里是放在/etc/bash_4399这个文件里面,然后在/etc/bashrc里面加入:

test -z "$BASH_EXECUTION_STRING" || { test -f /etc/bash_4399 && . /etc/bash_4399; logger -t -bash -s "HISTORY $SSH_CLIENT USER=$NAME_OF_KEY CMD=$BASH_EXECUTION_STRING " >/dev/null 2>&1;}

这样就完美解决了。

 

虽然是完美解决了,但是在centos 5系列上面才完美,在6系列上面问题就出来了,他不加载/etc/bashrc这个文件。想办法。。man sshd仔细找帮助,发现LOGIN PROCESS这段有些内容:

When a user successfully logs in, sshd does the following:

其中有一条Reads the file ~/.ssh/environment,看来6系列的也会去价值这个文件,测试在这个文件里面加入a=a ,然后远程查看,确实有这个变量值,看来把之前那个判断NAME_OF_KEY 的加在这个文件里面就可以了,看似是可以的,其实不然,这个只是赋值型的加载,并不支持命令,比如a=`pwd`,结果变量a的内容是`pwd`,并不是pwd的执行结果。崩溃。另外下办法。。对比5系列和6系列的系统,发现openssh版本上升了一个大版本,现在怀疑是系统或者openssh版本或者bash版本问题,测试。。在5系列编译6系列的openssh版本,发现失效了,6系列编译5系列的openssh版本居然可以了,看来就是openssh版本问题。解决办法?

下载源码,看看有没有发现opensshbash结合地方的一些可疑地方。n久也只发现sshconnect.c里面有点点猫腻,但是都失败了,后来在bash的源码发现shell.c里面有一些no_rc之类的定义(static int no_rc; /* Don’t execute ~/.bashrc */),看来就是这些变量的问题,结果下面n长又是一团糟。发现下面有个定义相似的变量

/* get the rshd/sshd case out of the way first. */
  if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 && 
      act_like_sh == 0 && command_execution_string)
    {    
#ifdef SSH_SOURCE_BASHRC
      run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) ||
                   (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0);
#else
      run_by_ssh = 0; 
#endif
 
      /* If we were run by sshd or we think we were run by rshd, execute
         ~/.bashrc if we are a top-level shell. */

google发现了这个含义:

只要编译前在`config-top.h`中定义` SSH_SOURCE_BASHRC`,那么尽管Bash在被sshd fork出来的时候加上了`-c`选项,也确实是non-login non-interactive shell,只要发现`SSH_CLIENT`或者`SSH2_CLIENT`环境变量存在,就仍然会依次载入`SYS_BASHRC``~/.bashrc`文件。”

很切题,就是这个了。马上在原来改过的源码上面继续改,取消了 SSH_SOURCE_BASHRC这个宏定义的注释,重新编译,果然生效了,这些远程登录会加载~/.bashrc这个变量了。继续完善结论:

远程ssh执行命令加入日志方法:

6系列机器:

在原先更改过bash源码的基础上,在源码根目录config-top.h文件里面再加入#define SSH_SOURCE_BASHRC(原先有这个值,只是注释掉了),然后重新编译bash 

5系列和6系列机器把原先/etc/profile里面的从pid=$PPID开始到/bin/rm /tmp/key.log.$pid这段剪切到/etc/bash_4399,在/etc/profile加入. /etc/bash_4399,再统一在/etc/bashrc里面加入test -z “$BASH_EXECUTION_STRING” || { test -f /etc/bash_4399 && . /etc/bash_4399; logger -t -bash -s “HISTORY $SSH_CLIENT USER=$NAME_OF_KEY CMD=$BASH_EXECUTION_STRING ” >/dev/null 2>&1;}

 

这样就完成了远程执行命令的ssh记录

为了让记录发送到远程服务器上面,我们在syslog里面加内容:

5系列是在 /etc/syslog.conf

6系列是在/etc/rsyslog.conf

里面加入 *.* @IP

重启syslog或者rsyslog服务就可以了,远程日志服务器自己搭建~

 

总结最后修改内容:

修改bash源码。

在最原始的/etc/profile里面加入

 test -f /etc/bash_4399 && ./etc/bash_4399
 
/etc/bash_4399 那个文件内容:
pid=$PPID
#在自己home目录得到所有的key,如果/var/log/key 没有的时候,添加进去
while read line
do
grep "$line" /var/log/key >/dev/null || echo "$line" >> /var/log/key
done < $HOME/.ssh/authorized_keys
#得到每个key的指纹
cat /var/log/key | while read LINE
do
                NAME=$(echo $LINE | awk '{print $3}')
echo $LINE >/tmp/key.log.$pid
                KEY=$(ssh-keygen -l -f /tmp/key.log.$pid | awk '{print $2}')
grep  "$KEY $NAME"  /var/log/ssh_key_fing >/dev/null || echo "$KEY $NAME" >> /var/log/ssh_key_fing
done
#如果是root用户,secure文件里面是通过PPID号验证指纹
if [ $UID == 0 ]
then
ppid=$PPID
else
#如果不是root用户,验证指纹的是另外一个进程号
ppid=`/bin/ps -ef | grep $PPID |grep 'sshd:' |awk '{print $3}'`
fi
#得到RSA_KEY和NAME_OF_KEY,用来bash4.1得到历史记录
RSA_KEY=`/bin/egrep 'Found matching RSA key' /var/log/secure|/bin/egrep "$ppid"|/bin/awk '{print $NF}'|tail -1`
 if [ -n "$RSA_KEY" ];then
NAME_OF_KEY=`/bin/egrep "$RSA_KEY" /var/log/ssh_key_fing|/bin/awk '{print $NF}'`
fi
#把NAME_OF_KEY设置为只读
readonly NAME_OF_KEY
export NAME_OF_KEY
/bin/rm /tmp/key.log.$pid

/etc/bashrc追加一行

test -z "$BASH_EXECUTION_STRING" || { test -f /etc/bash_4399 && . /etc/bash_4399; logger -t -bash -s "HISTORY $SSH_CLIENT USER=$NAME_OF_KEY CMD=$BASH_EXECUTION_STRING " >/dev/null 2>&1;}

Linux审计功能实现的两种实现方式:由于当前网络安全很问题很突出,黑客们又都很强大,不怕外患,像小公司不足被黑客盯住,就怕内忧,历史记录怎么能少呢?linux系统本身虽然提供了历史命令的记录功能(记录在:~/.bash_history),默认也能记录1000条之多,但是容易被清除,依然不安全,所以需要采取别的方式记录下服务器操作的日志,这样"坏蛋"将无处可遁,下面将介绍两种简单的实现方式:

第一种:

#将下面这段内容添加在/etc/profile文件末尾,完事后执行source /etc/profile使之生效。

HISTSIZE=1000
HISTTIMEFORMAT="%Y/%m/%d %T ";export HISTTIMEFORMAT
export HISTORY_FILE=/var/log/audit.log
export PROMPT_COMMAND='{ thisHistID=`history 1|awk "{print \\$1}"`;lastCommand=`history 1| awk "{\\$1=\"\" ;print}"`;user=`id -un`;whoStr=(`who -u am i`);realUser=${whoStr[0]};logMonth=${whoStr[2]};logDay=${whoStr[3]};logTime=${whoStr[4]};pid=${whoStr[6]};ip=${whoStr[7]};if [ ${thisHistID}x != ${lastHistID}x ];then echo -E `date "+%Y/%m/%d %H:%M:%S"` $user\($realUser\)@$ip[PID:$pid][LOGIN:$logMonth $logDay $logTime] --- $lastCommand ;lastHistID=$thisHistID;fi; } >> $HISTORY_FILE'

#然后便可查看是否生效了呢?

[root@test2 ~]# cat /var/log/audit.log
2015/04/14 14:18:42 root(root)@[PID:(192.168.101.110)][LOGIN:2015-04-14 14:18 .] --- 2015/04/09 09:22:57 cat /etc/sysctl.conf
2015/04/14 14:19:16 root(root)@[PID:(192.168.101.110)][LOGIN:2015-04-14 14:18 .] --- 2015/04/14 14:19:16 cd /usr/local/nginx/conf/sites-enabled/
2015/04/14 14:19:17 root(root)@[PID:(192.168.101.110)][LOGIN:2015-04-14 14:18 .] --- 2015/04/14 14:19:17 ll
2015/04/14 14:19:27 root(root)@[PID:(192.168.101.110)][LOGIN:2015-04-14 14:18 .] --- 2015/04/14 14:19:27 cat awstats.conf
2015/04/14 14:21:04 root(root)@[PID:(192.168.101.110)][LOGIN:2015-04-14 14:18 .] --- 2015/04/14 14:21:04 cat /etc/profile


第二种:

#将下面这段内容添加在/etc/profile文件末尾,完事后执行source /etc/profile使之生效。

function log2syslog{
declare command
command=$(fc -ln -0)
logger -p local1.notice -t bash -i — $SSH_CLIENT :$USER : $command
}
trap log2syslog DEBUG
[root@test2 u1]# tail -f -n100 /var/log/messages
Aug 16 18:22:36 test2 bash[4460]: — 192.168.101.116 63383 22 :root : vim /etc/profile

第二种方式目前有一个缺陷就是每次记录的命令,同一条会出现多次,这是待完善的地方。
#哈哈,就算"坏蛋"执行了history -c命令,他的犯罪记录也不会被抹杀掉的,这就叫做"要想人不知,除非己莫为",不要当坏蛋哦~~~

标签:none

添加新评论 »

分类
最新文章
最近回复
  • 轨迹: niubility!
  • 没穿底裤: 好办法..
  • emma: 任务计划那有点小问题,调用后Activation.exe不是当前活动窗口,造成回车下一步下一步...
  • 没穿底裤: hook execve函数
  • tuhao lam: 大佬,还有持续跟进Linux命令执行记录这块吗?通过内核拦截exec系统调用的方式,目前有没有...