Files
yakpanel-core/class_v2/breaking_through.py

1353 lines
58 KiB
Python
Raw Normal View History

2026-04-07 02:04:22 +05:30
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2017 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: hezhihong <hezhihong@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# 防爆破、编译器类
#------------------------------
import os,sys,time,db,json
import shlex
panel_path = '/www/server/panel'
if not os.name in ['nt']:
os.chdir(panel_path)
if not 'class/' in sys.path:
sys.path.insert(0, 'class/')
if not 'class_v2/' in sys.path:
sys.path.insert(0, 'class_v2/')
import public
from public.regexplib import match_ipv4,match_ipv6
from safeModelV2.base import safeBase
# from pyroute2 import IPSet, NetlinkError
try:
from pyroute2 import IPSet, NetlinkError
except:
public.ExecShell("btpip install pyroute2")
from pyroute2 import IPSet, NetlinkError
class main(safeBase):
_config={}
def __init__(self):
self._types={'white':'yakpanel.ipv4.whitelist','black':'yakpanel.ipv4.blacklist'}
self._types_system={'white':'whitelist','black':'blacklist'}
self._config_file='/www/server/panel/data/breaking_through.json'
try:
self._config_file='{}/data/breaking_through.json'.format(public.get_panel_path())
except:
pass
self._breaking_white_file='{}/data/breaking_white.conf'.format(public.get_panel_path())
self._limit_file='{}/data/limit_login.pl'.format(public.get_panel_path())
self.__script_py = public.get_panel_path() + '/script/breaking_through_check.py'
self.__complier_group='yakpanel_complier'
self.__gcc_path=""
if os.path.exists("/usr/bin/gcc"):
self.__gcc_path="/usr/bin/gcc"
else:self.__gcc_path=public.ExecShell('which gcc')[0].strip()
self.__log_type='Brute force protection'
self.__write_log=True
with db.Sql() as sql:
sql = sql.dbfile("/www/server/panel/data/default.db")
black_white_sql = '''CREATE TABLE IF NOT EXISTS `black_white` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`ip` VARCHAR(45),
`ps` VARCHAR(40),
`add_type` VARCHAR(20),
`add_time` TEXT,
`timeout` INTEGER,
`black_reason` INTEGER
)'''
#black_reason 0 手动添加 1 ssh爆破ip 2 yakpanel爆破ip 3 ftp爆破ip 4 历史记录爆破ip
sql.execute(black_white_sql, ())
sql.close()
def init_ipset(self):
"""
@name 初始化ipset
"""
# try:
# # 在循环外只查询指定 ipset集合
# check_result = public.ExecShell('ipset list yakpanel.ipv4.whitelist && ipset list yakpanel.ipv4.blacklist')[0]
# for i in self._types:
# # check_result=public.ExecShell('ipset list')[0]
# if self._types[i] not in check_result:
# public.ExecShell('ipset create '+self._types[i]+' hash:net timeout 0')
# rule_type='DROP'
# if i=='white':rule_type='ACCEPT'
# if not public.ExecShell('iptables-save | grep "match-set '+self._types[i]+'"')[0]:
# public.ExecShell('iptables -I INPUT -m set --match-set '+self._types[i]+' src -j '+rule_type)
# except:pass
from pyroute2 import IPSet, NetlinkError
import socket
try:
with IPSet() as ipset:
# 创建或检查ipset集合
for i in self._types:
try:
# 尝试获取集合
ipset.get_set_byname(self._types[i])
except NetlinkError as e:
if e.code == 2: # ENOENT - No such file or directory
# 集合不存在创建新的ipset集合
public.print_log(f"Creating ipset {self._types[i]}")
ipset.create(name=self._types[i], stype='hash:net', family=socket.AF_INET, timeout=0)
else:
raise
# 设置iptables规则
for i in self._types:
rule_type = 'DROP'
if i == 'white':
rule_type = 'ACCEPT'
if not public.ExecShell('iptables-save | grep "match-set {}"'.format(self._types[i]))[0]:
public.ExecShell('iptables -I INPUT -m set --match-set {} src -j {}'.format(self._types[i], rule_type))
except:
pass
def type_conversion(self,data,types):
if types =='bool':
try:
if data=='true':
return True
else:return False
# data=bool(data)
# return data
except:return False
elif types =='int':
try:
if type(data)=='int':return data
data=int(data)
return data
except:return 0
def set_config(self,get):
"""
@name 设置防护配置
"""
self._config=self.read_config()
if 'global_status' in get or 'username_status' in get or 'ip_status' in get:
self._config['global_status']=self.type_conversion(get.global_status,'bool') if 'global_status' in get else self._config['global_status']
self._config['username_status']=self.type_conversion(get.username_status,'bool') if 'username_status' in get else self._config['username_status']
self._config['ip_status']=self.type_conversion(get.ip_status,'bool') if 'ip_status' in get else self._config['ip_status']
public.writeFile(self._config_file,json.dumps(self._config))
public.write_log_gettext(self.__log_type, 'Configuration modification successful!')
return public.return_message(0, 0, public.lang("Setting successful"))
try:
# if 'ip_command' in get:
try:
get.ip_command=get.ip_command.strip()
except:pass
if get.ip_command=='' and not self.type_conversion(get.ip_ipset_filter,'bool'): return public.return_message(-1, 0, public.lang("Please enable at least one command and firewall"))
self._config['based_on_username']={"limit":self.type_conversion(get.username_limit,'int'),"count":self.type_conversion(get.username_count,'int'),"type":self.type_conversion(get.username_type,'int'),"limit_root":self.type_conversion(get.username_limit_root,'bool')}
self._config['based_on_ip']={"limit":self.type_conversion(get.ip_limit,'int'),"count":self.type_conversion(get.ip_count,'int'),"command":get.ip_command,"ipset_filter":self.type_conversion(get.ip_ipset_filter,'bool')}
self._config['history_limit']=self.type_conversion(get.history_limit,'int')
self._config['global_status']=self._config['global_status']
self._config['username_status']=self._config['username_status']
self._config['ip_status']=self._config['ip_status']
public.writeFile(self._config_file,json.dumps(self._config))
except Exception as ee:
public.print_log('ee:{}'.format(ee))
#写日志
if self.__write_log:
public.write_log_gettext(self.__log_type, 'Configuration modification successful!')
return public.return_message(0, 0, public.lang("Setting successful"))
def get_config(self,get):
"""
@name 获取防护配置
"""
self._config=self.read_config()
try:
tmp_config=public.readFile(self._config_file)
self._config = json.loads(tmp_config)
return public.return_message(0,0,self._config)
except:
pass
return public.return_message(0,0,self._config)
def read_config(self):
"""
@name 读取防护配置
"""
self._config={"based_on_username":{"limit":5,"count":8,"type":0,"limit_root":False},"based_on_ip":{"limit":5,"count":8,"command":"","ipset_filter":True},"history_limit":60,"history_start":0,'global_status':True,'username_status':False,'ip_status':False}
if not os.path.exists(self._config_file):
public.writeFile(self._config_file,json.dumps(self._config))
return self._config
tmp_config = public.readFile(self._config_file)
if not tmp_config:
return self._config
try:
self._config = json.loads(tmp_config)
except:
public.writeFile(self._config_file,json.dumps(self._config))
return self._config
return self._config
def format_date_to_timestamp(self,time_string):
"""
@name将时间转化为字符串
"""
from datetime import datetime
# 给定的时间字符串
# time_string = "Jul 16 02:33:09"
# 当前年份
current_year = datetime.now().year
# 构造完整的日期时间字符串
full_time_string = f"{current_year} {time_string}"
# 定义日期时间格式
date_format = "%Y %b %d %H:%M:%S"
# 解析时间字符串
parsed_time = datetime.strptime(full_time_string, date_format)
# 转换为时间戳
timestamp = parsed_time.timestamp()
return timestamp
def get_ssh_info(self,result, data=[], keyword='', get_data: bool = False):
"""
@获取SSH信息
@param since_time:'2024-07-01 05:39:30'
"""
self._config=self.read_config()
ssh_info_list=data
keys=['journalctl_fail','journalctl_connection','journalctl_invalid_user',"log_file_fail","log_file_connection","log_file_invalid_user"]
ip_total={}
limit_time=self._config['based_on_ip']['limit']*60
now_time=int(time.time())
for key in keys:
if key in result and result[key]:
line_list=result[key].split('\n')
for line in line_list:
if line =='' or len(line)<50 :continue
# ssh_info={"user":"","exptime":"","ip":"","authservice":"yakpanel safe","country_code":"","logintime":"","service":"","country_name":"","timeleft":"","lock_status":'unlock'}
ssh_info={"user":"","exptime":"","ip":"","authservice":"","country_code":"","logintime":"","service":"","country_name":"","timeleft":"","lock_status":'unlock'}
user='root'
ip='127.0.0.1'
#取user
try:
user=line.split(' for ',1)[1].split(' ')[0]
except: pass
#取ip
try:
ip=line.split(' from ',1)[1].split(' ')[0]
except: pass
ssh_info['service']='sshd'
if ip =="127.0.0.1" and "Connection closed by authenticating user" in line:
#取ip
try:
ip=line.split(' user ',1)[1].split(' ')[1]
except: pass
#从爆破用户日志取用户
if key in ['journalctl_invalid_user','log_file_invalid_user']:
#取user
try:
user=line.split('Invalid user ',1)[1].split(' ')[0]
except: pass
#检测用户是否为空
if user =='':
user='-'
ssh_info['ip']=ip
ssh_info['user']=user
check_result=public.ExecShell('ipset test yakpanel.ipv4.blacklist '+ip)[1]
if 'is in set' in check_result:ssh_info['lock_status']='lock'
#取时间
try:
tmp_time=self.format_date_to_timestamp(line[:15])
except:
tmp_time=public.to_date(format="%Y-%m-%dT%H:%M:%S.%f%z",times=line[:32])
logintime=public.format_date(times=tmp_time)
tmp_exp_time=tmp_time+limit_time
exp_time=public.format_date(times=tmp_exp_time)
timeleft=0 if now_time>tmp_exp_time else tmp_exp_time-now_time
ssh_info["exptime"]=exp_time
ssh_info["timeleft"]=timeleft
ssh_info['logintime']=logintime
if ip not in ip_total:
ip_total[ip]={'count':1,'ssh_infos':[]}
else:
ip_total[ip]['count']+=1
if get_data:
if keyword !='' and (keyword in 'sshd' or keyword in ssh_info['authservice'] or keyword in ip or keyword in ssh_info['user'] or keyword in logintime):
ip_total[ip]['ssh_infos'].append(ssh_info)
ssh_info_list.append(ssh_info)
if keyword =='':
ip_total[ip]['ssh_infos'].append(ssh_info)
ssh_info_list.append(ssh_info)
return ssh_info_list, ip_total
def get_ssh_intrusion(self,since_time):
"""
@获取SSH爆破次数
@param since_time:'2024-07-01 05:39:30'
"""
test_string="""Aug 4 05:22:56 cpanel76262789 sshd[2112635]: Failed password for root from 218.92.0.52 port 20956 ssh2
Aug 4 05:23:01 cpanel76262789 sshd[2112635]: Failed password for root from 218.92.0.52 port 20956 ssh2
Aug 4 05:23:04 cpanel76262789 sshd[2112635]: Failed password for root from 218.92.0.52 port 20956 ssh2
Aug 4 05:23:08 cpanel76262789 sshd[2112635]: Failed password for root from 218.92.0.52 port 20956 ssh2
Aug 4 05:23:11 cpanel76262789 sshd[2112635]: Failed password for root from 218.92.0.52 port 20956 ssh2
Aug 4 05:23:18 cpanel76262789 sshd[2112655]: Failed password for root from 218.92.0.52 port 41852 ssh2
Aug 4 05:23:23 cpanel76262789 sshd[2112655]: Failed password for root from 218.92.0.52 port 41852 ssh2
Aug 4 05:47:03 cpanel76262789 sshd[2114164]: Failed password for root from 49.235.86.107 port 54144 ssh2
Aug 4 05:47:13 cpanel76262789 sshd[2114181]: Failed password for root from 81.192.46.48 port 39134 ssh2
Aug 4 05:49:10 cpanel76262789 sshd[2114252]: Failed password for root from 188.235.158.112 port 41790 ssh2
"""
result = {'journalctl_fail':"",'journalctl_connection':"",'journalctl_invalid_user':"","log_file_fail":"","log_file_connection":"","log_file_invalid_user":""}
if os.path.exists("/etc/debian_version"):
version = public.readFile('/etc/debian_version').strip()
if 'bookworm' in version or 'jammy' in version or 'impish' in version:
version = 12
else:
try:
version = float(version)
except:
version = 11
if version >= 12:
result['journalctl_fail'] = public.ExecShell("journalctl -u ssh --no-pager --since '"+since_time+"'|grep -a 'Failed password for' |grep -v 'invalid'")[0]
result['journalctl_connection']=public.ExecShell("journalctl -u ssh --no-pager --since '"+since_time+"'|grep -a 'Connection closed by authenticating user' |grep -a 'preauth'")[0]
result['journalctl_invalid_user']=public.ExecShell("journalctl -u ssh --no-pager --since '"+since_time+"'|grep -a 'sshd' |grep -a 'Invalid user'|grep -v 'Connection closed by'")[0]
return result
for sfile in self.get_ssh_log_files(None):
start_timestramp=public.to_date(times=since_time)
try:
try:
tmp_result = public.ExecShell("cat %s|grep -a 'Failed password for' |grep -v 'invalid'" % (sfile))[0].strip()
add_result=[]
line_list=tmp_result.split('\n')
for line in line_list:
try:
tmp_time=self.format_date_to_timestamp(line[:15])
except:
tmp_time=public.to_date(format="%Y-%m-%dT%H:%M:%S.%f%z",times=line[:32])
# print('tmp_time:{}'.format(public.format_date(times=tmp_time)))
if start_timestramp<=tmp_time:
add_result.append(line)
add_string='\n'.join(add_result)
result['log_file_fail']+=add_string
except:pass
try:
tmp_result= public.ExecShell("cat %s|grep -a 'Connection closed by authenticating user' |grep -a 'preauth'" % (sfile))[0].strip()
add_result=[]
line_list=tmp_result.split('\n')
for line in line_list:
try:
tmp_time=self.format_date_to_timestamp(line[:15])
except:
tmp_time=public.to_date(format="%Y-%m-%dT%H:%M:%S.%f%z",times=line[:32])
# print('tmp_time:{}'.format(public.format_date(times=tmp_time)))
if start_timestramp<=tmp_time:
add_result.append(line)
add_string='\n'.join(add_result)
result['log_file_connection']+=add_string
except:pass
try:
cmd="cat %s|grep -a 'sshd' |grep -a 'Invalid user '|grep -v 'Connection closed by'" % (sfile)
tmp_result= public.ExecShell("cat %s|grep -a 'sshd' |grep -a 'Invalid user'|grep -v 'Connection closed by'" % (sfile))[0].strip()
add_result=[]
line_list=tmp_result.split('\n')
for line in line_list:
try:
tmp_time=self.format_date_to_timestamp(line[:15])
except:
tmp_time=public.to_date(format="%Y-%m-%dT%H:%M:%S.%f%z",times=line[:32])
# print('tmp_time:{}'.format(public.format_date(times=tmp_time)))
if start_timestramp<=tmp_time:
add_result.append(line)
add_string='\n'.join(add_result)
result['log_file_invalid_user']+=add_string
except:pass
except: pass
# self.set_ssh_cache(data)
return result
def check_black_white_ipset(self):
"""
@name 检测ipset黑白名单
"""
for list_type,list_name in self._types.items():
ip_info=public.M('black_white').where('add_type=?', (list_type,)).select()
if list_type=='white':#白名单检测
for ip in ip_info:
public.ExecShell('ipset add '+list_name+' '+ip['ip']+' timeout 0')
else:#黑名单检测
for ip in ip_info:
timeout=timeleft=0
if int(ip['timeout'])!=0:
add_time=int(public.to_date(times=ip['add_time']))
now_time=int(time.time())
exptime=add_time+int(ip['timeout'])
timeleft=exptime-now_time
timeout=timeleft
if timeleft>0:
public.ExecShell('ipset add '+list_name+' '+ip['ip']+' timeout '+str(timeout))
else:
public.M('black_white').where('id=?', (ip['id'],)).delete()
else:
public.ExecShell('ipset add '+list_name+' '+ip['ip']+' timeout 0')
return
def cron_method(self):
"""
@name 防爆破检测方法
"""
self.init_ipset()
# public.print_log('Starting through check task execution')
self.check_black_white_ipset()
self._config=self.read_config()
if not self._config['global_status']:
return
# public.print_log('防爆破脚本开始运行...')
yakpanel_login_info=[]
now_time=old_limit=time.time()
if self._config['username_status']:
limit_time=int(self._config['based_on_username']['limit'])*60
count=int(self._config['based_on_username']['count'])
start_time=public.format_date(times=now_time-limit_time)
yakpanel_login_info=public.M('logs').where('type=? and addtime>=? and log LIKE ?',('Login',start_time,'%is incorrec%')).select()
yakpanel_login_limit=now_time+limit_time
try:
old_limit=int(public.readFile(self._limit_file))
except:old_limit=now_time
if len(yakpanel_login_info)>=count and old_limit<=now_time:
public.writeFile(self._limit_file,str(yakpanel_login_limit))
# public.print_log('统计到面板登录最大尝试次数')
# public.print_log('当前时间为:{}'.format(public.format_date(times=now_time)))
# public.print_log('限制时间为:{}'.format(public.format_date(times=yakpanel_login_limit)))
if self._config['ip_status']:
#ssh login
#取ssh记录
limit_time=int(self._config['based_on_ip']['limit'])*60
# limit_time=2592000
start_time=public.format_date(times=now_time-limit_time)
ip_count=int(self._config['based_on_ip']['count'])
ssh_info=self.get_ssh_intrusion(start_time)
_, ip_total = self.get_ssh_info(ssh_info)
# ssh最大ip限制处理
for ip, details in ip_total.items():
if int(details['count'])<ip_count:continue
# public.print_log('统计到ssh登录最大尝试次数')
if self._config['based_on_ip']['ipset_filter']:
# public.print_log('防火墙防护状态打开')
args = public.dict_obj()
args.types = 'black'
args.ips = ip
args.cron = 'true'
args.black_reason = 1
self.add_black_white(args,False)
command = self._config['based_on_ip']['command']
if command != '':
public.ExecShell('nohup ' + str(command) + ' &')
# public.print_log('Breaking through check task has been executed')
# time.sleep(60)
return
def is_shell_command(self,command_string):
# 尝试使用shlex.split处理字符串
try:
# 分割字符串
split_command = shlex.split(command_string)
# 检查是否有至少一个非空字符串
if split_command:
# 检查第一个元素是否可能是命令名
command_name = split_command[0]
# 检查命令名是否只包含字母、数字或下划线
if all(c.isalnum() or c == '_' for c in command_name):
return True
except ValueError:
# 如果shlex.split抛出ValueError那么可能不是一个合法的shell命令
pass
return False
def get_ssh_info_v2(self,search='',select='Failed',limit=999):
"""
@name 获取ssh信息
"""
from pathlib import Path
import public.PluginLoader as plugin_loader
mod_relative_path = "mod/project/ssh/comMod.py"
# 拼接路径
mod_file = str(Path(public.get_panel_path()) / mod_relative_path)
plugin_class = plugin_loader.get_module(mod_file)
class_string='main'
plugin_object = getattr(plugin_class,class_string)()
pdata = public.dict_obj()
pdata.p=1
pdata.limit=limit
pdata.search=search
pdata.select=select
result = getattr(plugin_object,"get_ssh_list")(pdata)
if isinstance(result, dict):
try:
#检测数据获取是否成功
status=result.get("status",-1)
if status<0:
return 0,[]
data = result["message"]["data"]
return len(data),data
except:
return 0,[]
return 0,[]
def get_history_record2(self,get):
"""
@name 获取历史记录能匹配关键词搜索
{'error_logins':[
{
"timeleft": "356099", #解封剩余分钟数
"user": "anonymous", #用户名
"exptime": "2025-04-09 10:21:01", #解封时间
"ip": "34.22.135.234",
"authservice": "pure-ftpd",#身份验证服务
"country_code": "BE",#所在国家简称
"logintime": "2024-08-02 10:21:01",#登录时间
"service": "system", #服务
"country_name": "Belgium"#所在国家名称
"lock_status":0 #0(未封锁)/1(封锁中)
}]
"""
self._config=self.read_config()
now_time=int(time.time())
keyword=get.keyword.strip()
result=[]
limit_time=int(self._config['history_limit'])*60 #默认最近1小时
yakpanel_user=public.M('users').where("id=?", (1,)).getField('username')
start_time=public.format_date(times=now_time-limit_time)
if self._config['history_start'] !=0 and int(time.time())-int(self._config['history_start'])<limit_time:
start_time=public.format_date(times=self._config['history_start'])
if get.types == 'login':
#取面板登录记录
login_info=public.M('logs').where('type=? and addtime>=? and log LIKE ?',('Login',start_time,'%is incorrec%')).select()
if len(login_info)>0:
for i in login_info:
ip=i['log'].split('Login IP:')[1].strip()
ip=ip.split(':')[0]
exptime=int(time.time())-limit_time-public.to_date(i['addtime'])
if exptime<0:exptime=0
timeleft= 0 if now_time>public.to_date(i['addtime'])+limit_time else now_time-(public.to_date(i['addtime'])+limit_time)
tt_time=public.format_date(times=public.to_date(times=i['addtime'])+limit_time)
single_info={"timeleft":timeleft,
"user":yakpanel_user,
"ip":ip,
"authservice":"yakpanel",
"exptime":tt_time,#当前时间-超时时间-登录时间
"country_code":"",
"logintime":i['addtime'],
"service":"yakpanel",
"country_name":""
}
#搜索过滤
if keyword !='' and (keyword in yakpanel_user or keyword in ip or keyword in "yakpanel" or keyword in i['addtime']) :result.append(single_info)
if keyword =='':result.append(single_info)
#取ssh记录
_,ssh_result=self.get_ssh_info_v2()
for i in ssh_result:
timeleft= 0 if now_time>public.to_date(i['timestamp'])+limit_time else now_time-(public.to_date(i['timestamp'])+limit_time)
tt_time=public.format_date(times=public.to_date(times=i['timestamp'])+limit_time)
single_info={"timeleft":timeleft,
"user":i["user"],
"ip":i["address"],
"authservice":"sshd",
"exptime":tt_time,#当前时间-超时时间-登录时间
"country_code":"",
"logintime":i['time'],
"service":"sshd",
"country_name":""
}
#搜索过滤
if keyword !='' and (keyword in yakpanel_user or keyword in i["address"] or keyword in "sshd" or keyword in single_info['logintime']) :result.append(single_info)
if keyword =='':result.append(single_info)
elif get.types == 'ip':
ip_info=public.M('black_white').where('add_type=? and timeout !=? and add_time>?', ('black',0,start_time )).select()
for i in ip_info:
if keyword !='' and (keyword not in i['ip'] and keyword in "yakpanel" and keyword in i['addtime']) :continue
add_time=int(public.to_date(times=i['add_time']))
exptime=add_time+i['timeout']
timeleft= 0 if now_time>exptime else exptime-now_time
single_info={"timeleft":timeleft//60,
"ip":i['ip'],
"exptime":public.format_date(times=exptime),
"begin":i['add_time'],
"country_code":"",
"note":"",
"action":"yakpanel",
"country_name":"",
'lock_status':'blocked',
'block_reason':'Trigger SSH explosion-proof rule breaking' if i['black_reason']==1 else 'Trigger yakpanel explosion-proof rule breaking'
}
result.append(single_info)
#排序
result = sorted(result, key=lambda x: x['logintime'], reverse=True)
#取分页数据
data = self.get_page(get,result)
return public.return_message(0,0,data)
def get_history_record(self, get):
"""
获取历史记录支持关键词搜索
返回格式:
{'error_logins': [
{
"timeleft": "356099", # 解封剩余分钟数
"user": "anonymous", # 用户名
"exptime": "2025-04-09 10:21:01", # 解封时间
"ip": "34.22.135.234",
"authservice": "pure-ftpd", # 身份验证服务
"country_code": "BE", # 国家简称
"logintime": "2024-08-02 10:21:01", # 登录时间
"service": "system", # 服务
"country_name": "Belgium", # 国家名称
"lock_status": 0 # 0(未封锁)/1(封锁中)
}
]}
"""
# 常量定义:避免硬编码,便于维护
SERVICE_YAKPANEL = "yakpanel"
SERVICE_SSHD = "sshd"
BLOCK_REASON_SSH = "Trigger SSH explosion-proof rule breaking"
BLOCK_REASON_PANEL = "Trigger yakpanel explosion-proof rule breaking"
# 初始化配置和时间参数
self._config = self.read_config()
now_time = int(time.time())
keyword = get.keyword.strip()
limit_time = int(self._config['history_limit']) * 60 # 默认最近1小时
start_time = self._get_start_time(now_time, limit_time)
# 获取用户名
yakpanel_user = public.M('users').where("id=?", (1,)).getField('username')
result = []
# 根据类型分发处理逻辑
if get.types == 'login':
result = self._handle_login_type(
start_time, now_time, limit_time,
yakpanel_user, keyword,
SERVICE_YAKPANEL, SERVICE_SSHD
)
# 排序
if len(result)>1:
result = sorted(result, key=lambda x: x['logintime'], reverse=True)
elif get.types == 'ip':
result = self._handle_ip_type(
start_time, now_time, keyword,
SERVICE_YAKPANEL, BLOCK_REASON_SSH, BLOCK_REASON_PANEL
)
# 排序
if len(result)>1:
result = sorted(result, key=lambda x: x['begin'], reverse=True)
#分页返回结果
data = self.get_page(get, result)
return public.return_message(0, 0, data)
# ------------------------------
# 拆分后的逻辑模块
# ------------------------------
def _get_start_time(self, now_time, limit_time):
"""计算查询的起始时间"""
config_start = self._config['history_start']
if config_start != 0 and (now_time - int(config_start) < limit_time):
return public.format_date(times=config_start)
return public.format_date(times=now_time - limit_time)
def _handle_login_type(self, start_time, now_time, limit_time, yakpanel_user, keyword, service_panel, service_sshd):
"""处理登录类型(login)的记录逻辑"""
result = []
# 1. 处理面板登录记录
panel_logs = public.M('logs').where(
'type=? and addtime>=? and log LIKE ?',
('Login', start_time, '%is incorrec%')
).select()
for login_log in panel_logs:
login_info = self._build_panel_login_info(
login_log, yakpanel_user, now_time, limit_time, service_panel
)
if self._is_match_keyword(login_info, keyword, service_panel):
result.append(login_info)
# 2. 处理SSH登录记录
_, ssh_logs = self.get_ssh_info_v2()
start_time_int= public.to_date(times=start_time)
for ssh_log in ssh_logs:
if ssh_log["timestamp"]<start_time_int:continue
ssh_info = self._build_ssh_login_info(
ssh_log, now_time, limit_time, service_sshd
)
if self._is_match_keyword(ssh_info, keyword, service_sshd):
result.append(ssh_info)
return result
def _handle_ip_type(self, start_time, now_time, keyword, service, reason_ssh, reason_panel):
"""处理IP类型(ip)的记录逻辑"""
result = []
ip_logs = public.M('black_white').where(
'add_type=? and timeout !=? and add_time>?',
('black', 0, start_time)
).select()
for ip_log in ip_logs:
# 关键词过滤
if keyword and not self._ip_log_match_keyword(ip_log, keyword, service):
continue
# 构建IP记录信息
ip_info = self._build_ip_info(ip_log, now_time, service, reason_ssh, reason_panel)
result.append(ip_info)
return result
# ------------------------------
# 信息构建与过滤
# ------------------------------
def _build_panel_login_info(self, login_log, username, now_time, limit_time, service):
"""构建面板登录记录的信息字典"""
# 解析IP地址
log_str = login_log['log']
ip = log_str.split('Login IP:')[1].strip().split(':')[0]
# 计算过期时间和剩余时间
login_timestamp = public.to_date(login_log['addtime'])
expire_timestamp = login_timestamp + limit_time
timeleft = self._calculate_timeleft(now_time, expire_timestamp)
return {
"timeleft": timeleft,
"user": username,
"ip": ip,
"authservice": service,
"exptime": public.format_date(times=expire_timestamp),
"country_code": "",
"logintime": login_log['addtime'],
"service": service,
"country_name": ""
}
def _build_ssh_login_info(self, ssh_log, now_time, limit_time, service):
"""构建SSH登录记录的信息字典"""
login_timestamp = public.to_date(ssh_log['timestamp'])
expire_timestamp = login_timestamp + limit_time
timeleft = self._calculate_timeleft(now_time, expire_timestamp)
return {
"timeleft": timeleft,
"user": ssh_log["user"],
"ip": ssh_log["address"],
"authservice": service,
"exptime": public.format_date(times=expire_timestamp),
"country_code": "",
"logintime": ssh_log['time'],
"service": service,
"country_name": ""
}
def _build_ip_info(self, ip_log, now_time, service, reason_ssh, reason_panel):
"""构建IP封锁记录的信息字典"""
add_timestamp = int(public.to_date(times=ip_log['add_time']))
exptime_timestamp = add_timestamp + ip_log['timeout']
timeleft = self._calculate_timeleft(now_time, exptime_timestamp) // 60 # 转换为分钟
# 确定封锁原因
block_reason = reason_ssh if ip_log['black_reason'] == 1 else reason_panel
return {
"timeleft": timeleft,
"ip": ip_log['ip'],
"exptime": public.format_date(times=exptime_timestamp),
"begin": ip_log['add_time'],
"country_code": "",
"note": "",
"action": service,
"country_name": "",
'lock_status': 'blocked',
'block_reason': block_reason
}
def _calculate_timeleft(self, now_time, expire_timestamp):
"""计算剩余时间如果已过期则返回0"""
return max(0, expire_timestamp - now_time) if now_time <= expire_timestamp else 0
def _is_match_keyword(self, info, keyword, service):
"""判断记录是否匹配关键词(无关键词时默认匹配)"""
if not keyword:
return True
# 检查关键词是否在关键字段中
match_fields = [
info['user'], info['ip'], service, info['logintime']
]
return any(keyword in str(field) for field in match_fields)
def _ip_log_match_keyword(self, ip_log, keyword, service):
"""IP记录的关键词匹配逻辑"""
return (keyword in ip_log['ip']) or (keyword in service) or (keyword in ip_log['add_time'])
def set_history_record_limit(self,get=None):
"""
@name 设置历史记录时间
"""
self._config=self.read_config()
try:
if 'history_limit' in get:
self._config['history_limit']=self.type_conversion(get.history_limit,'int')
#写日志
if self.__write_log:
public.write_log_gettext(self.__log_type, 'Set the duration of failed login attempts (in minutes) to [{}] minutes'.format(self._config['history_limit']))
if 'history_start' in get:
self._config['history_start']=self.type_conversion(get.history_start,'int')
#写日志
if self.__write_log:
public.write_log_gettext(self.__log_type, 'History has been cleared!')
except Exception as ee:
public.print_log('ee:{}'.format(ee))
public.writeFile(self._config_file,json.dumps(self._config))
return public.return_message(0, 0, public.lang("Setting successful"))
def clear_history_record_limit(self,get):
"""
@name 移除并清空历史记录
"""
self.init_ipset()
get.history_start=int(time.time())
self.set_history_record_limit(get)
#清除历史记录
clear_ips=public.M('black_white').where('add_type=? and timeout !=?', ('black',0)).select()
for clear_info in clear_ips:
check_result=public.ExecShell('ipset test yakpanel.ipv4.blacklist '+clear_info['ip'])[1]
if 'is in set' in check_result:
public.ExecShell('ipset del '+self._types['black']+' '+clear_info['ip'])
public.M('black_white').where('add_type=? and timeout !=?', ('black',0)).delete()
#写日志
if self.__write_log:
public.write_log_gettext(self.__log_type, 'The history has been cleared and the blocked IP has been cleared')
return public.return_message(0, 0, public.lang("Setting successful"))
def get_black_white(self,get):
"""
@name 获取黑/白名单
"""
ip_list=[]
result=public.M('black_white').where('add_type=? and black_reason=?', (get.types,0)).select()
return public.return_message(0,0,result)
def add_black_white(self,get,write_log=True):
"""
@name 添加编辑删除黑/白名单
"""
self.init_ipset()
self.init_complier()
if 'black_reason' not in get:get.black_reason=0
ip_infos=get.ips.strip().split('\\n')
ip_list=[]
#检测ip是否正确
for i in ip_infos:
if i=='':continue
ps=''
i_list=i.split('#',1)
ip=i_list[0].replace('"', '').strip()
if len(i_list)>1:
ps=i_list[1].strip()
single_info={"ip":ip,"ps":ps}
if public.is_ipv4(ip):
ip_list.append(single_info)
else:
# public.print_log('ip:{}'.format(ip))
return {'status': -1, "timestamp": int(time.time()), "message": {'result':'[{}] IP address incorrect'.format(ip)}}
if len(ip_list)==0:
#清空黑/白名单
public.ExecShell('ipset flush '+self._types[get.types])
public.M('black_white').where('add_type=? and black_reason =?', (get.types,0)).delete()
self.writeListFile()
if write_log:
public.write_log_gettext(self.__log_type, 'The black and white list operation settings have been executed')
return public.return_message(0, 0, public.lang("The operation has been executed"))
if 'ps' not in get and 'cron' not in get:
public.ExecShell('ipset flush '+self._types[get.types])
public.M('black_white').where('add_type=? and black_reason =?', (get.types,0)).delete()
timeout=0
if get.types=='black' and 'hand' not in get:
timeout=int(self._config['based_on_ip']['limit']) *60
check_result=public.ExecShell('ipset list')[0]
if self._types[get.types] not in check_result:
public.ExecShell('ipset create '+self._types[get.types]+' hash:net timeout 0')
# success_list=[]
# failed_list=[]
try:
for ip_info in ip_list:
ip=ip_info['ip']
ps=ip_info['ps']
if ps=='' and len(ip_list)==1 and 'ps' in get:ps=get.ps
if ip=='':continue
if 'cron' in get and get.types=='black' and public.M('black_white').where('ip=? and add_type=?', (ip, 'white')).count():
# public.print_log('匹配到白名单规则,跳过:{}'.format(ip))
continue
#解封黑名单
if 'clear_black' in get:
public.ExecShell('ipset del '+self._types['black']+' '+ip )
public.M('black_white').where('ip=?', (ip,)).delete()
if get.types=='black' and 'hand' not in get:
if ip=='127.0.0.1':
# public.print_log('The IP address [{}] is the local IP address and cannot be added to the blacklist'.format(ip))
continue
if not public.M('black_white').where('ip=? and add_type=?', (ip, get.types)).count():
public.M('black_white').add('ip,add_type,ps,add_time,timeout,black_reason',(ip, get.types,ps,time.strftime('%Y-%m-%d %X',time.localtime()),timeout,get.black_reason))
if public.M('black_white').where('ip=? and add_type=?', (ip, get.types)).count():
#写日志
if write_log:
public.write_log_gettext(self.__log_type, 'Successfully added IP [{}] to the interception system [{}]', (ip,self._types_system[get.types]))
result=public.ExecShell('ipset add '+self._types[get.types]+' '+ip+' timeout '+str(timeout))
# if public.M('black_white').where('ip=? and add_type=?', (ip, get.types)).count():
# success_list.append(ip)
# else:
# failed_list.append(ip)
except:pass
# if len(success_list)>0:
# message='The following IP addresses have been successfully added【{}】'.format(",".join(success_list))
# if len(failed_list)>0:
# message+='The following IP addresses have been failed added【{}】'.format(",".join(failed_list))
self.writeListFile()
# if self.__write_log:
# public.write_log_gettext(self.__log_type, 'The black and white list operation settings have been executed')
return {'status': 0, "timestamp": int(time.time()), "message": {'result':'The operation has been executed'}}
# return public.return_message(0, 0, public.lang("The operation has been executed"))
def writeListFile(self):
result=public.M('black_white').where('add_type=?', ('white',)).select()
if len(result)<1:return
ip_list=[]
for i in result:
ip_list.append(i['ip'])
ip_string=','.join(ip_list)
public.writeFile(self._breaking_white_file,ip_string)
return
def check_local_ip_white(self,get):
"""
@name 编辑黑/白名单
"""
if not public.M('black_white').where('ip=? and add_type=?', (get.ip, 'white')).count():
return public.return_message(-1, 0, public.lang("Your current IP address [{}] is not on the whitelist.", get.ip))
return public.return_message(0, 0, public.lang("Your current IP address [{}] is on the whitelist.", get.ip))
def panel_ip_white(self,get):
"""
@name 面板设置ip加白
"""
get.ips=get.ip.strip()
# from YakPanel import cache
# limitip=''
# try:
# limitip = public.readFile('data/limitip.conf')
# limitip=limitip.strip()
# except:
# limitip=''
# if limitip=='':limitip=get.ip
# else:
# if get.ip not in limitip:
# limitip=limitip+','+get.ip
# public.writeFile('data/limitip.conf',limitip)
# cache.set('limit_ip',[])
get.types='white'
if 'ps' not in get:
get.ps='your ip address'
get.hand=True
self.__write_log=False
result=self.add_black_white(get)
self.__write_log=True
if result['status']==0:
if self.__write_log:
public.write_log_gettext(self.__log_type, 'Access IP [{}] successfully whitelisted'.format(get.ips))
return public.return_message(0, 0, public.lang("Added successfully"))
else:
return public.return_message(-1, 0, public.lang("Added failed"))
def del_cron(self):
cron_name='[Do not delete] breaking through check task'
cron_path = public.GetConfigValue('setup_path') + '/www/server/cron/'
try:
cron_path = public.GetConfigValue('setup_path') + '/cron/'
except:
pass
# python_path = ''
# try:
# python_path = public.ExecShell('which btpython')[0].strip("\n")
# except:
# try:
# python_path = public.ExecShell('which python')[0].strip("\n")
# except:
# pass
# if not python_path: return False
if public.M('crontab').where('name=?',(cron_name,)).count():
cron_echo = public.M('crontab').where(
"name=?", (cron_name, )).getField('echo')
cron_id = public.M('crontab').where(
"echo=?", (cron_echo, )).getField('id')
args = {"id": cron_id}
import crontab
crontab.crontab().DelCrontab(args)
del_cron_file = cron_path + cron_echo
public.ExecShell(
"crontab -u root -l| grep -v '{}'|crontab -u root -".
format(del_cron_file))
return True
def add_cron(self):
cron_name='[Do not delete] breaking through check task'
cron_path = public.GetConfigValue('setup_path') + '/www/server/cron/'
try:
cron_path = public.GetConfigValue('setup_path') + '/cron/'
except:
pass
python_path = ''
try:
python_path = public.ExecShell('which btpython')[0].strip("\n")
except:
try:
python_path = public.ExecShell('which python')[0].strip("\n")
except:
pass
if not python_path: return False
count=public.M('crontab').where('name=?',(cron_name,)).count()
if count>1:
cron_echo = public.M('crontab').where(
"name=?", (cron_name, )).getField('echo')
cron_id = public.M('crontab').where(
"echo=?", (cron_echo, )).getField('id')
args = {"id": cron_id}
import crontab
crontab.crontab().DelCrontab(args)
del_cron_file = cron_path + cron_echo
public.ExecShell(
"crontab -u root -l| grep -v '{}'|crontab -u root -".
format(del_cron_file))
if not public.M('crontab').where('name=?',
(cron_name, )).count():
cmd = '{} {}'.format(python_path, self.__script_py)
args = {
"name": cron_name,
"type": 'minute-n',
"where1": '1',
"hour": '',
"week":'',
"minute": '',
"sName": "",
"sType": 'toShell',
"notice": '',
"notice_channel": '',
'datab_name':'',
'tables_name':'',
"save": '',
"save_local": '1',
"backupTo": '',
"sBody": cmd,
"urladdress": ''
}
import crontab
res = crontab.crontab().AddCrontab(args)
if res and "id" in res.keys():
return True
return False
return True
def get_protected_services(self,get):
"""
@name 获取防护配置
"""
result={'based_on_username':['yakpanel'],'based_on_ip':['ssh']}
return public.return_message(0,0,result)
def get_login_limit(self):
"""
@name 获取登录限制配置
"""
self._config=self.read_config()
if not self._config['global_status'] or not self._config['username_status']:return False
#防爆破检测
now_time=limit_time=time.time()
white_ips=''
_limit_login='{}/data/limit_login.pl'.format(public.get_panel_path())
breaking_white='{}/data/breaking_white.conf'.format(public.get_panel_path())
#获取限制时间
try:
limit_time=float(public.readFile(_limit_login))
if os.path.exists(breaking_white):
limit_time=float(public.readFile(_limit_login))
if os.path.exists(breaking_white):
white_ips+=public.readFile(breaking_white)
except:pass
intranet_local_ip=public.get_local_ip()
if intranet_local_ip=='':intranet_local_ip=public.get_local_ip_2()
from YakPanel import session
if 'address' not in session:
session['address']=public.GetClientIp()
if now_time<limit_time and (white_ips=='' or session['address'] not in white_ips and intranet_local_ip not in white_ips):
return True
return False
def get_linux_users(self,get):
"""
@name 获取linux用户列表信息
"""
#取用户列表
user_list=[]
user_exec=public.ExecShell('cat /etc/passwd')[0].strip()
try:
tmp_list=user_exec.split('\n')
for i in tmp_list:
i_strip=i.strip()
if i_strip=='':continue
user_list.append(i_strip.split(':',1)[0])
except:pass
#取分页数据
data = self.get_page(get,user_list)
return public.return_message(0,0,data)
def get_page(self,get,result):
"""
@name 取分页信息
"""
#取分页数据
import page
page = page.Page()
info = {}
info['count'] = len(result)
info['row'] = 10
info['p'] = 1
if hasattr(get, 'p'):
info['p'] = int(get['p'])
if hasattr(get, 'limit'):
info['row'] = int(get['limit'])
info['uri'] = get
info['return_js'] = ''
if hasattr(get, 'tojs'):
info['return_js'] = get.tojs
data = {}
# 获取分页数据
data['data']=[]
data['page'] = page.GetPage(info, '1,2,3,4,5,8')
start = (info['p']-1)*info['row']
end= info['p']*info['row']-1
for index in range(len(result)):
if index<start:continue
if index >end:continue
data['data'].append(result[index])
return data
def get_compiler_info(self,get):
"""
@name 获取编译器组内成员信息
"""
complier_members=[]
try:
group_exec=public.ExecShell('grep '+self.__complier_group+' /etc/group')[0].strip()
group_string=group_exec.split(':')[3].strip()
if group_string!='':
complier_members=group_exec.split(':')[3].split(',')
except:pass
#取分页数据
data = self.get_page(get,complier_members)
return public.return_message(0,0,data)
def init_complier(self):
"""
@name 初始化编译器组
"""
#读取配置文件/etc/group
if os.path.exists('/etc/group'):
groupContent = public.readFile('/etc/group')
if groupContent.find(self.__complier_group+":x:") == -1:
public.ExecShell('groupadd '+self.__complier_group)
try:
file_stat = os.stat(self.__gcc_path)
gid=file_stat.st_gid
import grp
group_name=grp.getgrgid(gid).gr_name
if group_name!=self.__complier_group:
public.ExecShell('chgrp '+self.__complier_group+' '+self.__gcc_path)
except:pass
def add_user_to_compiler(self,get):
"""
@name 添加指定用户到编译器组
"""
self.init_complier()
if 'users' not in get:return public.return_message(-1, 0, public.lang("parameter error"))
try:
if type(get.users)==str:
import ast
get.users=ast.literal_eval(get.users)
except:pass
add_users=get.users
get.limit=10000
complier_members=self.get_compiler_info(get)['message']['data']
for add_user in add_users:
try:
if (len(complier_members)>0 and add_user not in complier_members) or len(complier_members)<1:
public.ExecShell('usermod -aG '+self.__complier_group+' '+add_user)
# complier_members=self.get_compiler_info(get)['message']['data']
# public.print_log('complier_members2:{}'.format(complier_members))
# if add_user in complier_members:return public.return_message(0, 0, public.lang("Added successfully"))
except:pass
#写日志
if self.__write_log:
public.write_log_gettext(self.__log_type, 'Successfully added users [{}] to the compiler group', (','.join(add_users),))
return public.return_message(0, 0, public.lang("Operation executed"))
def del_user_to_compiler(self,get):
"""
@name 删除编译器组内指定用户
"""
if 'user' not in get or get.user.strip()=='':return public.return_message(-1, 0, public.lang("parameter error"))
del_user=get.user.strip()
get.limit=10000
complier_members=self.get_compiler_info(get)['message']['data']
try:
if len(complier_members)>0 and del_user in complier_members:
public.ExecShell('gpasswd -d '+del_user+' '+self.__complier_group)
complier_members=self.get_compiler_info(get)['message']['data']
if len(complier_members)>0 and del_user in complier_members:return public.return_message(-1, 0, public.lang("Delete failed"))
except:pass
#写日志
if self.__write_log:
public.write_log_gettext(self.__log_type, 'User [{}] successfully removed from compiler group', (del_user,))
return public.return_message(0, 0, public.lang("Delete successfully"))
def set_compiler_status(self,get):
"""
@name 为编译器其他用户设置状态
@param get.status 0关闭 1开启 不传此参数时仅获取状态不设置状态
"""
chmod_limt='0750'
log_string={'0750':'closed','0755':'enabled'}
if 'status' in get:
if int(get.status)==1:chmod_limt='0755'
public.ExecShell('chmod '+chmod_limt+' '+self.__gcc_path)
#写日志
if self.__write_log:
public.write_log_gettext(self.__log_type, 'Non privileged user successfully {} gcc compiler', (log_string[chmod_limt],))
limit_status=False
try:
accept=oct(os.stat(self.__gcc_path).st_mode)[-4:]
if accept=='0755':limit_status=True
except:pass
return public.return_message(0,0,limit_status)