网站防篡改程序

pull/403/head
midoks 2 years ago
parent ed3e3336b2
commit 528094cfa7
  1. 1
      .gitignore
  2. 19
      plugins/system_safe/README.md
  3. 5
      plugins/tamper_proof_py/conf/config.json
  4. BIN
      plugins/tamper_proof_py/ico.png
  5. 1163
      plugins/tamper_proof_py/index.html
  6. 628
      plugins/tamper_proof_py/index.py
  7. 15
      plugins/tamper_proof_py/info.json
  8. 15
      plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl
  9. 95
      plugins/tamper_proof_py/init.d/tamper_proof_py.tpl
  10. 52
      plugins/tamper_proof_py/install.sh
  11. 552
      plugins/tamper_proof_py/tamper_proof_service.py

1
.gitignore vendored

@ -162,7 +162,6 @@ plugins/my_*
plugins/l2tp plugins/l2tp
plugins/openlitespeed plugins/openlitespeed
plugins/tamper_proof plugins/tamper_proof
plugins/tamper_proof_*
plugins/cryptocurrency_trade plugins/cryptocurrency_trade
plugins/op_load_balance plugins/op_load_balance
plugins/gdrive plugins/gdrive

@ -1,19 +0,0 @@
# plugins_system_safe
系统加固插件
# DEBUG
```
https://code_hosting_007.midoks.me/midoks/plugins_system_safe
ln -s /www/git/midoks/plugins_system_safe /www/server/mdserver-web/plugins/system_safe
ln -s /www/wwwroot/midoks/plugins_system_safe /www/server/mdserver-web/plugins/system_safe
python3 /www/server/mdserver-web/plugins/system_safe/index.py bg_start
systemctl daemon-reload
```

