一句话需求
- 现在是七月, 从三月开始我的一个网站一直受到几百个IP的流量攻击, 具体表现就是日志里面出现大量访问一个固定url网址的不带reffer的手机端的国内IP段的大量请求. 每秒请求超过50次.
- 一开始用宝塔面板的免费WAF nginx防火墙, 能防住, 但是效果不好, 依然会有大量额外的图片等资源请求.
- 事件的经过在没查明IP之前我是不想封的, 因为有些站群的操作手法就是克隆我的网站来引流到他们自己的网站, 这种手法会造成大量访问我IP的请求都是来自真实用户的手机. 然后根绝我长期采样观察发现这些虽然是真人IP但是应该是来自他们手机里面的后门, 所以流量对我没有意义, 并且通过User-Agent分析是代理池的可能性很大, 鬼知道这些代理池以后会干什么啊, 换个User-Agent也是简简单单啊, 所以直接封杀掉IP好了.
- 定性之后就要封IP了. nginx那个防火墙不好用, 实际上请求还是请求了nginx的只是nginx返回了错误信息直接挡住了.所以nginx依然是有负载的.总觉得不爽.
- 我们要用linux自带的防火墙来直接封IP才能有效抵御住DDOS. 我使用的是 centos 7.6 系统自带防火墙是 firewalld , 我禁用了iptable
- 本文实现了 “单独为宝塔系统防火墙3.0生成批量导入屏蔽配置文件” 和 “直接为linux的firewall生成防火墙规则并自动每日导入”
- 可视化管理是宝塔面板的系统防火墙3.0
这是利用centos最新的系统级firewalld来封锁IP的高级防火墙, 比iptable更好.
- 从图片上的导入规则我们可以批量导入要封锁的IP地址
- 导入格式是
{"id": 1, "types": "drop", "address": "171.8.172.145", "brief": "", "addtime": "2021-07-28 17:26:50"}
需求落地:
- 分析nginx的web日志
- 提取要封锁的IP
- 生成JSON规则列表, 并保留历史记录, 下一次分析日志就只需要添加新规则
- 一次性导入即可
项目命名 digIP.js
文件夹结构
源码:
./digIP.js 主程
const fs = require('fs').promises
const path = require('path')
const writeLog = require('./utils/write-log.js')
const formatDate = require('./utils/datetime-format.js').formatDate
const runCMD = require('./utils/exec.js').runScript
const myEnv = require('./utils/my-env.js')
/*
分析网站日志 main 组
提取非法IP列表
输出指定格式JSON
**********************使用放方法:
配置config里面的weblog路径地址, 必须是main路径或者IP在第一位(日志分隔符是空格), main配置格式见后
首次执行, 执行即可在当前目录下生成一个JSON文件, 复制这个文件内容到宝塔的系统防火墙导入IP界面即可(导入之前先删除老的JSON, 700个IP耗时半小时, 这个导入时间很长, 是宝塔的问题)
二次执行, 会生成全新JSON, 包含老的(这里要优化下, 提供只生成新IP的策略JSON)
之后对新weblog分析(含老日志或者全新日志都可以), 会自动对比老配置文件和新weblog, 自动去重已添加的IP并分配新的序列号
我的main log日志样本
171.8.172.9 - - [28/Jul/2021:19:24:29 +0800] requesthost:"m.xxx.com"; "GET /tag/XX/return%20false8382610997276947256873 HTTP/1.1" requesttime:"0.000"; 444 0 "-" - - "Mozilla/5.0(Linux;U;Android 5.1.1;zh-CN;OPPO A33 Build/LMY47V) AppleWebKit/537.36(KHTML,like Gecko) Version/4.0 Chrome/40.0.2214.89 UCBrowser/11.7.0.953 Mobile Safari/537.36" "-"
*/
const config = {
newIpNewFile: true, // true 单独输出一份新IP输出为独立json文件; false 不单独输出一份
logFile: String.raw`F:\my_download\你的网站nginx访问日志文件(单行数据里面是空格分隔,其中IP是第一个数据).log`,
outputFile: path.resolve(__dirname, 'output-policy.json'),
policy: {
keyword: String.raw`(return(%20| )false\d+|OPPO A33 Build)`,
pattern:
/((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)) - .*(return(%20| )false\d+|OPPO A33 Build|Android 7.0; FRD-AL00 Build)/g
},
executeFinalBanIPBash: false // 是否在程序结束之前运行 BanIP 的bash
}
const getIPList = async () => {
const ctx = await fs.readFile(config.logFile, {
encoding: 'utf-8' })
let list = ctx.split('\n')
console.log(`总计 ${
list.length} 条记录`)
list = ctx.matchAll(config.policy.pattern)
const tmp = []
for (const item of list) {
tmp.push(item[1])
}
list = tmp
console.log(`找到 ${
list.length} 条非法记录`)
list = list.map(item => item.split(' ')[0])
list = [...new Set(list)]
list = list.sort()
console.log(`本次攻击IP总计: ${
list.length} 个`)
return list
}
// 生成封锁模板
// {"id": 1, "types": "drop", "address": "171.8.172.145", "brief": "", "addtime": "2021-07-28 17:26:50"}
const outputJSON = async (ipList, JsonInfo = 1, type = 'bt') =>
ipList.map((item, index, arr) => ({
id:
index +
(typeof JsonInfo === 'number'
? JsonInfo
: JsonInfo.length > 0
? JsonInfo.slice(-1)[0].id + 1
: 1),
types: 'drop',
address: item.trim(),
brief: '',
addtime: formatDate(Date.now(), 'yyyy-MM-dd hh:mm:ss')
}))
// 对比本地list 检测新的
const getNewJsonDeltaBundle