监控提权命令之audit

发布时间 2023-09-12 23:33:51作者: windysai
监控提权命令之audit
  这个需求源于8月份的系统安全加固的内容,一开始领导希望能找到工具对linux系统日志进行分析,其中一个是提权指令记录及告警。这两天经过我跟ai的努力,还有今天前公司运维朋友的鼎力相助,算是完工了。
  先看看最终效果,我用了两个用户去运行sudo 指令进行测试,然后告警:

    利用了linux自带的audit审计。老实说,我对这个东西挺陌生的,这两天差点把我劝退。。。一开始想用inotifywait去做,发现只能记录root的,然后想到用户家目录下的.bash_history去拿最近日志,感觉还是不够audit靠谱,所以硬着头皮研究吧。

  提下关键点,下面脚本很多都是ai写的(尤其它写的正则,真是自叹不如),当然我也花了很多功夫帮它纠正各种代码逻辑,验证它写的内容,算是互相成就吧,哈哈哈~~不然自己写可能不止搞一天哦,本人代码能力比较弱。

1、预先设置含关键字的audit规则,然后才能记录到审计日志

  centos 7 编辑这个文件 /etc/audit/rules.d/audit.rules ,规则内容如下(-k 标识记录日志的关键字,下面搜索会用到),标识记录普通用户运行sudo时的审计日志。

-a always,exit -F arch=b64 -S execve -F uid!=0 -F exe=/usr/bin/sudo -k sudo-exec
-a always,exit -F arch=b64 -S execve -F uid!=0 -F exe=/bin/sudo -k sudo-exec

-a always,exit -F arch=b64 -S execve -F uid!=0 -F exe=/bin/su -k sudo-exec
-a always,exit -F arch=b64 -S execve -F uid!=0 -F exe=/usr/bin/su -k sudo-exec

  设置完规则需要重启audit服务,还有如果写的规则有问题,会发现“auditctl -l” 是没有规则的,所以最好写入这个文件前,命令行先验证规则是否正确,例如:

auditctl -a always,exit -F arch=b64 -S execve -C euid=0 -F euid!=0 -k sudo-exec

   另一个问题是ai一直给我写条件规则:euid!=0来排除root用户,这个是错误的,需要用uid去判断

2、提取关于提权命令的日志

  一开始我看/var/log/audit/audit.log,可读性根本不是人类能够适应的。然后找到工具ausearch,能使用关键字来搜索日志记录,格式类似这样:“ausearch -k 关键字”

然后发现满屏都是这样的日志,所以要用搜索时间去限制。目前找到的最小粒度是最近10分钟内的日志。写法是:

ausearch --start recent --end now -k 'sudo-exec'

   老实说,用ausearch搜的日志比直接在audit.log可读性强很多,每个日志段用“----”分割,然后需要从每个日志段提取我们需要的内容。

 3、日志分析

  包括字段提取,和数组存值。日志段我们需要用数组去存下来,因为后续要遍历进行钉钉告警。除此我们需要处理以下的点

(1)time->Tue Sep 12 20:44:34 2023,需要利用时间戳来转格式

(2)提取命令在argc{n}这列,需要拼接a0~an-1里面“=”的内容

(3)提取操作用户,根据uid的值,读取/etc/passwd 去匹配

4、钉钉告警用json拼接消息

  这个大家直接看脚本,我以前那种写法报警不了,可能是因为操作命令里有空格导致的,这个有机会再研究