@ -0,0 +1,5 @@
{
"open": true,
"excludePath": [ "cache", "threadcache", "log", "logs", "config", "runtime", "temp","tmp","caches","tmps"],
"protectExt": [ "php", "html", "htm", "shtml", "tpl", "js", "css", "jsp", "do" ]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

File diff suppressed because it is too large Load Diff

@ -0,0 +1,628 @@
# coding:utf-8
import sys
import io
import os
import time
import json
import re
import psutil
from datetime import datetime
sys.dont_write_bytecode = True
sys.path.append(os.getcwd() + "/class/core")
import mw
app_debug = False
if mw.isAppleSystem():
app_debug = True
class App:
__total = 'total.json'
__sites = []
def __init__(self):
pass
def getPluginName(self):
return 'tamper_proof_py'
def getPluginDir(self):
return mw.getPluginDir() + '/' + self.getPluginName()
def getServerDir(self):
return mw.getServerDir() + '/' + self.getPluginName()
def getInitDFile(self):
if app_debug:
return '/tmp/' + self.getPluginName()
return '/etc/init.d/' + self.getPluginName()
def getInitDTpl(self):
path = self.getPluginDir() + "/init.d/" + self.getPluginName() + ".tpl"
return path
def getArgs(self):
args = sys.argv[2:]
tmp = {}
args_len = len(args)
if args_len == 1:
t = args[0].strip('{').strip('}')
if t.strip() == '':
tmp = []
else:
t = t.split(':')
tmp[t[0]] = t[1]
elif args_len > 1:
for i in range(len(args)):
t = args[i].split(':')
tmp[t[0]] = t[1]
return tmp
def checkArgs(self, data, ck=[]):
for i in range(len(ck)):
if not ck[i] in data:
return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
return (True, mw.returnJson(True, 'ok'))
def getTotal(self, siteName=None, day=None):
defaultTotal = {"total": 0, "delete": 0,
"create": 0, "modify": 0, "move": 0}
if siteName:
total = {}
total_path = self.getServerDir() + '/sites/' + siteName + '/' + self.__total
if not os.path.exists(total_path):
total['site'] = defaultTotal
else:
total_data = mw.readFile(total_path)
if total_data['site']:
total['site'] = json.loads(total_data['site'])
else:
total['site'] = defaultTotal
if not day:
day = time.strftime("%Y-%m-%d", time.localtime())
total_day_path = self.getServerDir() + '/sites/' + siteName + '/day/total.json'
if not os.path.exists(total_day_path):
total['day'] = defaultTotal
else:
total['day'] = mw.readFile(total_day_path)
if total['day']:
total['day'] = json.loads(total['day'])
else:
total['day'] = defaultTotal
else:
filename = self.getServerDir() + '/sites/' + self.__total
if os.path.exists(filename):
total = json.loads(mw.readFile(filename))
else:
total = defaultTotal
return total
def getSites(self):
sites_path = self.getServerDir() + '/sites.json'
t = mw.readFile(sites_path)
if not os.path.exists(sites_path) or not t:
mw.writeFile(sites_path, '[]')
data = json.loads(mw.readFile(sites_path))
is_write = False
rm_keys = ['lock', 'bak_open']
for i in data:
i_keys = i.keys()
if not 'open' in i_keys:
i['open'] = False
for o in rm_keys:
if o in i_keys:
if i[o]:
i['open'] = True
i.pop(o)
is_write = True
if is_write:
mw.writeFile(sites_path, json.dumps(data))
self.__sites = data
return data
def writeSites(self, data):
mw.writeFile(self.getServerDir() + '/sites.json', json.dumps(data))
# mw.ExecShell('/etc/init.d/bt_tamper_proof reload')
def __getFind(self, siteName):
data = self.getSites()
for siteInfo in data:
if siteName == siteInfo['siteName']:
return siteInfo
return None
def writeLog(self, log):
mw.writeLog('防篡改程序', log)
def saveSiteConfig(self, siteInfo):
data = self.getSites()
for i in range(len(data)):
if data[i]['siteName'] != siteInfo['siteName']:
continue
data[i] = siteInfo
break
self.writeSites(data)
def syncSites(self):
data = self.getSites()
sites = mw.M('sites').field('name,path').select()
config_path = self.getPluginDir() + '/conf/config.json'
config = json.loads(mw.readFile(config_path))
names = []
n = 0
# print(config)
for siteTmp in sites:
names.append(siteTmp['name'])
siteInfo = self.__getFind(siteTmp['name'])
if siteInfo:
if siteInfo['path'] != siteTmp['path']:
siteInfo['path'] = siteTmp['path']
self.saveSiteConfig(siteInfo)
data = self.getSites()
continue
siteInfo = {}
siteInfo['siteName'] = siteTmp['name']
siteInfo['path'] = siteTmp['path']
siteInfo['open'] = False
siteInfo['excludePath'] = config['excludePath']
siteInfo['protectExt'] = config['protectExt']
data.append(siteInfo)
n += 1
newData = []
for siteInfoTmp in data:
if siteInfoTmp['siteName'] in names:
newData.append(siteInfoTmp)
else:
mw.execShell("rm -rf " + self.getServerDir() +
'/sites/' + siteInfoTmp['siteName'])
n += 1
if n > 0:
self.writeSites(newData)
self.__sites = None
def initDreplace(self):
file_tpl = self.getInitDTpl()
service_path = self.getServerDir()
initD_path = service_path + '/init.d'
if not os.path.exists(initD_path):
os.mkdir(initD_path)
# init.d
file_bin = initD_path + '/' + self.getPluginName()
if not os.path.exists(file_bin):
# initd replace
content = mw.readFile(file_tpl)
content = content.replace('{$SERVER_PATH}', service_path)
mw.writeFile(file_bin, content)
mw.execShell('chmod +x ' + file_bin)
# systemd
# /usr/lib/systemd/system
systemDir = mw.systemdCfgDir()
systemService = systemDir + '/tamper_proof_py.service'
systemServiceTpl = self.getPluginDir() + '/init.d/tamper_proof_py.service.tpl'
if os.path.exists(systemDir) and not os.path.exists(systemService):
se_content = mw.readFile(systemServiceTpl)
se_content = se_content.replace('{$SERVER_PATH}', service_path)
mw.writeFile(systemService, se_content)
mw.execShell('systemctl daemon-reload')
return file_bin
def getDays(self, path):
days = []
if not os.path.exists(path):
os.makedirs(path)
for dirname in os.listdir(path):
if dirname == '..' or dirname == '.' or dirname == 'total.json':
continue
if not os.path.isdir(path + '/' + dirname):
continue
days.append(dirname)
days = sorted(days, reverse=True)
return days
def status(self):
'''
状态
'''
initd_file = self.getServerDir() + '/init.d/' + self.getPluginName()
if not os.path.exists(initd_file):
return 'stop'
cmd = initd_file + ' status|grep already'
data = mw.execShell(cmd)
if data[0] != '':
return 'start'
return 'stop'
def tpOp(self, method):
file = self.initDreplace()
if not mw.isAppleSystem():
cmd = 'systemctl ' + method + ' ' + self.getPluginName()
data = mw.execShell(cmd)
if data[1] == '':
return mw.returnJson(True, '操作成功')
return mw.returnJson(False, '操作失败')
cmd = file + ' ' + method
data = mw.execShell(cmd)
if data[1] == '':
return mw.returnJson(True, '操作成功')
return mw.returnJson(False, '操作失败')
def start(self):
return self.tpOp('start')
def restart(self):
return self.tpOp('restart')
def service_admin(self):
if mw.isAppleSystem():
return mw.returnJson(False, '仅支持Linux!')
args = self.getArgs()
check = self.checkArgs(args, ['serviceStatus'])
if not check[0]:
return check[1]
method = args['serviceStatus']
return self.tpOp(method)
def initd_status(self):
if mw.isAppleSystem():
return "Apple Computer does not support"
shell_cmd = 'systemctl status %s | grep loaded | grep "enabled;"' % (
self.getPluginName())
data = mw.execShell(shell_cmd)
if data[0] == '':
return 'fail'
return 'ok'
def initd_install(self):
if mw.isAppleSystem():
return "Apple Computer does not support"
mw.execShell('systemctl enable ' + self.getPluginName())
return 'ok'
def initd_uninstall(self):
if mw.isAppleSystem():
return "Apple Computer does not support"
mw.execShell('systemctl disable ' + self.getPluginName())
return 'ok'
def set_site_status(self):
args = self.getArgs()
check = self.checkArgs(args, ['siteName'])
if not check[0]:
return check[1]
siteName = args['siteName']
siteInfo = self.__getFind(siteName)
if not siteInfo:
return mw.returnJson(False, '指定站点不存在!')
try:
siteInfo['open'] = not siteInfo['open']
except:
siteInfo['open'] = not siteInfo['open']
m_logs = {True: '开启', False: '关闭'}
self.writeLog('%s站点[%s]防篡改保护' %
(m_logs[siteInfo['open']], siteInfo['siteName']))
self.siteReload(siteInfo)
self.saveSiteConfig(siteInfo)
self.restart()
return mw.returnJson(True, '设置成功!')
def get_run_logs(self):
log_file = self.getServerDir() + '/service.log'
return mw.returnJson(True, mw.getLastLine(log_file, 200))
# 取文件指定尾行数
def getNumLines(self, path, num, p=1):
pyVersion = sys.version_info[0]
try:
import cgi
if not os.path.exists(path):
return ""
start_line = (p - 1) * num
count = start_line + num
fp = open(path, 'rb')
buf = ""
fp.seek(-1, 2)
if fp.read(1) == "\n":
fp.seek(-1, 2)
data = []
b = True
n = 0
for i in range(count):
while True:
newline_pos = str.rfind(str(buf), "\n")
pos = fp.tell()
if newline_pos != -1:
if n >= start_line:
line = buf[newline_pos + 1:]
try:
data.append(json.loads(cgi.escape(line)))
except:
pass
buf = buf[:newline_pos]
n += 1
break
else:
if pos == 0:
b = False
break
to_read = min(4096, pos)
fp.seek(-to_read, 1)
t_buf = fp.read(to_read)
if pyVersion == 3:
if type(t_buf) == bytes:
t_buf = t_buf.decode('utf-8')
buf = t_buf + buf
fp.seek(-to_read, 1)
if pos - to_read == 0:
buf = "\n" + buf
if not b:
break
fp.close()
except:
return []
if len(data) >= 2000:
arr = []
for d in data:
arr.insert(0, json.dumps(d))
mw.writeFile(path, "\n".join(arr))
return data
def get_safe_logs(self):
args = self.getArgs()
check = self.checkArgs(args, ['siteName'])
if not check[0]:
return check[1]
siteName = args['siteName']
data = {}
path = self.getPluginDir() + '/sites/' + siteName + '/day'
data['days'] = self.getDays(path)
if not data['days']:
data['logs'] = []
else:
p = 1
if hasattr(args, 'p'):
p = args['p']
day = data['days'][0]
if hasattr(args, 'day'):
day = args['day']
data['get_day'] = day
logs_path = path + '/' + day + '/logs.json'
data['logs'] = self.getNumLines(logs_path, 2000, int(p))
return mw.returnJson(True, 'ok', data)
def get_site_find(self):
args = self.getArgs()
check = self.checkArgs(args, ['siteName'])
if not check[0]:
return check[1]
siteName = args['siteName']
data = self.__getFind(siteName)
return mw.returnJson(True, 'ok', data)
def siteReload(self, siteInfo):
cmd = "python3 {} {}".format(
mw.getPluginDir() + '/tamper_proof_service.py unlock', siteInfo['path'])
mw.execShell(cmd)
tip_file = mw.getServerDir() + '/tips/' + siteInfo['siteName'] + '.pl'
if os.path.exists(tip_file):
os.remove(tip_file)
def remove_protect_ext(self):
args = self.getArgs()
check = self.checkArgs(args, ['siteName', 'protectExt'])
if not check[0]:
return check[1]
siteName = args['siteName']
protectExt = args['protectExt'].strip()
siteInfo = self.__getFind(siteName)
if not siteInfo:
return mw.returnJson(False, '指定站点不存在!')
if not protectExt:
return mw.returnJson(False, '被删除的保护列表不能为空')
for protectExt in protectExt.split(','):
if not protectExt in siteInfo['protectExt']:
continue
siteInfo['protectExt'].remove(protectExt)
self.writeLog('站点[%s]从受保护列表中删除[.%s]' %
(siteInfo['siteName'], protectExt))
self.siteReload(siteInfo)
self.saveSiteConfig(siteInfo)
return mw.returnJson(True, '删除成功!')
def add_protect_ext(self):
args = self.getArgs()
check = self.checkArgs(args, ['siteName', 'protectExt'])
if not check[0]:
return check[1]
siteName = args['siteName']
protectExt = args['protectExt'].strip()
siteInfo = self.__getFind(siteName)
if not siteInfo:
return mw.returnJson(False, '指定站点不存在!')
protectExt = protectExt.lower()
for protectExt in protectExt.split("\n"):
if protectExt[0] == '/':
if os.path.isdir(protectExt):
continue
if protectExt in siteInfo['protectExt']:
continue
siteInfo['protectExt'].insert(0, protectExt)
self.writeLog('站点[%s]添加文件类型或文件名[.%s]到受保护列表' %
(siteInfo['siteName'], protectExt))
self.siteReload(siteInfo)
self.saveSiteConfig(siteInfo)
return mw.returnJson(True, '添加成功!')
def add_excloud(self):
args = self.getArgs()
check = self.checkArgs(args, ['siteName', 'excludePath'])
if not check[0]:
return check[1]
siteName = args['siteName']
excludePath = args['excludePath'].strip()
siteInfo = self.__getFind(siteName)
if not siteInfo:
return mw.returnJson(False, '指定站点不存在!')
if not excludePath:
return mw.returnJson(False, '排除内容不能为空')
for excludePath in excludePath.split('\n'):
if not excludePath:
continue
if excludePath.find('/') != -1:
if not os.path.exists(excludePath):
continue
excludePath = excludePath.lower()
if excludePath[-1] == '/':
excludePath = excludePath[:-1]
if excludePath in siteInfo['excludePath']:
continue
siteInfo['excludePath'].insert(0, excludePath)
self.writeLog('站点[%s]添加排除目录名[%s]到排除列表' %
(siteInfo['siteName'], excludePath))
self.siteReload(siteInfo)
self.saveSiteConfig(siteInfo)
return mw.returnJson(True, '添加成功!')
def remove_excloud(self):
args = self.getArgs()
check = self.checkArgs(args, ['siteName', 'excludePath'])
if not check[0]:
return check[1]
siteName = args['siteName']
siteInfo = self.__getFind(siteName)
excludePath = args['excludePath'].strip()
if excludePath == '':
return mw.returnJson(False, '排除文件或目录不能为空')
if not siteInfo:
return mw.returnJson(False, '指定站点不存在!')
for excludePath in excludePath.split(','):
if not excludePath:
continue
if not excludePath in siteInfo['excludePath']:
continue
siteInfo['excludePath'].remove(excludePath)
self.writeLog('站点[%s]从排除列表中删除目录名[%s]' %
(siteInfo['siteName'], excludePath))
self.siteReload(siteInfo)
self.saveSiteConfig(siteInfo)
return mw.returnJson(True, '删除成功!')
def sim_test(self):
args = self.getArgs()
check = self.checkArgs(args, ['path'])
if not check[0]:
return check[1]
path = args['path'].strip()
if not os.path.exists(path):
return mw.returnJson(False, "此目录不存在")
# 判断是否安装php
import site_api
php_version = site_api.site_api().getPhpVersion()
if not php_version:
return mw.returnJson(False, "未安装PHP测试失败")
php_path = '/www/server/php/' + php_version[1]['version'] + '/bin/php'
php_name = path + "/" + str(int(time.time())) + ".php"
if os.path.exists(php_name):
mw.execShell("rm -rf %s" % php_name)
# 写入
cmd = php_path + \
" -r \"file_put_contents('{}','{}');\"".format(php_name, php_name)
mw.execShell(cmd)
time.sleep(0.5)
if os.path.exists(php_name):
if os.path.exists(php_name):
mw.execShell("rm -rf %s" % php_name)
return mw.returnJson(False, "拦截失败,可能未开启防篡改")
return mw.returnJson(True, "拦截成功")
def set_site_status_all(self):
args = self.getArgs()
check = self.checkArgs(args, ['siteNames', 'siteState'])
if not check[0]:
return check[1]
sites = self.getSites()
siteState = True if args['siteState'] == '1' else False
siteNames = json.loads(args['siteNames'])
m_logs = {True: '开启', False: '关闭'}
for i in range(len(sites)):
if sites[i]['siteName'] in siteNames:
sites[i]['open'] = siteState
self.writeLog('%s站点[%s]防篡改保护' %
(m_logs[siteState], sites[i]['siteName']))
self.writeSites(sites)
return mw.returnJson(True, '批量设置成功')
def get_index(self):
self.syncSites()
args = self.getArgs()
day = None
if 'day' in args:
day = args['day']
ser_status = self.status()
ser_status_bool = False
if ser_status == 'start':
ser_status_bool = True
data = {}
data['open'] = ser_status_bool
data['total'] = self.getTotal()
data['sites'] = self.getSites()
for i in range(len(data['sites'])):
data['sites'][i]['total'] = self.getTotal(
data['sites'][i]['siteName'], day)
return mw.returnJson(True, 'ok', data)
def get_speed(self):
print("12")
if __name__ == "__main__":
func = sys.argv[1]
classApp = App()
try:
data = eval("classApp." + func + "()")
print(data)
except Exception as e:
print(mw.getTracebackInfo())

@ -0,0 +1,15 @@
{
"title": "网站防篡改程序[PY]",
"tip": "lib",
"name": "tamper_proof_py",
"type": "soft",
"ps": "事件型防篡改程序,可有效保护网站重要文件不被木马篡改",
"versions": "1.0",
"shell": "install.sh",
"checks": "server/tamper_proof_py",
"path": "server/tamper_proof_py",
"author": "midoks",
"home": "",
"date":"2022-01-18",
"pid":"4"
}

@ -0,0 +1,15 @@
[Unit]
Description=tamper_proof_py server daemon
After=network.target
[Service]
Type=forking
ExecStart={$SERVER_PATH}/init.d/tamper_proof_py start
ExecStop={$SERVER_PATH}/init.d/tamper_proof_py stop
ExecReload={$SERVER_PATH}/init.d/tamper_proof_py reload
ExecRestart={$SERVER_PATH}/init.d/tamper_proof_py restart
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target

@ -0,0 +1,95 @@
#!/bin/bash
# chkconfig: 2345 55 25
# description:tamper_proof_service
### BEGIN INIT INFO
# Provides: tamper_proof_service
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts tamper_proof_service
# Description: starts the tamper_proof_service
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
mw_path={$SERVER_PATH}
rootPath=$(dirname "$mw_path")
PATH=$PATH:$mw_path/bin
if [ -f $rootPath/mdserver-web/bin/activate ];then
source $rootPath/mdserver-web/bin/activate
fi
# cd /www/server/mdserver-web && python3 plugins/tamper_proof_py/tamper_proof_service.py start
sys_start()
{
isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep |grep -v 'tamper_proof_py/tamper_proof_service.py start' | grep -v 'tamper_proof_py/tamper_proof_service.py reload' | grep -v 'tamper_proof_py/tamper_proof_service.py restart' | grep -v systemctl | grep -v '/bin/sh' | grep -v '/bin/bash' | awk '{print $2}'|xargs)
if [ "$isStart" == '' ];then
echo -e "Starting tamper_proof_service... \c"
cd $rootPath/mdserver-web
nohup python3 plugins/tamper_proof_py/tamper_proof_service.py start &> $mw_path/service.log &
sleep 0.5
isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|awk '{print $2}'|xargs)
if [ "$isStart" == '' ];then
echo -e "\033[31mfailed\033[0m"
echo '------------------------------------------------------'
cat $mw_path/service.log
echo '------------------------------------------------------'
echo -e "\033[31mError: tamper_proof_service startup failed.\033[0m"
return;
fi
echo -e "\033[32mdone\033[0m"
else
echo "Starting tamper_proof_service (pid $isStart) already running"
fi
}
sys_stop()
{
echo -e "Stopping tamper_proof_service... \c";
pids=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|grep -v '/bin/bash'|grep -v systemctl | grep -v 'tamper_proof_py/tamper_proof_service.py stop' | grep -v 'tamper_proof_py/tamper_proof_service.py reload' | grep -v 'tamper_proof_py/tamper_proof_service.py restart' |awk '{print $2}'|xargs)
arr=($pids)
for p in ${arr[@]}
do
kill -9 $p
done
cd $rootPath/mdserver-web
python3 plugins/tamper_proof_py/tamper_proof_service.py stop
echo -e "\033[32mdone\033[0m"
}
sys_status()
{
isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|grep -v "init.d/tamper_proof_py"|grep -v systemctl|awk '{print $2}'|xargs)
if [ "$isStart" != '' ];then
echo -e "\033[32mtamper_proof_service (pid $isStart) already running\033[0m"
else
echo -e "\033[32mtamper_proof_service not running\033[0m"
fi
}
case "$1" in
'start')
sys_start
;;
'stop')
sys_stop
;;
'restart')
sys_stop
sleep 0.2
sys_start
;;
'reload')
sys_stop
sleep 0.2
sys_start
;;
'status')
sys_status
;;
*)
echo "Usage: systemctl {start|stop|restart|reload} tamper_proof_service"
;;
esac

