rule编写
任务目标
- 查看modsec拦截是是否匹配到相关rule,如果有其id是多少,没有则自定义一条
- 或扩展上面为 同一IP 60秒内被拦截20次自动封禁
- 添加集成,限定相关组,或rule id 实现封禁名单通知 飞书
- 后续自动化封禁IP
找到modsecurity拦截的规则
id 30411
level 7
可使用搜索:
rule.level: 7


新增规则同一IP60秒内ban10次
通过查找官网定义的例子进行编写
参考: https://documentation.wazuh.com/current/user-manual/ruleset/rules/custom.html
cd /var/ossec/ruleset/rules
grep -rn "same" ./ # 相同IP的语法
grep -rn frequency ./ # 频率
if_matched_sid 如果使用频率的话必须要matched
same_field 相同字段
完成
注意rule id 不可重复 100001已存在故改为110001
<group name="modsec,">
<rule id="110001" level="12" frequency="5" timeframe="60">
<if_matched_sid>30411</if_matched_sid>
<same_field>client</same_field>
<description>同ip 60秒 5次 拦截</description>
</rule>
</group>


飞书通知
参考:
https://wazuh.com/blog/how-to-integrate-external-software-using-integrator/
https://blog.csdn.net/wanganmuzi/article/details/148555367
前言
经过上面的规则编写,同ip 60秒 5次 拦截的进行一个规则告警id 110001

开启integration日志
后续脚本执行的问题都在这里排查
vi /var/ossec/etc/internal_options.conf
integrator.debug=2 # 开启integrator
systemctl restart wazuh-manager
此时可以在/var/ossec/logs/ossec.log 中查看integrator相关执行日志
如没安装requests库

新增integrator
注意
-
要在ossec_config中
-
这里限定使用最少的。额外可选的如level、group规则触发 参考链接 https://documentation.wazuh.com/current/user-manual/reference/ossec-conf/integration.html#integration-options
-
custom-feishu中
<integration> <name>custom-feishu</name> <rule_id>110001</rule_id> <alert_format>json</alert_format> <hook_url>填你的飞书webhook链接</hook_url> </integration>custom-必不可少,后面的可以自己修改
vi /var/ossec/etc/ossec.conf

