Linux Shell命令审计

  1. 一种简单的bash审计方法
    1. 交互式bash记录命令
    2. 非交互式bash记录命令
    3. 白名单
    4. 使用Garylog2实现在前端页面展示数据

一种简单的bash审计方法

在高交互蜜罐环境中,ssh服务允许攻击者成功由ssh登录并获取到一个可进行各种操作的真实shell,同时需要记录shell中所有操作。严谨的方案是在bash源码及内核模块中做审计记录。这里给出另一种娱乐性质的方案,简单而tricky的方式达成该目标的基本功能,当然这种方案在了解的人面前很容易被绕过。

本文参考环境为Ubuntu 18.01

这里记录shell执行命令的核心是bash内建的trap命令,trap可以看作bash的信号处理,同时有一些虚拟信号,这里要使用的是DEBUG信号。trap arg DEBUG执行后,arg命令将在每一个命令执行前被执行,同时可以从变量BASH_COMMAND中读取到将要执行的命令。参考man trapman bash

交互式bash记录命令

先看一下例子,log_command中可以替换为其他脚本,将记录到的命令发送到远程服务器并保存。这里仅简单保存日志文件。

function log_command()
{
	echo -e "\e[33m$(date +"%Y-%m-%d %H:%M:%S")\e[0m" ["\e[01;35m`whoami`\e[0m""@""\e[01;36m$(who am i | awk "{print \$2\"\"\$5}")\e[0m":"\e[01;32m`pwd`\e[0m"]# $BASH_COMMAND >> /var/log/history.log
}

unset PROMPT_COMMAND

trap 'log_command' DEBUG

试验一下

[root@GuangZhou:sbin]# source /usr/local/sbin/cli.sh 
[root@GuangZhou:sbin]# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Apr04 ?        00:12:43 /sbin/init
root         2     0  0 Apr04 ?        00:00:00 [kthreadd]
root         4     2  0 Apr04 ?        00:00:00 [kworker/0:0H]
root         6     2  0 Apr04 ?        00:00:00 [mm_percpu_wq]
root         7     2  0 Apr04 ?        00:01:51 [ksoftirqd/0]
root         8     2  0 Apr04 ?        00:01:57 [rcu_sched

看看输出

[root@GuangZhou:sbin]# tail -n2 /var/log/history.log 
2021-05-10 17:16:27 [root@pts/0(192.168.21.254):/usr/local/sbin]# ps -ef
2021-05-10 17:16:59 [root@pts/0(192.168.21.254):/usr/local/sbin]# tail -n2 /var/log/history.log

为了让每个bash都能生效,在/etc/bashrc文件末尾增加一行. /etc/bash_logger.sh,这样在默认配置下每一个交互式bash都会加载新增的文件(除非通过参数指定bash不加载配置文件)。

非交互式bash记录命令

上一个例子里可以记录到交互式bash中调用的命令,那么再试一下通过bash执行的脚本文件是否可以记录到脚本文件内调用的命令,结果是无法记录。因为trap只能影响到当前bash,通过bash SCRIPT_NAMEbash -c COMMAND会启动一个子进程,执行非交互式bash,不会加载之前准备的配置文件。

之前说过非交互式bash会检查环境变量BASH_ENV并尝试加载其指定的文件。因此在配置文件中再增加BASH_ENV环境变量。

function log_command()
{
	echo -e "\e[33m$(date +"%Y-%m-%d %H:%M:%S")\e[0m" ["\e[01;35m`whoami`\e[0m""@""\e[01;36m$(who am i | awk "{print \$2\"\"\$5}")\e[0m":"\e[01;32m`pwd`\e[0m"]# $BASH_COMMAND >> /var/log/history.log
}

    unset PROMPT_COMMAND
    export BASH_ENV=/usr/local/sbin/cli.sh


trap 'log_command' DEBUG

重启一个bash,验证一下

[root@GuangZhou:~]# cat test.sh 
echo 1
echo 2
[root@GuangZhou:~]# bash test.sh 
1
2
[root@GuangZhou:~]# 

看看日志

2021-05-10 17:20:14 [root@pts/0(192.168.21.254):/root]# bash test.sh
2021-05-10 17:20:14 [root@pts/0(192.168.21.254):/root]# echo 1
2021-05-10 17:20:14 [root@pts/0(192.168.21.254):/root]# echo 2
2021-05-10 17:20:36 [root@pts/0(192.168.21.254):/root]# cat /var/log/history.log

可以看到没有函数内部的调用记录。想要这部分记录也很简单,在配置文件中增加一行set -o functrace,参考man setman bash

白名单

经过上面的步骤,已经可以记录bash和sh调用的所有命令了,但是由于linux中大量使用shell做一些辅助工作,因此很多时候会有大量冗余的调用记录。比如执行一下man bash,就会在日志文件中看到数十行调用记录,这里可以通过增加白名单的方式把明显无害的调用排除出记录范围。下面是进行了一定优化之后的脚本。

export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "
export HISTCONTROL=ignoredups:erasedups

# 排除自动加载等无用命令
EXCLUDE_CMDS=("true" "mesg" "unset" "test" "[")

function is_excluded_command() {
    for cmd in "${EXCLUDE_CMDS[@]}"; do
        [[ "$BASH_COMMAND" == "$cmd"* ]] && return 0
    done
    [[ -z "$BASH_COMMAND" || "$BASH_COMMAND" == " " ]] && return 0
    return 1
}

function log_command() {
    is_excluded_command && return

    local ts user tty path
    ts=$(date +"%Y-%m-%d %H:%M:%S")
    user=$(whoami)
    # 提取终端和来源IP,并加上主机名
    tty=$(who am i | awk -v host="$(hostname)" '{gsub(/[()]/,"",$NF); print host":"$2"("$NF")"}')
    path=$(pwd)

    echo -e "\e[33m${ts}\e[0m [\e[01;35m${user}\e[0m@\e[01;36m${tty}\e[0m:\e[01;32m${path}\e[0m]# $BASH_COMMAND" >> /var/log/history.log
}

unset PROMPT_COMMAND
export BASH_ENV=/etc/profile.d/cli.sh
#set -o functrace
trap 'log_command' DEBUG

使用Garylog2实现在前端页面展示数据

之前都是将命令记录输出到/var/log/history.log,其实可以配合Garylog2一起使用,一些高危命令操作可以方便展示在web上,能及时监控,下面是一个简单的示例

  1. 脚本代码

    export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "
    export HISTCONTROL=ignoredups:erasedups
    
    # 排除自动加载等无用命令
    EXCLUDE_CMDS=("true" "mesg" "unset"  "test" "[")
    
    function is_excluded_command() {
        for cmd in "${EXCLUDE_CMDS[@]}"; do
            [[ "$BASH_COMMAND" == "$cmd"* ]] && return 0
        done
        [[ -z "$BASH_COMMAND" || "$BASH_COMMAND" == " " ]] && return 0
        return 1
    }
    
    function log_command() {
        is_excluded_command && return
    
        local ts user tty path
        ts=$(date +"%Y-%m-%d %H:%M:%S")
        user=$(whoami)
        # 从 who am i 提取 pts/0 和 IP
        tty=$(who am i | awk '{gsub(/[()]/,"",$NF); print $2"("$NF")"}')
        path=$(pwd)
    
        echo -e "\e[33m${ts}\e[0m [\e[01;35m${user}\e[0m@\e[01;36m${tty}\e[0m:\e[01;32m${path}\e[0m]# $BASH_COMMAND" >> /var/log/history.log
    }
    
    unset PROMPT_COMMAND
    export BASH_ENV=/etc/profile.d/cli.sh
    #set -o functrace
    trap 'log_command' DEBUG
    
  2. 我们Ctrl + D 断开当前ssh连接,在重新登陆之后查看以下syslog日志:

    tail -f /var/log/messages
    

    能看到有audit_log关键字的命令记录了,说明脚本已经生效了。

    2020-04-02 10:15:22 graylog bash2syslog[25424]: audit_log,graylog.server.local,192.168.254.102,root,32298,192.168.254.133 56701 22,/etc/yum.repos.d,tail -f /var/log/messages
    
  3. 返回到graylog2中,找到刚才关键字为tail的那条message,创建解析器,选中Copy input

  1. 条件Condition中选择正则匹配,填入高危命令关键字的正则表达式,并创建新的risk_cmd字段。

    (.+:.+:.+:|rm .+rf|dd .+|fdisk .+|\/dev.+|mkfs.+|wget.+|curl.+|ftp.+|sftp.+|mv .+|kill .+|.+ restart|.+ reload)
    

    然后在菜鸟工具中检测一下:

  2. 创建一个名为risk_cmd的Stream,条件是Field risk_cmd must be present。

  3. 在grafana添加一条数据源

  4. 创建审计看板:


转载请注明来源