改BASH源码-实现BASH命令操作审计

作者: 康康 分类: 企业安全 发布时间: 2020-06-19 23:44

一、需求背景

本次介绍的思路是修改bash源代码并重编译,以将bash执行的命令等信息输出到远程syslog服务器用于审计。有朋友可能会觉得,有了堡垒机为啥还需要这个?不妨想想以下场景:

  • 有人绕过了堡垒机。想知道他都在服务器上干啥了麽?
  • 有攻击者通过外网RCE漏洞直接在服务器上执行命令。想知道攻击者都执行什么了吗,有没有干坏事?

这就体现出bash命令审计的价值,即便攻击者登录系统后,通过删除history记录等方式抹掉了操作行为。安全工程师在入侵溯源和应急响应过程中仍然能够获得完整的原始日志。这是一个纵深防御思路,可在安全建设中取长补短。同时这个思路相比logger等方案占用资源也更少

二、修改过程

在这里下载一份源码,本文以bash 4.4为例介绍。 https://ftp.gnu.org/gnu/bash/

解压以后编辑bashhist.c文件。

1.日志增加当前主机hostname字段信息

加hostname是为了能够区分不同的主机。在bashhist.c 中,在bash_syslog_history中定义current_host_name并赋值。增加代码如下(注释包裹):

 755 void
 756 bash_syslog_history (line)
 757      const char *line;
 758 {
 759   /*  add begain,add current_host_name */
 760   char hostname[256];
 761   char *current_host_name = (char *)NULL;
 762   if (current_host_name == 0)
 763     {
 764       /* Initialize current_host_name. */
 765       if (gethostname (hostname, 255) < 0)
 766     current_host_name = "??host??";
 767       else
 768     current_host_name = savestring (hostname);
 769     }
 770   /*add end */

2.日志增加当前用户名信息

该字段是为了记录当前用户名,便于审计。修改syslog 打印信息,改为如下:增加current_user.username和current_host_name、pid、父进程pid、session id

 820   if (strlen(line) < SYSLOG_MAXLEN)
 821     syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "ACCORDING: HOST=%s   PID=%d PPID=%d SID=%d USER=%s CMD=%s", current_host_name, getpid(), getppid(), getsid(getpid()), current_user.user_name,remote_user, line);
 822   else
 823     {
 824       strncpy (trunc, line, SYSLOG_MAXLEN);
 825       trunc[SYSLOG_MAXLEN - 1] = '\0';
 826       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);
 827     }

3.修改syslog宏定义开关

将 #define SYSLOG_HISTORY两边的注释去掉,变为如下即可开启。

114 /* Define if you want each line saved to the history list in bashhist.c:
115    bash_add_history() to be sent to syslog(). */
116 #define SYSLOG_HISTORY
117 #if defined (SYSLOG_HISTORY)
118 #  define SYSLOG_FACILITY LOG_USER
119 #  define SYSLOG_LEVEL LOG_INFO
120 #  define OPENLOG_OPTS LOG_PID
121 #endif

4.编译

./configure –prefix=/usr/local/bash && make && make install

5.将bash替换掉原有的/bin/bash,并视情况禁用其他shell

这样的话用户只能用修改过的bash了,此处为了提高使用别的shell以绕过bash的门槛

6. 编辑syslog配置,远程转发日志

vim /etc/rsyslog.conf

# record bash history command 
bash.log  @@yoursyslogserver.domian

收集到的syslog效果如下:

/var/log/messages-20200607:May 31 17:33:58 localhost /usr/local/bash/bin/bash[1640]: ACCORDING: HOST=notesus  PID=1640 PPID=19532 SID=19532  USER=root  CMD=whoami

至此,修改bash源码的环节就结束了。分析syslog的环节不再赘述,可以借助使用ELK、splunk等大数据分析工具。

二、进阶修改:增加更多字段

当然,还可以打印出更多的字段信息,我自行修改后,达到的效果如下:

/var/log/messages-20200607:Jun 4 00:51:37 localhost /usr/local/bash/bin/bash[17443]: ACCORDING: HOST=notesus REMOTE_HOST=(124.65.9.45) REMOTE_TTY=/dev/pts/3 REMOTE_TIME= 2020-06-04 00:50 PID=17443 PPID=23763 SID=23763 USER=root REMOTE_USER=root CMD=cat /var/log/messages

这里我将用户登录的远程机器的host信息、远程登录的用户名、远程登录的时间和bash的文件描述符信息也打印出来了。

1.增加远程用户信息字段

谁登陆的bash?什么时间?IP或host是多少?这个信息有助于安全审计和溯源。我是通过将utmp(who命令)相关信息输出到环境变量,bash把取环境变量信息打到syslog,这个方法虽然曲线救国,但至少解决了问题。不过我相信既然选择了改动bash,就应该会有更优雅的方式获取该信息,先占坑,有空再慢慢研究。

先回顾一下bash环境变量加载顺序:

交互式登录的用户:
 /etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc
非交互式登录的用户:
  ~/.bash_profile --> ~/.bashrc --> /etc/bashrc --> /etc/profile.d/*.sh

综合实际攻击场景,我们选择在 /etc/profile.d/下进行添加:

ACCORDING_WHO_INFO=`who|sort -k3| tail -n1`
export ACCORDING_REMOTE_USER=`echo $ACCORDING_WHO_INFO |awk '{print $1}'`
export ACCORDING_REMOTE_HOSTNAME=`echo $ACCORDING_WHO_INFO|awk '{print $NF}'`
export ACCORDING_REMOTE_TIME=`echo $ACCORDING_WHO_INFO|awk -F 

修改bash源码,从环境变量获取信息:

/*add REMOTE USER INFO*/
const char *remote_hostname;
const char *remote_user;
const char *remote_time;
remote_hostname = getenv("ACCORDING_REMOTE_HOSTNAME");
remote_user = getenv("ACCORDING_REMOTE_USER");
remote_time = getenv("ACCORDING_REMOTE_TIME");
/*add end */
...
syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "ACCORDING: HOST=%s REMOTE_HOST=%s REMOTE_TTY=%s REMOTE_TIME=%s PID=%d PPID=%d SID=%d USER=%s REMOTE_USER=%s CMD=%s", current_host_name,remote_hostname,remote_time, getpid(), getppid(), getsid(getpid()), current_user.user_name,remote_user, line);

2.增加bash文件描述符信息字段

这有助于反弹shell检测。反弹shell的检测方式有很多,可以通过Audit(EXECVE系统调用)、netlink(内核与用户态的IPC)通信来围绕stdin/stdout监控。 众所周知, 正常用户上机执行命令,stdin/stdout指向终端(/dev/pts*)。 反弹shell的一大特征就是shell运行时stdin/stdout的文件描述符(fd)指向一个socket/pipe:

目前我通过向bash源码增加 remote_tty = (char *)ttyname (fileno (stdin));可以将stdin的指向信息打印出来,但还不完美。lsof命令源码应该有相关实现,有空再慢慢探索对stdin指向结构体类型做区分、输出到syslog。

本文参考了:
http://vinc.top/2017/08/03/%E3%80%90%E4%BC%81%E4%B8%9A%E5%AE%89%E5%85%A8%E5%AE%9E%E6%88%98%E3%80%91%E8%BF%90%E7%BB%B4bash%E5%91%BD%E4%BB%A4%E5%AE%A1%E8%AE%A1/

https://my.oschina.net/liujen/blog/78441

发表评论

电子邮件地址不会被公开。 必填项已用*标注