Files
aaPanel/class/ssh_security.py
T
jose 3ac20e08ce v6.8.12
1. Add disk IO information to the homepage
2. Add a clear list button in the upload window
3. Optimize the background task overhead of the panel
4. Optimize the disk information caching mechanism
5. Panel Pro edition is online
6. Optimize panel resource usage
7. Optimize SSL certificate renewal
8. Fix the problem of reporting an error when the security entrance is empty
9. Fix the problem of infinite recursion when copying directories in extreme cases
2021-06-11 11:50:57 +08:00

516 lines
22 KiB
Python

#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: lkqiang <lkq@bt.cn>
#-------------------------------------------------------------------
# SSH 安全类
#------------------------------
import public,os,re,send_mail,json
from datetime import datetime
class ssh_security:
__SSH_CONFIG='/etc/ssh/sshd_config'
__ip_data = None
__ClIENT_IP='/www/server/panel/data/host_login_ip.json'
__pyenv = 'python'
__REPAIR={"1":{"id":1,
"type":"file",
"harm":"High",
"repaired":"1",
"level":"3",
"name":"Make sure SSH MaxAuthTries is set between 3-6",
"file":"/etc/ssh/sshd_config",
"Suggestions":"Remove the MaxAuthTries comment symbol # in /etc/ssh/sshd_config, set the maximum number of failed password attempts 3-6 recommended 4",
"repair":"MaxAuthTries 4",
"rule":[{"re":"\nMaxAuthTries\\s*(\\d+)","check":{"type":"number","max":7,"min":3}}],
"repair_loophole":[{"re":"\n?#?MaxAuthTries\\s*(\\d+)","check":"\nMaxAuthTries 4"}]},
"2":{"id":2,
"repaired":"1",
"type":"file",
"harm":"High",
"level":"3",
"name":"SSHD Mandatory use of V2 security protocol",
"file":"/etc/ssh/sshd_config",
"Suggestions":"Set parameters in the /etc/ssh/sshd_config file as follows",
"repair":"Protocol 2",
"rule":[{"re":"\nProtocol\\s*(\\d+)",
"check":{"type":"number","max":3,"min":1}}],
"repair_loophole":[{"re":"\n?#?Protocol\\s*(\\d+)","check":"\nProtocol 2"}]},
"3":{"id":3,
"repaired":"1",
"type":"file",
"harm":"High",
"level":"3",
"name":"Set SSH idle exit time",
"file":"/etc/ssh/sshd_config",
"Suggestions":"Set ClientAliveInterval to 300 to 900 in /etc/ssh/sshd_config, which is 5-15 minutes, and set ClientAliveCountMax to 0-3",
"repair":"ClientAliveInterval 600 ClientAliveCountMax 2",
"rule":[{"re":"\nClientAliveInterval\\s*(\\d+)","check":{"type":"number","max":900,"min":300}}],
"repair_loophole":[{"re":"\n?#?ClientAliveInterval\\s*(\\d+)","check":"\nClientAliveInterval 600"}]},
"4":{"id":4,
"repaired":"1",
"type":"file",
"harm":"High",
"level":"3",
"name":"Make sure SSH LogLevel is set to INFO",
"file":"/etc/ssh/sshd_config",
"Suggestions":"Set parameters in the /etc/ssh/sshd_config file as follows (uncomment)",
"repair":"LogLevel INFO",
"rule":[{"re":"\nLogLevel\\s*(\\w+)","check":{"type":"string","value":["INFO"]}}],
"repair_loophole":[{"re":"\n?#?LogLevel\\s*(\\w+)","check":"\nLogLevel INFO"}]},
"5":{"id":5,
"repaired":"1",
"type":"file",
"harm":"High",
"level":"3",
"name":"Disable SSH users with empty passwords from logging in",
"file":"/etc/ssh/sshd_config",
"Suggestions":"Configure PermitEmptyPasswords to no in /etc/ssh/sshd_config",
"repair":"PermitEmptyPasswords no",
"rule":[{"re":"\nPermitEmptyPasswords\\s*(\\w+)","check":{"type":"string","value":["no"]}}],
"repair_loophole":[{"re":"\n?#?PermitEmptyPasswords\\s*(\\w+)","check":"\nPermitEmptyPasswords no"}]},
"6":{"id":6,
"repaired":"1",
"type":"file",
"name":"SSH uses the default port 22",
"harm":"High",
"level":"3",
"file":"/etc/ssh/sshd_config",
"Suggestions":"Set Port to 6000 to 65535 in / etc / ssh / sshd_config",
"repair":"Port 60151",
"rule":[{"re":"Port\\s*(\\d+)","check":{"type":"number","max":65535,"min":22}}],
"repair_loophole":[{"re":"\n?#?Port\\s*(\\d+)","check":"\nPort 65531"}]}}
def __init__(self):
if not os.path.exists(self.__ClIENT_IP):
public.WriteFile(self.__ClIENT_IP,json.dumps([]))
self.__mail=send_mail.send_mail()
self.__mail_config=self.__mail.get_settings()
self._check_pyenv()
try:
self.__ip_data = json.loads(public.ReadFile(self.__ClIENT_IP))
except:
self.__ip_data=[]
def _check_pyenv(self):
if os.path.exists('/www/server/panel/pyenv'):
self.__pyenv = 'btpython'
def return_python(self):
if os.path.exists('/www/server/panel/pyenv/bin/python'):return '/www/server/panel/pyenv/bin/python'
if os.path.exists('/usr/bin/python'):return '/usr/bin/python'
if os.path.exists('/usr/bin/python3'):return '/usr/bin/python3'
return 'python'
def return_bashrc(self):
if os.path.exists('/root/.bashrc'):return '/root/.bashrc'
if os.path.exists('/etc/bashrc'):return '/etc/bashrc'
if os.path.exists('/etc/bash.bashrc'):return '/etc/bash.bashrc'
fd = open('/root/.bashrc', mode="w", encoding="utf-8")
fd.close()
return '/root/.bashrc'
def check_files(self):
try:
json.loads(public.ReadFile(self.__ClIENT_IP))
except:
public.WriteFile(self.__ClIENT_IP, json.dumps([]))
def get_ssh_port(self):
conf = public.readFile(self.__SSH_CONFIG)
if not conf: conf = ''
rep = "#*Port\s+([0-9]+)\s*\n"
tmp1 = re.search(rep,conf)
port = '22'
if tmp1:
port = tmp1.groups(0)[0]
return port
# 主判断函数
def check_san_baseline(self, base_json):
if base_json['type'] == 'file':
if 'check_file' in base_json:
if not os.path.exists(base_json['check_file']):
return False
else:
if os.path.exists(base_json['file']):
ret = public.ReadFile(base_json['file'])
for i in base_json['rule']:
valuse = re.findall(i['re'], ret)
print(valuse)
if i['check']['type'] == 'number':
if not valuse: return False
if not valuse[0]: return False
valuse = int(valuse[0])
if valuse > i['check']['min'] and valuse < i['check']['max']:
return True
else:
return False
elif i['check']['type'] == 'string':
if not valuse: return False
if not valuse[0]: return False
valuse = valuse[0]
print(valuse)
if valuse in i['check']['value']:
return True
else:
return False
return True
def san_ssh_security(self,get):
data={"num":100,"result":[]}
result = []
ret = self.check_san_baseline(self.__REPAIR['1'])
if not ret: result.append(self.__REPAIR['1'])
ret = self.check_san_baseline(self.__REPAIR['2'])
if not ret: result.append(self.__REPAIR['2'])
ret = self.check_san_baseline(self.__REPAIR['3'])
if not ret: result.append(self.__REPAIR['3'])
ret = self.check_san_baseline(self.__REPAIR['4'])
if not ret: result.append(self.__REPAIR['4'])
ret = self.check_san_baseline(self.__REPAIR['5'])
if not ret: result.append(self.__REPAIR['5'])
ret = self.check_san_baseline(self.__REPAIR['6'])
if not ret: result.append(self.__REPAIR['6'])
data["result"]=result
if len(result)>=1:
data['num']=data['num']-(len(result)*10)
return data
################## SSH 登陆报警设置 ####################################
def send_mail_data(self,title,body,type='mail'):
if type=='mail':
if self.__mail_config['user_mail']['user_name']:
if len(self.__mail_config['user_mail']['mail_list'])>=1:
for i in self.__mail_config['user_mail']['mail_list']:
self.__mail.qq_smtp_send(i, title, body)
elif type=='dingding':
if self.__mail_config['dingding']['dingding']:
self.__mail.dingding_send(title+body)
return True
#检测非UID为0的账户
def check_user(self):
data=public.ExecShell('''cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'|grep -v '^root$' ''')
data=data[0]
if re.search("\w+",data):
self.send_mail_data(public.GetLocalIp()+'There are backdoor users on the server',public.GetLocalIp()+'There are backdoor users on the server'+data+'Check the /etc/passwd file')
return True
else:
return False
#记录root 的登陆日志
#返回登陆IP
def return_ip(self,get):
self.check_files()
return public.returnMsg(True, self.__ip_data)
#添加IP白名单
def add_return_ip(self, get):
self.check_files()
if get.ip.strip() in self.__ip_data:
return public.returnMsg(False, "Already exists")
else:
self.__ip_data.append(get.ip.strip())
public.writeFile(self.__ClIENT_IP, json.dumps(self.__ip_data))
return public.returnMsg(True, "Added successfully")
def del_return_ip(self, get):
self.check_files()
if get.ip.strip() in self.__ip_data:
self.__ip_data.remove(get.ip.strip())
public.writeFile(self.__ClIENT_IP, json.dumps(self.__ip_data))
return public.returnMsg(True, "Successfully deleted")
else:
return public.returnMsg(False, "IP does not exist")
#取登陆的前50个条记录
def login_last(self):
self.check_files()
data=public.ExecShell('last -n 50')
data=re.findall("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",data[0])
if data>=1:
data2=list(set(data))
for i in data2:
if not i in self.__ip_data:
self.__ip_data.append(i)
public.writeFile(self.__ClIENT_IP, json.dumps(self.__ip_data))
return self.__ip_data
#获取ROOT当前登陆的IP
def get_ip(self):
data = public.ExecShell(''' who am i |awk ' {print $5 }' ''')
data = re.findall("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",data[0])
return data
def get_logs(self, args):
if 'p' in args: p = int(args.p)
rows = 10
if 'rows' in args: rows = int(args.rows)
count = public.M('logs').where('type=?', ('SSH security',)).count()
data = public.get_page(count, int(args.p), int(rows))
data['data'] = public.M('logs').where('type=?', ('SSH security',)).limit(data['shift'] + ',' + data['row']).order(
'addtime desc').select()
return data
def get_server_ip(self):
if os.path.exists('/www/server/panel/data/iplist.txt'):
data=public.ReadFile('/www/server/panel/data/iplist.txt')
return data.strip()
else:return '127.0.0.1'
#登陆的情况下
def login(self):
self.check_files()
if not self.__mail_config['user_mail']['user_name']:return False
self.check_user()
self.__ip_data = json.loads(public.ReadFile(self.__ClIENT_IP))
ip=self.get_ip()
if not ip:
ip = ["127.0.0.1"]
if len(ip[0])==0:return False
try:
import time
mDate = time.strftime('%Y-%m-%d %X', time.localtime())
if ip[0] in self.__ip_data:
if public.M('logs').where('type=? addtime', ('SSH security',mDate,)).count():return False
public.WriteLog('SSH security', 'The server {} login IP is {}, login user is root'.format(public.GetLocalIp(),ip[0]))
return False
else:
if public.M('logs').where('type=? addtime', ('SSH security', mDate,)).count(): return False
self.send_mail_data('Server {} login alarm'.format(public.GetLocalIp()),'There is a login alarm on the server {}, the login IP is {}, the login user is root'.format(public.GetLocalIp(),ip[0]))
public.WriteLog('SSH security','There is a login alarm on the server {}, the login IP is {}, login user is root'.format(public.GetLocalIp(),ip [0]))
return True
except:
pass
#开启监控
def start_jian(self,get):
data=public.ReadFile(self.return_bashrc())
if not re.search('{}\/www\/server\/panel\/class\/ssh_security.py'.format(".*python\s+"),data):
public.WriteFile(self.return_bashrc(),data.strip()+'\n'+self.return_python()+ ' /www/server/panel/class/ssh_security.py login\n')
return public.returnMsg(True, 'Open successfully')
return public.returnMsg(False, 'Open failed')
#关闭监控
def stop_jian(self,get):
data = public.ReadFile(self.return_bashrc())
if re.search(self.return_python()+' /www/server/panel/class/ssh_security.py', data):
public.WriteFile(self.return_bashrc(),data.replace(self.return_python()+' /www/server/panel/class/ssh_security.py login',''))
if os.path.exists('/etc/bashrc'):
if re.search('python /www/server/panel/class/ssh_security.py', data):
public.WriteFile(self.return_bashrc(),data.replace(self.return_python()+' /www/server/panel/class/ssh_security.py login',''))
return public.returnMsg(True, 'Closed successfully')
else:
return public.returnMsg(True, 'Closed successfully')
#监控状态
def get_jian(self,get):
data = public.ReadFile(self.return_bashrc())
if re.search('{}\/www\/server\/panel\/class\/ssh_security.py\s+login'.format(".*python\s+"), data):
return public.returnMsg(True, '1')
else:
return public.returnMsg(False, '1')
def set_password(self, get):
'''
开启密码登陆
get: 无需传递参数
'''
ssh_password = '\n#?PasswordAuthentication\s\w+'
file = public.readFile(self.__SSH_CONFIG)
if len(re.findall(ssh_password, file)) == 0:
file_result = file + '\nPasswordAuthentication yes'
else:
file_result = re.sub(ssh_password, '\nPasswordAuthentication yes', file)
self.wirte(self.__SSH_CONFIG, file_result)
self.restart_ssh()
return public.returnMsg(True, 'Open successfully')
def set_sshkey(self, get):
'''
设置ssh 的key
参数 ssh=rsa&type=yes
'''
type_list = ['rsa', 'dsa','ed25519']
ssh_type = ['yes', 'no']
ssh = get.ssh
if not ssh in ssh_type: return public.returnMsg(False, 'ssh option failed')
type = get.type
if not type in type_list: return public.returnMsg(False, 'Wrong encryption method')
file = ['/root/.ssh/id_rsa.pub', '/root/.ssh/id_rsa', '/root/.ssh/authorized_keys']
for i in file:
if os.path.exists(i):
os.remove(i)
os.system("ssh-keygen -t %s -P '' -f ~/.ssh/id_rsa |echo y" % type)
if os.path.exists(file[0]):
public.ExecShell('cat %s >%s && chmod 600 %s' % (file[0], file[-1], file[-1]))
rec = '\n#?RSAAuthentication\s\w+'
rec2 = '\n#?PubkeyAuthentication\s\w+'
file = public.readFile(self.__SSH_CONFIG)
if len(re.findall(rec, file)) == 0: file = file + '\nRSAAuthentication yes'
if len(re.findall(rec2, file)) == 0: file = file + '\nPubkeyAuthentication yes'
file_ssh = re.sub(rec, '\nRSAAuthentication yes', file)
file_result = re.sub(rec2, '\nPubkeyAuthentication yes', file_ssh)
if ssh == 'no':
ssh_password = '\n#?PasswordAuthentication\s\w+'
if len(re.findall(ssh_password, file_result)) == 0:
file_result = file_result + '\nPasswordAuthentication no'
else:
file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file_result)
self.wirte(self.__SSH_CONFIG, file_result)
self.restart_ssh()
return public.returnMsg(True, 'Open successfully')
else:
return public.returnMsg(False, 'Open failed')
def stop_key(self, get):
'''
关闭key
无需参数传递
'''
file = ['/root/.ssh/id_rsa.pub', '/root/.ssh/id_rsa', '/root/.ssh/authorized_keys']
rec = '\n#?RSAAuthentication\s\w+'
rec2 = '\n#?PubkeyAuthentication\s\w+'
file = public.readFile(self.__SSH_CONFIG)
file_ssh = re.sub(rec, '\nRSAAuthentication no', file)
file_result = re.sub(rec2, '\nPubkeyAuthentication no', file_ssh)
self.wirte(self.__SSH_CONFIG, file_result)
self.set_password(get)
self.restart_ssh()
return public.returnMsg(True, 'Closed successfully')
def get_config(self, get):
'''
获取配置文件
无参数传递
'''
result = {}
file = public.readFile(self.__SSH_CONFIG)
rec = '\n#?RSAAuthentication\s\w+'
pubkey = '\n#?PubkeyAuthentication\s\w+'
ssh_password = '\nPasswordAuthentication\s\w+'
#是否运行root登录
root_is_login='\n#?PermitRootLogin\s\w+'
ret = re.findall(ssh_password, file)
if not ret:
result['password'] = 'no'
else:
if ret[-1].split()[-1] == 'yes':
result['password'] = 'yes'
else:
result['password'] = 'no'
pubkey = re.findall(pubkey, file)
if not pubkey:
result['pubkey'] = 'no'
else:
if pubkey[-1].split()[-1] == 'no':
result['pubkey'] = 'no'
else:
result['pubkey'] = 'yes'
rsa_auth = re.findall(rec, file)
if not rsa_auth:
result['rsa_auth'] = 'no'
else:
if rsa_auth[-1].split()[-1] == 'no':
result['rsa_auth'] = 'no'
else:
result['rsa_auth'] = 'yes'
is_root=re.findall(root_is_login, file)
if not is_root:
result['root_is_login'] = 'no'
else:
if is_root[-1].split()[-1] == 'no':
result['root_is_login'] = 'no'
else:
result['root_is_login'] = 'yes'
return result
def set_root(self, get):
'''
开启密码登陆
get: 无需传递参数
'''
ssh_password = '\n#?PermitRootLogin\s\w+'
file = public.readFile(self.__SSH_CONFIG)
if len(re.findall(ssh_password, file)) == 0:
file_result = file + '\nPermitRootLogin yes'
else:
file_result = re.sub(ssh_password, '\nPermitRootLogin yes', file)
self.wirte(self.__SSH_CONFIG, file_result)
self.restart_ssh()
return public.returnMsg(True, 'Successfully opened')
def stop_root(self, get):
'''
开启密码登陆
get: 无需传递参数
'''
ssh_password = '\n#?PermitRootLogin\s\w+'
file = public.readFile(self.__SSH_CONFIG)
if len(re.findall(ssh_password, file)) == 0:
file_result = file + '\nPermitRootLogin no'
else:
file_result = re.sub(ssh_password, '\nPermitRootLogin no', file)
self.wirte(self.__SSH_CONFIG, file_result)
self.restart_ssh()
return public.returnMsg(True, 'Closed successfully')
def stop_password(self, get):
'''
关闭密码访问
无参数传递
'''
file = public.readFile(self.__SSH_CONFIG)
ssh_password = '\n#?PasswordAuthentication\s\w+'
file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file)
self.wirte(self.__SSH_CONFIG, file_result)
self.restart_ssh()
return public.returnMsg(True, 'Closed successfully')
def get_key(self, get):
'''
获取key 无参数传递
'''
file = '/root/.ssh/id_rsa'
if not os.path.exists(file): return public.returnMsg(True, '')
ret = public.readFile(file)
return public.returnMsg(True, ret)
def wirte(self, file, ret):
result = public.writeFile(file, ret)
return result
def restart_ssh(self):
'''
重启ssh 无参数传递
'''
version = public.readFile('/etc/redhat-release')
act = 'restart'
if not os.path.exists('/etc/redhat-release'):
public.ExecShell('service ssh ' + act)
elif version.find(' 7.') != -1 or version.find(' 8.') != -1:
public.ExecShell("systemctl " + act + " sshd.service")
else:
public.ExecShell("/etc/init.d/sshd " + act)
if __name__ == '__main__':
import sys
type = sys.argv[1]
if type=='login':
try:
aa = ssh_security()
aa.login()
except:pass
else:
pass