新增可执行脚本
alert的json日志
/var/ossec/logs/alerts/alerts.json
找到你要触发的规则 ,比如我的规则是110001
{"timestamp":"2026-01-05T00:10:32.178+0800","rule":{"level":12,"description":"同ip 60秒 5次 拦截","id":"110001","frequency":5,"firedtimes":1,"mail":true,"groups":["modsec"]},"agent":{"id":"001","name":"c8-1","ip":"10.20.0.147"},"manager":{"name":"c8-50g"},"id":"1767543032.74677","previous_output":"[Mon Jan 05 00:12:57.298633 2026] [:error] [pid 1533445:tid 139845762520832] [client 10.20.0.81:56484] [client 10.20.0.81] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file \"/etc/httpd/modsecurity.d/activated_rules/REQUEST-949-BLOCKING-EVALUATION.conf\"] [line \"153\"] [id \"949110\"] [msg \"Inbound Anomaly Score Exceeded (Total Score: 8)\"] [severity \"CRITICAL\"] [ver \"OWASP_CRS/3.3.4\"] [tag \"application-multi\"] [tag \"language-multi\"] [tag \"platform-multi\"] [tag \"attack-generic\"] [hostname \"10.20.0.147\"] [uri \"/catalog-portal/ui/oauth/verify\"] [unique_id \"aVqRiSz2c2hIU6C2KKXCHQAAAEc\"]\n[Mon Jan 05 00:12:57.264398 2026] [:error] [pid 1533445:tid 139845745735424] [client 10.20.0.81:56494] [client 10.20.0.81] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file \"/etc/httpd/modsecurity.d/activated_rules/REQUEST-949-BLOCKING-EVALUATION.conf\"] [line \"153\"] [id \"949110\"] [msg \"Inbound Anomaly Score Exceeded (Total Score: 13)\"] [severity \"CRITICAL\"] [ver \"OWASP_CRS/3.3.4\"] [tag \"application-multi\"] [tag \"language-multi\"] [tag \"platform-multi\"] [tag \"attack-generic\"] [hostname \"10.20.0.147\"] [uri \"/${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(\\\\\"id\\\\\").getInputStream(),\\\\\"utf-8\\\\\")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader(\\\\\"X-Cmd-Response\\\\\",#a))}/\"] [unique_id \"aVqRiSz2c2hIU6C2KKXCHAAAAEk\"]\n[Mon Jan 05 00:12:57.110959 2026] [:error] [pid 1533666:tid 139845678593792] [client 10.20.0.81:56454] [client 10.20.0.81] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file \"/etc/httpd/modsecurity.d/activated_rules/REQUEST-949-BLOCKING-EVALUATION.conf\"] [line \"153\"] [id \"949110\"] [msg \"Inbound Anomaly Score Exceeded (Total Score: 18)\"] [severity \"CRITICAL\"] [ver \"OWASP_CRS/3.3.4\"] [tag \"application-multi\"] [tag \"language-multi\"] [tag \"platform-multi\"] [tag \"attack-generic\"] [hostname \"10.20.0.147\"] [uri \"/plus/weixin.php\"] [unique_id \"aVqRiapBCZNoRJDlGtzGFQAAANE\"]\n[Mon Jan 05 00:12:57.087256 2026] [:error] [pid 1533666:tid 139845712164608] [client 10.20.0.81:56468] [client 10.20.0.81] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file \"/etc/httpd/modsecurity.d/activated_rules/REQUEST-949-BLOCKING-EVALUATION.conf\"] [line \"153\"] [id \"949110\"] [msg \"Inbound Anomaly Score Exceeded (Total Score: 18)\"] [severity \"CRITICAL\"] [ver \"OWASP_CRS/3.3.4\"] [tag \"application-multi\"] [tag \"language-multi\"] [tag \"platform-multi\"] [tag \"attack-generic\"] [hostname \"10.20.0.147\"] [uri \"/actuator/gateway/routes/iwtcoaro\"] [unique_id \"aVqRiapBCZNoRJDlGtzGFgAAAM0\"]","full_log":"[Mon Jan 05 00:12:57.634304 2026] [:error] [pid 1533666:tid 139845653415680] [client 10.20.0.81:56454] [client 10.20.0.81] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file \"/etc/httpd/modsecurity.d/activated_rules/REQUEST-949-BLOCKING-EVALUATION.conf\"] [line \"153\"] [id \"949110\"] [msg \"Inbound Anomaly Score Exceeded (Total Score: 8)\"] [severity \"CRITICAL\"] [ver \"OWASP_CRS/3.3.4\"] [tag \"application-multi\"] [tag \"language-multi\"] [tag \"platform-multi\"] [tag \"attack-generic\"] [hostname \"10.20.0.147\"] [uri \"/index.php\"] [unique_id \"aVqRiapBCZNoRJDlGtzGFwAAANQ\"]","decoder":{"parent":"apache-errorlog","name":"apache-errorlog"},"data":{"id":"949110","client":"10.20.0.81","file":"REQUEST-949-BLOCKING-EVALUATION","msg":"Inbound Anomaly Score Exceeded (Total Score: 8)"},"location":"/var/log/httpd/error_log"}
脚本编写方法
参考 https://wazuh.com/blog/how-to-integrate-external-software-using-integrator/ 中的
alert_file = open(sys.argv[1]) # 对应上面json日志
api_key = sys.argv[2].split(':')[1] # ossec.conf定义的,我上面没有定义,具体可以参考官方的链接
hook_url = sys.argv[3] # ossec.conf定义的,具体可以参考官方的链接,也可以直接写死在python文件里面
AI提示词
(贴上json日志)
帮我写一个python脚本,alert_file = open(sys.argv[1]) alert_file为上方的json日志 hook_url = sys.argv[3] hook_url为飞书的机器人
要求提取data.client msg time agentname ruleid full_log,字段
使用requests库进行发送
custom-feishu
注意
-
脚本文件权限一定为: 750 root:wazuh
-
#!/usr/bin/env python3# -*- coding: utf-8 -*-这个一定不能少 -
检查是否有python 与requests库
vi /var/ossec/integrations/custom-feishu#!/usr/bin/env python3
-- coding: utf-8 --
import json
import sys
import requests
import re
from datetime import datetime使用方式:
python script.py alert.json your_api_key:xxx https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx
if len(sys.argv) < 4:
print("用法: python script.py <alert_json_file> <api_key:xxx> <feishu_hook_url>")
sys.exit(1)alert_file = sys.argv[1]
hook_url = sys.argv[3] # 飞书 Webhook 地址读取 JSON 文件
try:
with open(alert_file, 'r', encoding='utf-8') as f:
alert = json.load(f)
except Exception as e:
print(f"读取文件失败: {e}")
sys.exit(1)提取核心字段
data_client = alert.get('data', {}).get('client', 'N/A')
msg = alert.get('data', {}).get('msg', 'N/A')
rule_id = alert.get('rule', {}).get('id', 'N/A')
agent_name = alert.get('agent', {}).get('name', 'N/A')
full_log = alert.get('full_log', '')提取时间(优先从 full_log 里解析 apache 日志时间,更精确)
time_str = '未知时间'
if full_log:
# 匹配类似 [Mon Jan 05 20:48:51.424887 2026]
match = re.search(r'[([A-Za-z]{3} [A-Za-z]{3} \d{2} \d{2}:\d{2}:\d{2}.\d+ \d{4})]', full_log)
if match:
time_str = match.group(1)
else:
# 回退使用 timestamp
ts = alert.get('timestamp')
if ts:
try:
dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
time_str = dt.strftime('%Y-%m-%d %H:%M:%S %z')
except:
time_str = ts
else:
ts = alert.get('timestamp')
if ts:
try:
dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
time_str = dt.strftime('%Y-%m-%d %H:%M:%S %z')
except:
time_str = ts构造飞书富文本卡片(红色高危样式)
payload = {
"msg_type": "interactive",
"card": {
"config": {
"wide_screen_mode": True,
"enable_forward": True
},
"header": {
"template": "red",
"title": {
"tag": "plain_text",
"content": "ModSecurity 拦截 - 高危 SQL 注入尝试"
}
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": f"规则ID:{rule_id} (同IP 60秒内 ≥5次触发)"
}
},
{
"tag": "hr"
},
{
"tag": "div",
"fields": [
{
"is_short": True,
"text": {
"tag": "lark_md",
"content": f"时间\n{time_str}"
}
},
{
"is_short": True,
"text": {
"tag": "lark_md",
"content": f"攻击IP\n{data_client}"
}
}
]
},
{
"tag": "div",
"fields": [
{
"is_short": True,
"text": {
"tag": "lark_md",
"content": f"Agent\n{agent_name}"
}
},
{
"is_short": True,
"text": {
"tag": "lark_md",
"content": "异常分数\n8 (≥5 拦截)"
}
}
]
},
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": f"告警详情\n{msg}"
}
},
{
"tag": "note",
"elements": [
{
"tag": "plain_text",
"content": f"日志片段:{full_log[:180]}{'...' if len(full_log) > 180 else ''}"
}
]
}
]
}
}发送到飞书
try:
headers = {'Content-Type': 'application/json'}
resp = requests.post(hook_url, json=payload, headers=headers, timeout=10)
if resp.status_code == 200:
result = resp.json()
if result.get('code') == 0:
print("飞书消息发送成功")
else:
print(f"飞书返回错误: {result}")
else:
print(f"HTTP {resp.status_code}: {resp.text}")
except Exception as e:
print(f"发送失败: {e}")
[root@c8-50g integrations]#
AI生成的标题,下次再改
