搭建自动封禁 WordPress 登录攻击的 Matomo + Apache 脚本
在管理 WordPress 网站时,wp-login.php 常常成为攻击目标。本文分享我如何利用 Matomo API 获取访问数据,并自动生成 Apache 封禁规则,实现 T+1 自动封禁恶意 IP。

1. 准备工作
1.1 获取 Matomo 访问 Token
1. 登录 Matomo 后台。
2. 打开 个人 → 设置 → 安全 生成验证令牌。

3. 保存 token,用于脚本调用 API。
1.2 确认 Apache 配置可用
- Apache 版本 ≥ 2.4。
- 确认 /etc/apache2/apache2.conf 中包含:
IncludeOptional conf-enabled/*.conf IncludeOptional sites-enabled/*.conf
- 确保能通过 apachectl graceful 重载配置。

2. Python 脚本实现
2.1 核心功能
- 调用 Matomo API 获取最近访问 wp-login.php 的 IP。
- 检查 IP 是否在白名单。
- 历史封禁 IP 累加,不覆盖。
- 打印日志至指定路径,方便记录。
2.2 完整脚本
matomo_block.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import requests import subprocess import ipaddress import os import traceback from datetime import datetime # ================= 配置 ================= MATOMO_API = "http://matomo.ruianding.com/index.php" SITE_ID = 1 TOKEN_AUTH = "{your_matomo_api_token}" BLOCK_CONF = "/etc/apache2/block_wp.conf" # Apache 全局封禁配置 LIMIT = 1000 # 拉取最近访问数 LOG_FILE = "matomo_block.log" HISTORY_FILE = "blocked_ips.txt" # 历史封禁 IP 文件 # 白名单 IP / 网段 (CIDR 支持) WHITELIST = [ "{白名单 IPV4 地址}" ] # ================= 函数 ================= def log(msg): timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") line = f"[{timestamp}] {msg}" print(line) with open(LOG_FILE, "a") as f: f.write(line + "\n") def ip_in_whitelist(ip_str): """判断 ip 是否在白名单""" ip = ipaddress.ip_address(ip_str) for net in WHITELIST: try: if '/' in net: if ip in ipaddress.ip_network(net): return True else: if ip == ipaddress.ip_address(net): return True except ValueError: continue return False def load_blocked_ips(): """读取历史封禁 IP""" blocked = set() if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE) as f: for line in f: line = line.strip() if line: blocked.add(line) return blocked def save_blocked_ips(blocked_ips): """保存累积封禁 IP""" with open(HISTORY_FILE, "w") as f: for ip in sorted(blocked_ips): f.write(ip + "\n") def get_wp_login_ips(limit=LIMIT): """从 Matomo API 获取访问 wp-login.php 的 IP""" params = { "module": "API", "method": "Live.getLastVisitsDetails", "idSite": SITE_ID, "period": "day", "date": "today", "format": "JSON", "filter_limit": limit, "token_auth": TOKEN_AUTH } r = requests.post(MATOMO_API, data=params) r.raise_for_status() data = r.json() wp_ips = set() for visit in data: for action in visit.get("actionDetails") or []: url = action.get("url") if url and "wp-login.php" in url: ip = visit.get("visitIp") if ip: wp_ips.add(ip) return wp_ips def write_apache_conf(blocked_ips): """生成 Apache 全局封禁规则,使用 <If> 判断 REMOTE_ADDR""" lines = ["# Auto-generated by matomo_block.py"] blocked_count = 0 for ip in sorted(blocked_ips): if ip_in_whitelist(ip): continue lines.append(f'<If "%{{REMOTE_ADDR}} == \'{ip}\'">') lines.append(" Require all denied") lines.append("</If>") blocked_count += 1 with open(BLOCK_CONF, "w") as f: f.write("\n".join(lines)) log(f"[+] Wrote {blocked_count} rules to {BLOCK_CONF}") def reload_apache(): """重载 Apache""" try: subprocess.run(["apachectl", "graceful"], check=True) log("[+] Apache reloaded") except subprocess.CalledProcessError as e: log(f"[-] Apache reload failed: {e}") log(traceback.format_exc()) # ================= 主程序 ================= if __name__ == "__main__": try: old_blocked = load_blocked_ips() log(f"[+] Loaded {len(old_blocked)} previously blocked IPs") new_ips = get_wp_login_ips() log(f"[+] Fetched {len(new_ips)} IPs accessing wp-login.php today") # 过滤白名单并累加 to_block = {ip for ip in new_ips if not ip_in_whitelist(ip)} all_blocked = old_blocked | to_block save_blocked_ips(all_blocked) log(f"[+] Total {len(all_blocked)} IPs will be blocked (including history)") write_apache_conf(all_blocked) reload_apache() except Exception as e: log(f"[-] Error: {e}") log(traceback.format_exc())
3. Apache 配置说明
- 封禁规则通过 <RequireAll> 包裹,避免负向 Require 报错。
- 白名单可支持单个 IP 或 CIDR 网段。
- 历史封禁 IP 会累加,不会覆盖。
3.1 Apache 加载自定义封禁规则
为了让 Apache 实际应用我们生成的 /etc/apache2/block_wp.conf,需要在主配置里 Include 它:
# Include custom block rules for wp-login.php Include /etc/apache2/block_wp.conf
- 你可以加在 /etc/apache2/apache2.conf 或对应的虚拟主机配置文件中。
- 这样脚本生成的新规则每次重载 Apache 都会生效。
- 注意 Include 路径必须正确,否则 Apache 启动会报错。
4. 调试与排错
4.1 Apache 无法识别配置报错 – Require not ip
使用 <RequireAll> 包裹负向规则即可。


4.2 ServerName 相关报错
AH00558: apache2: Could not reliably determine the server's fully qualified domain name

解决方式:为Apache配置默认域名。
# /etc/apache2/conf-available/servername.conf ServerName www.ruianding.com
sudo a2enconf servername sudo systemctl reload apache2
不会影响其他子域名,例如 grafana.ruianding.com。
4.3 Cron 执行配置
- 确保 Python 路径正确。
- 例子:每天 0 点自动执行:
- 由于脚本已自带日志打印功能,所以无需输出Cron日志
0 0 * * * /usr/bin/python3 /data/matomo/matomo_block.py
5. 进一步优化
5.1 全局封禁 vs 单路径封禁的调整
在最初的实现中,我的 Matomo IP 封禁脚本是针对单个路径 /wp-login.php 的,使用 Apache <RequireAll>/<RequireAny> 配置来拒绝访问。但我想到一个问题:
无法覆盖攻击者的其他尝试路径 攻击者可能通过不同的 URL 尝试访问登录页,例如 www.ruianding.com/blog/login-wp.php。如果只封单路径,攻击者仍然可以通过其他路径尝试暴力登录。
因此,我做了两项关键调整:
全局封禁 使用 Apache <If> 判断 REMOTE_ADDR:
<If "%{REMOTE_ADDR} == '1.2.3.4'"> Require all denied </If>
这样可以针对每个攻击 IP 全局拒绝访问,不再依赖路径限定,攻击者无论访问哪个 URL 都会被阻止。

6. 总结
通过 Matomo + Python + Apache 自动化封禁,减少了手动排查攻击 IP 的时间,提高了网站安全性。
整个流程包括:
- Matomo API 调用调试
- Python 自动化脚本生成封禁规则
- Apache 配置更新与重载
- Cron 自动化执行
- 调试、排错与优化