Python脚本防止Linux远程SSH暴力破解
问题:
服务器被长时间持续ssh访问暴力破解,系统存在大量的sshd异常访问日志和错误提示,例如
Failed password for invalid user 用户名 from IP地址 port 端口号 ssh2
Connection closed by IP地址 port 端口号 [preauth]
error: kex_exchange_identification: Connection closed by remote host
解决方案:
读取Linux日志/var/log/secure
中的日志记录(不同版本的系统的日志文件不同,但一般都在/var/log
l路径下的某个日志文件中,例如messages
),识别包含Connection closed.*preauth
并且不包含user
的sshd日志(即在用户认证之前被异常关闭的访问请求),和包含Failed
的sshd日志(即由于密码或端口错误而失败的请求),以及包含authentication failure
的sshd
日志(身份验证失败),提取其中IP地址和记录出现次数。当IP地址出现的次数大于指定的阈值时,判断该Ip地址是否已井存在于防火墙规则中,若不存在,则将其加入防火墙规则拒绝其访问。
代码:
#!/usr/bin/python
# coding=utf-8
# 导入启动其他进程的模块
import subprocess
# 导入正则表达式模块
import re
# 从collections模块中导入一个计数器Counter类
from collections import Counter
# 导入时间模块
import time
# 系统日志文件路径
sys_log_path = '/var/log/secure'
# 脚本日志路径
log_path = '/sshPrevent/' + time.strftime('%Y%m%d') + '.log'
# IP地址出现次数阈值
define = 5
# 读取系统sshd日志
def read_sshd_log():
# 始化一个计数器来存储IP地址及其出现次数
ip_counter = Counter()
# 读取日志文件
with open (sys_log_path,'r') as file:
# 逐行读取
for line in file:
# 用re模块中的search函数来搜索字符串line中是否包含符合特定模式的子串
ip_match = re.search(r'\b([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\b', line)
# 判断ip_match对象是否存在
if ip_match:
# 提取正则表达式中的第1个捕获组匹配到的文本
ip_address = ip_match.group(1)
# 检查行是否满足条件
if 'Connection closed.*preauth' in line and 'user' not in line:
# 把ip_counter字典中ip_address键对应的值增加1,如果ip_adress不存在则新增并将值设置为1
ip_counter[ip_address] += 1
elif 'Failed' in line:
ip_counter[ip_address] += 1
elif 'authentication failure' in line:
ip_counter[ip_address] += 1
# 返回最终的计数器字典
return ip_counter
# 判断IP地址是否已经在firewall列表中
def check_firewall_ip(ip):
# 构建firewall查询命令,使用grep来搜索包含指定IP地址
command = "firewall-cmd --list-all |grep " + ip
# 执行命令并获取输出(python3.6和3.7版本的subprocess参数不太一样)
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, universal_newlines=True)
# 检查命令是否成功执行并且有输出
if result.returncode == 0 and result.stdout:
# 如果有输出,说明找到了包含该IP地址的规则
return 'true'
else:
# 如果没有输出或者命令执行失败,说明没有找到该IP地址的规则
return 'false'
# 添加firewall规则,拒绝指定的IP地址
def add_firewall_rule(ip):
# 构建防火墙规则(\是为了转译单引号,在此处不加也可以)
rule = "rule family=\'ipv4\' source address=" + ip + " reject"
# 构建添加firewall规则的参数
command = ['firewall-cmd', '--permanent', '--add-rich-rule', rule]
# 执行命令并获取输出(在python3.7中,universal_newlines=True变成了text=True)
result = subprocess.run(command,stdout=subprocess.PIPE, universal_newlines=True)
# 执行成功返回true,否则返回false
if result.returncode == 0:
return 'true'
else:
# 执行失败则返回false
return 'false'
# 重新加载firewall规则
def reload_firewall_rule():
# 构建firewall重新加载命令
command = ['firewall-cmd', '--reload']
# 执行命令并获取输出
result = subprocess.run(command,stdout=subprocess.PIPE, universal_newlines=True)
# 执行完成,成功则返回true
if result.returncode == 0:
return 'true'
else:
return 'false'
# 写入执行日志
def write_python_log(ip):
# 打开日志,执行写入,方式为追加
with open(log_path, 'a') as file:
# 写入的内容为时间+IP地址+提示内容,并换行
file.write( time.strftime('%Y-%m-%d %H:%M:%S') + ' ' + ip + ' Add To Firewall Success.\n')
return
# 执行步骤
def start():
try:
# 读取计数器中的Ip地址和出现次数
for ip, count in read_sshd_log().items():
# 若IP地址出现的次数大于等于阈值,则继续
if count > define:
# 判断Ip地址是否已存在于firewall,不存在则继续
if 'false' in check_firewall_ip(ip):
# 添加到firewall,成功则继续
if 'true' in add_firewall_rule(ip):
# 重新加载firewall,成功则继续
if 'true' in reload_firewall_rule():
# 将添加规则的记录写入日志
write_python_log(ip)
finally:
return None
if __name__ == '__main__':
start()
yunxi p.deng 2024/06/22