@ -0,0 +1,52 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
curPath=`pwd`
rootPath=$(dirname "$curPath")
rootPath=$(dirname "$rootPath")
serverPath=$(dirname "$rootPath")
pip install pyinotify
if [ -f ${rootPath}/bin/activate ];then
source ${rootPath}/bin/activate
fi
pip install pyinotify
install_tmp=${rootPath}/tmp/mw_install.pl
SYSOS=`uname`
VERSION=$2
APP_NAME=tamper_proof_py
Install_App()
{
echo '正在安装脚本文件...' > $install_tmp
mkdir -p $serverPath/tamper_proof_py
echo "$VERSION" > $serverPath/tamper_proof_py/version.pl
echo 'install complete' > $install_tmp
#初始化
cd ${serverPath}/mdserver-web && python3 plugins/tamper_proof_py/index.py start $VERSION
cd ${serverPath}/mdserver-web && python3 plugins/tamper_proof_py/index.py initd_install $VERSION
}
Uninstall_App()
{
if [ -f /usr/lib/systemd/system/${APP_NAME}.service ] || [ -f /lib/systemd/system/${APP_NAME}.service ] ;then
systemctl stop ${APP_NAME}
systemctl disable ${APP_NAME}
rm -rf /usr/lib/systemd/system/${APP_NAME}.service
rm -rf /lib/systemd/system/${APP_NAME}.service
systemctl daemon-reload
fi
rm -rf $serverPath/tamper_proof_py
echo "uninstall completed" > $install_tmp
}
action=$1
if [ "${1}" == 'install' ];then
Install_App
else
Uninstall_App
fi