5、放到定时任务搜索日志内容为空

  ausearch搜关键字日志在命令行执行可以重定向到日志文件,但是放到定时任务竟然为空,全靠前公司运维同事的醍醐灌顶,结合“--input-logs”管道去处理,真是万分感激。

  1 #!/bin/bash
  2 
  3 # 定义空数组来存储事件数据
  4 timestamps=()
  5 users=()
  6 commands=()
  7 cwds=()
  8 
  9 ausearchlog=/root/ausearch.log
 10 
 11 DATE=`date +%F_%T`
 12 >$ausearchlog
 13 # 找到10分钟前到现在的日志并重定向输出到文件
 14 # 写不进去日志: /sbin/ausearch --start recent --end now -k 'sudo-exec' > $ausearchlog 2>&1 
 15 # 搜不到内容,可能跟时间差有关: /sbin/ausearch --start recent --end now -k 'sudo-exec' -f /var/log/audit/audit.log > $ausearchlog
 16 ### 可用写法
 17 /sbin/ausearch --start recent --end now -k 'sudo-exec' --input-logs |tee -a $ausearchlog 
 18 
 19 
 20 # 循环读取日志文件
 21 while IFS= read -r line; do
 22   # 提取时间:
 23   if [[ $line =~ ([a-zA-Z]{3} [a-zA-Z]{3} [0-9]{1,2} [0-9:]{8} [0-9]{4}) ]]; then
 24     timestamp="${BASH_REMATCH[1]}"
 25     # 转换时间格式
 26     formatted_timestamp=$(date -d "$timestamp" +%Y-%m-%d_%H:%M:%S)
 27     timestamps+=("$formatted_timestamp")
 28     echo "时间: $formatted_timestamp" 
 29   fi
 30 
 31   # 提取操作用户所在目录:
 32   if [[ $line == *"type=CWD"* ]]; then
 33     cwd=$(echo "$line" | awk -F"cwd=" '{print $2}' | awk '{print $1}' | tr -d '"' )
 34     cwds+=("$cwd")
 35     echo "用户当前所在目录:$cwd"
 36   fi
 37 
 38   # 提取操作命令:
 39   if [[ $line =~ argc=([0-9]+) ]]; then
 40     argc="${BASH_REMATCH[1]}"
 41     command_args=()
 42 
 43     # 使用循环提取 a0 到 an-1 的内容
 44     for ((i = 0; i < argc; i++)); do
 45       arg_name="a$i"
 46       if [[ $line =~ $arg_name=\"([^\"]+)\" ]]; then
 47         arg_value="${BASH_REMATCH[1]}"
 48         command_args+=("$arg_value")
 49       fi
 50     done
 51 
 52     # 将命令参数拼接,并以空格分隔
 53     command="${command_args[*]}"
 54     echo "运行的命令: $command"
 55 
 56     # 添加命令到数组
 57     commands+=("$command")
 58   fi
 59   
 60   # 提取操作用户:
 61   if [[ $line == *"type=SYSCALL"* ]]; then
 62     #userid=$(echo "$line" | awk -F"uid=" '{print $2}' | awk '{print $1}' )
 63     userid=$(echo "$line" | grep -o '\buid=[0-9]\+\b' | awk -F'=' '{print $2}')
 64     echo "userid = $userid"
 65     user=$(grep "$userid" /etc/passwd | cut -d: -f1)
 66     users+=("$user")
 67     echo "操作用户:$user"
 68   fi
 69   
 70   # 分隔符行,调试每个日志段时为了好看
 71   if [[ $line == "----" ]]; then
 72     echo ""
 73   fi
 74 done < $ausearchlog
 75 
 76 # 统计事件数量
 77 cnt=${#timestamps[@]}
 78 echo "事件数量: $cnt"
 79 
 80 DATE=`date +%F_%T`
 81 TOKEN="钉钉机器人token"
 82 PHONE="报警人电话"
 83 
 84 # 输出事件信息
 85 for ((i = 0; i < $cnt; i++)); do
 86   timestamp="${timestamps[$i]}"
 87   user="${users[$i]}"
 88   command="${commands[$i]}"
 89   cwd="${cwds[$i]}"
 90   echo "(for循环)时间: $timestamp, 用户: $user, 命令: $command, 当前目录:$cwd" >> $aulog
 91 
 92 
 93   
 94   #钉钉报警  --- 不行,以前我经常用这种方式
 95   #curl -H "Content-Type:application/json" -X POST --data '{"msgtype":"text","text":{"content":"服务器:lt701\n当前时间:'$DATE'\n操作时间:'$timestamp'\n操作用户:'$user'\n操作命令:'${command}'\n当前用户所在目录:'$cwd'"} , "at": {"atMobiles": ['${PHONE}'], "isAtAll":false }}' ${TOKEN} > /dev/null 2>&1
 96 
 97 # 构建 JSON 字符串并将结果保存到 json_msg 变量中,需要事先安装jq
 98 json_msg=$(/usr/local/bin/jq -n \
 99   --arg DATE "$DATE" \
100   --arg command "$command" \
101   --arg PHONE "$PHONE" \
102  '{
103     "msgtype": "text",
104     "text": {
105       "content": "服务器:lt701\n当前时间:\($DATE)\n操作时间:'$timestamp'\n操作用户:'$user'\n操作命令:\($command)\n当前用户所在目录:'$cwd'"
106     },
107     "at": {
108       "atMobiles": [($PHONE | tonumber)],
109       "isAtAll": false
110     }
111   }')
112 
113 ## 使用 curl 发送 JSON 消息
114 curl -H "Content-Type: application/json" -X POST --data "$json_msg" "$TOKEN" > /dev/null 2>&1
115 
116 done
View Code