@ -0,0 +1,552 @@
# +--------------------------------------------------------------------
# | 事件型防篡改
# +--------------------------------------------------------------------
import sys
import os
import pyinotify
import json
import shutil
import time
import psutil
import threading
import datetime
sys.path.append(os.getcwd() + "/class/core")
import mw
class MyEventHandler(pyinotify.ProcessEvent):
_PLUGIN_PATH = '/www/server/tamper_proof_py'
_CONFIG = '/config.json'
_SITES = '/sites.json'
_SITES_DATA = None
_CONFIG_DATA = None
_DONE_FILE = None
bakcupChirdPath = []
def __init__(self):
self._PLUGIN_PATH = self.getServerDir()
def getPluginName(self):
return 'tamper_proof_py'
def getPluginDir(self):
return mw.getPluginDir() + '/' + self.getPluginName()
def getServerDir(self):
return mw.getServerDir() + '/' + self.getPluginName()
def rmdir(self, filename):
try:
shutil.rmtree(filename)
except:
pass
def check_site_logs(self, Stiename, datetime_time):
ret = []
cur_month = datetime_time.month
cur_day = datetime_time.day
cur_year = datetime_time.year
cur_hour = datetime_time.hour
cur_minute = datetime_time.minute
cur_second = int(datetime_time.second)
months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07',
'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'}
logs_data = self.get_site_logs(Stiename)
if not logs_data:
return False
for i2 in logs_data:
try:
i = i2.split()
# 判断状态码是否为200
if int(i[8]) not in [200, 500]:
continue
# 判断是否为POST
day_time = i[3].split('/')[0].split('[')[1]
if int(cur_day) != int(day_time):
continue
month_time = i[3].split('/')[1]
if int(months[month_time]) != int(cur_month):
continue
year_time = i[3].split('/')[2].split(':')[0]
if int(year_time) != int(cur_year):
continue
hour_time = i[3].split('/')[2].split(':')[1]
if int(hour_time) != int(cur_hour):
continue
minute_time = i[3].split('/')[2].split(':')[2]
if int(minute_time) != int(cur_minute):
continue
second_time = int(i[3].split('/')[2].split(':')[3])
if cur_second - second_time > 10:
continue
ret.append(i2)
except:
continue
ret2 = []
if len(ret) > 20:
for i2 in logs_data:
try:
i = i2.split()
if i[6] != 'POST':
continue
if int(i[8]) not in [200, 500]:
continue
# 判断是否为POST
day_time = i[3].split('/')[0].split('[')[1]
if int(cur_day) != int(day_time):
continue
month_time = i[3].split('/')[1]
if int(months[month_time]) != int(cur_month):
continue
year_time = i[3].split('/')[2].split(':')[0]
if int(year_time) != int(cur_year):
continue
hour_time = i[3].split('/')[2].split(':')[1]
if int(hour_time) != int(cur_hour):
continue
minute_time = i[3].split('/')[2].split(':')[2]
if int(minute_time) != int(cur_minute):
continue
ret2.append(i2)
except:
continue
if ret2:
ret = ret2
if len(ret) > 20:
return ret[0:20]
return ret
def get_site_logs(self, Stiename):
try:
pythonV = sys.version_info[0]
path = '/www/wwwlogs/' + Stiename + '.log'
num = 500
if not os.path.exists(path):
return []
p = 1
start_line = (p - 1) * num
count = start_line + num
fp = open(path, 'rb')
buf = ""
try:
fp.seek(-1, 2)
except:
return []
if fp.read(1) == "\n":
fp.seek(-1, 2)
data = []
b = True
n = 0
c = 0
while c < count:
while True:
newline_pos = str.rfind(buf, "\n")
pos = fp.tell()
if newline_pos != -1:
if n >= start_line:
line = buf[newline_pos + 1:]
if line:
try:
data.append(line)
except:
c -= 1
n -= 1
pass
else:
c -= 1
n -= 1
buf = buf[:newline_pos]
n += 1
c += 1
break
else:
if pos == 0:
b = False
break
to_read = min(4096, pos)
fp.seek(-to_read, 1)
t_buf = fp.read(to_read)
if pythonV == 3:
t_buf = t_buf.decode('utf-8', errors="ignore")
buf = t_buf + buf
fp.seek(-to_read, 1)
if pos - to_read == 0:
buf = "\n" + buf
if not b:
break
fp.close()
except:
data = []
return data
def process_IN_CREATE(self, event):
siteInfo = self.get_SITE_CONFIG(event.pathname)
if not self.check_FILE(event, siteInfo, True):
return False
self._DONE_FILE = event.pathname
if event.dir:
if os.path.exists(event.pathname):
self.rmdir(event.pathname)
self.write_LOG('create', siteInfo[
'siteName'], event.pathname, datetime.datetime.now())
else:
if os.path.exists(event.pathname):
try:
src_path = os.path.dirname(event.pathname)
os.system("chattr -a {}".format(src_path))
os.remove(event.pathname)
os.system("chattr +a {}".format(src_path))
self.write_LOG('create', siteInfo[
'siteName'], event.pathname, datetime.datetime.now())
except:
pass
def process_IN_MOVED_TO(self, event):
# 检查是否受保护
siteInfo = self.get_SITE_CONFIG(event.pathname)
if not self.check_FILE(event, siteInfo):
return False
if not getattr(event, 'src_pathname', None):
if os.path.isdir(event.pathname):
self.rmdir(event.pathname)
else:
os.remove(event.pathname)
self.write_LOG('move', siteInfo[
'siteName'], '未知 -> ' + event.pathname)
return True
# 是否为标记文件
if event.src_pathname == self._DONE_FILE:
return False
if not os.path.exists(event.src_pathname):
# 标记
self._DONE_FILE = event.pathname
# 还原
os.renames(event.pathname, event.src_pathname)
# 记录日志
self.write_LOG('move', siteInfo['siteName'],
event.src_pathname + ' -> ' + event.pathname)
def check_FILE(self, event, siteInfo, create=False):
if not siteInfo:
return False
if self.exclude_PATH(event.pathname):
return False
if event.dir and create:
return True
if not event.dir:
if not self.protect_EXT(event.pathname):
return False
return True
def protect_EXT(self, pathname):
if pathname.find('.') == -1:
return False
extName = pathname.split('.')[-1].lower()
siteData = self.get_SITE_CONFIG(pathname)
if siteData:
if extName in siteData['protectExt']:
return True
return False
def exclude_PATH(self, pathname):
if pathname.find('/') == -1:
return False
siteData = self.get_SITE_CONFIG(pathname)
return self.exclude_PATH_OF_SITE(pathname, siteData['excludePath'])
def exclude_PATH_OF_SITE(self, pathname, excludePath):
pathname = pathname.lower()
dirNames = pathname.split('/')
if excludePath:
if pathname in excludePath:
return True
if pathname + '/' in excludePath:
return True
for ePath in excludePath:
if ePath in dirNames:
return True
if pathname.find(ePath) == 0:
return True
return False
def get_SITE_CONFIG(self, pathname):
if not self._SITES_DATA:
self._SITES_DATA = json.loads(
mw.readFile(self._PLUGIN_PATH + self._SITES))
for site in self._SITES_DATA:
length = len(site['path'])
if len(pathname) < length:
continue
if site['path'] != pathname[:length]:
continue
return site
return None
def get_CONFIG(self):
if self._CONFIG_DATA:
return self._CONFIG_DATA
self._CONFIG_DATA = json.loads(
mw.readFile(self._PLUGIN_PATH + self._CONFIG))
def list_DIR(self, path, siteInfo): # path 站点路径
if not os.path.exists(path):
return
lock_files = []
lock_dirs = []
explode_a = ['log', 'logs', 'cache', 'templates', 'template', 'upload', 'img',
'image', 'images', 'public', 'static', 'js', 'css', 'tmp', 'temp', 'update', 'data']
for name in os.listdir(path):
try:
filename = "{}/{}".format(path, name).replace('//', '/')
lower_name = name.lower()
lower_filename = filename.lower()
if os.path.isdir(filename): # 是否为目录
if lower_name in siteInfo['excludePath']:
continue # 是否为排除的文件名
# 是否为排除目录
if not self.exclude_PATH_OF_SITE(filename, siteInfo['excludePath']):
if not lower_name in explode_a: # 是否为固定不锁定目录
lock_dirs.append('"' + name + '"')
self.list_DIR(filename, siteInfo)
continue
# 是否为受保护的文件名或文件全路径
if not lower_name in siteInfo['protectExt'] and not lower_filename in siteInfo['protectExt']:
if not self.get_EXT_NAME(lower_name) in siteInfo['protectExt']:
continue # 是否为受保护文件类型
if lower_filename in siteInfo['excludePath']:
continue # 是否为排除文件
if lower_name in siteInfo['excludePath']:
continue # 是否为排除的文件名
lock_files.append('"' + name + '"')
except:
print(mw.getTracebackInfo())
if lock_files:
self.thread_exec(lock_files, path, 'i')
if lock_dirs:
self.thread_exec(lock_dirs, path, 'a')
_thread_count = 0
_thread_max = 2 * psutil.cpu_count()
def thread_exec(self, file_list, cwd, i='i'):
while self._thread_count > self._thread_max:
time.sleep(0.1)
self._thread_count += 1
cmd = "cd {} && chattr +{} {} > /dev/null".format(
cwd, i, ' '.join(file_list))
p = threading.Thread(target=self.run_thread, args=(cmd,))
p.start()
def run_thread(self, cmd):
os.system(cmd)
self._thread_count -= 1
def get_EXT_NAME(self, fileName):
return fileName.split('.')[-1]
def write_LOG(self, eventType, siteName, pathname, datetime):
# 获取网站时间的top100 记录
site_log = '/www/wwwlogs/%s.log' % siteName
logs_data = []
if os.path.exists(site_log):
logs_data = self.check_site_logs(siteName, datetime)
dateDay = time.strftime("%Y-%m-%d", time.localtime())
logPath = self._PLUGIN_PATH + '/sites/' + \
siteName + '/day/' + dateDay
if not os.path.exists(logPath):
os.makedirs(logPath)
logFile = os.path.join(logPath, 'logs.json')
logVar = [int(time.time()), eventType, pathname, logs_data]
fp = open(logFile, 'a+')
fp.write(json.dumps(logVar) + "\n")
fp.close()
logFiles = [
logPath + '/total.json',
self._PLUGIN_PATH + '/sites/' + siteName + '/day/total.json',
self._PLUGIN_PATH + '/sites/total.json'
]
for totalLogFile in logFiles:
if not os.path.exists(totalLogFile):
totalData = {"total": 0, "delete": 0,
"create": 0, "modify": 0, "move": 0}
else:
dataTmp = mw.readFile(totalLogFile)
if dataTmp:
totalData = json.loads(dataTmp)
else:
totalData = {"total": 0, "delete": 0,
"create": 0, "modify": 0, "move": 0}
totalData['total'] += 1
totalData[eventType] += 1
mw.writeFile(totalLogFile, json.dumps(totalData))
# 设置.user.ini
def set_user_ini(self, path, up=0):
os.chdir(path)
useriniPath = path + '/.user.ini'
if os.path.exists(useriniPath):
os.system('chattr +i ' + useriniPath)
for p1 in os.listdir(path):
try:
npath = path + '/' + p1
if not os.path.isdir(npath):
continue
useriniPath = npath + '/.user.ini'
if os.path.exists(useriniPath):
os.system('chattr +i ' + useriniPath)
if up < 3:
self.set_user_ini(npath, up + 1)
except:
continue
return True
def unlock(self, path):
os.system('chattr -R -i {} &> /dev/null'.format(path))
os.system('chattr -R -a {} &> /dev/null'.format(path))
self.set_user_ini(path)
def close(self, reload=False):
# 解除锁定
sites = self.get_sites()
print("")
print("=" * 60)
print("{}】正在关闭防篡改,请稍候...".format(mw.formatDate()))
print("-" * 60)
for siteInfo in sites:
tip = self._PLUGIN_PATH + '/tips/' + siteInfo['siteName'] + '.pl'
if not siteInfo['open'] and not os.path.exists(tip):
continue
if reload and siteInfo['open']:
continue
if sys.version_info[0] == 2:
print(
"{}】|-解锁网站[{}]".format(mw.formatDate(), siteInfo['siteName'])),
else:
os.system(
"echo -e '{}】|-解锁网站[{}]\c'".format(mw.formatDate(), siteInfo['siteName']))
#print("【{}】|-解锁网站[{}]".format(mw.format_date(),siteInfo['siteName']),end=" ")
self.unlock(siteInfo['path'])
if os.path.exists(tip):
os.remove(tip)
print("\t=> 完成")
print("-" * 60)
print('|-防篡改已关闭')
print("=" * 60)
print(">>>>>>>>>>END<<<<<<<<<<")
# 获取网站配置列表
def get_sites(self):
siteconf = self._PLUGIN_PATH + '/sites.json'
d = mw.readFile(siteconf)
if not os.path.exists(siteconf) or not d:
mw.writeFile(siteconf, "[]")
data = json.loads(mw.readFile(siteconf))
# 处理多余字段开始 >>>>>>>>>>
is_write = False
rm_keys = ['lock', 'bak_open']
for i in data:
i_keys = i.keys()
if not 'open' in i_keys:
i['open'] = False
for o in rm_keys:
if o in i_keys:
if i[o]:
i['open'] = True
i.pop(o)
is_write = True
if is_write:
mw.writeFile(siteconf, json.dumps(data))
# 处理多余字段结束 <<<<<<<<<<<<<
return data
def __enter__(self):
self.close()
def __exit__(self, a, b, c):
self.close()
def run():
# 初始化inotify对像
event = MyEventHandler()
watchManager = pyinotify.WatchManager()
starttime = time.time()
mode = pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO
# 处理网站属性
sites = event.get_sites()
print("=" * 60)
print("{}】正在启动防篡改,请稍候...".format(mw.formatDate()))
print("-" * 60)
tip_path = event._PLUGIN_PATH + '/tips/'
if not os.path.exists(tip_path):
os.makedirs(tip_path)
speed_file = event._PLUGIN_PATH + '/speed.pl'
for siteInfo in sites:
s = time.time()
tip = tip_path + siteInfo['siteName'] + '.pl'
if not siteInfo['open']:
continue
if sys.version_info[0] == 2:
print("{}】|-网站[{}]".format(mw.formatDate(),
siteInfo['siteName'])),
else:
os.system(
"echo -e '{}】|-网站[{}]\c'".format(mw.formatDate(), siteInfo['siteName']))
# print("【{}】|-网站[{}]".format(public.format_date(),siteInfo['siteName']),end=" ")
mw.writeFile(speed_file, "正在处理网站[{}],请稍候...".format(
siteInfo['siteName']))
if not os.path.exists(tip):
event.list_DIR(siteInfo['path'], siteInfo)
try:
watchManager.add_watch(
siteInfo['path'], mode, auto_add=True, rec=True)
except:
print(mw.getTracebackInfo())
tout = round(time.time() - s, 2)
mw.writeFile(tip, '1')
print("\t\t=> 完成,耗时 {}".format(tout))
# 启动服务
endtime = round(time.time() - starttime, 2)
mw.writeLog('防篡改程序', "网站防篡改服务已成功启动,耗时[%s]秒" % endtime)
notifier = pyinotify.Notifier(watchManager, event)
print("-" * 60)
print('|-防篡改服务已启动')
print("=" * 60)
end_tips = ">>>>>>>>>>END<<<<<<<<<<"
print(end_tips)
mw.writeFile(speed_file, end_tips)
notifier.loop()
if __name__ == '__main__':
if len(sys.argv) > 1:
if 'stop' in sys.argv:
event = MyEventHandler()
event.close()
elif 'start' in sys.argv:
run()
elif 'unlock' in sys.argv:
event = MyEventHandler()
event.unlock(sys.argv[2])
elif 'reload' in sys.argv:
event = MyEventHandler()
event.close(True)
else:
print('error')
else:
run()
Loading…
Cancel
Save